From a8097ca9b13a8675489686ff6d6455b07c7cb88d Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Thu, 16 Apr 2026 22:28:03 +0530 Subject: [PATCH 01/16] wip --- app/build.gradle.kts | 9 ++- .../android/activities/ActivitySearch.java | 2 +- .../adapters/search/ADPVerseResults.java | 2 +- .../compose/components/VerseOfTheDay.kt | 4 +- .../homepage/HomeSectionFeaturedReading.kt | 2 +- .../reader/dialogs/BookmarkViewerSheet.kt | 2 +- .../reader/dialogs/QuickReference.kt | 2 +- .../reader/dialogs/VerseShareSheet.kt | 2 +- .../screens/reference/ReferenceScreen.kt | 2 +- .../quranapp/android/db/DatabaseProvider.kt | 40 +++++----- .../android/db/ExternalQuranDatabase.kt | 18 +++++ .../com/quranapp/android/db/dao/WbwDao.kt | 13 ++++ .../{db => repository}/QuranRepository.kt | 10 ++- .../{db => repository}/UserRepository.kt | 3 +- .../mediaplayer/RecitationServiceState.kt | 2 +- .../android/utils/quran/parser/ParserUtils.kt | 2 +- .../utils/reader/ReaderItemsBuilder.kt | 2 +- .../android/utils/verse/VerseUtils.kt | 2 +- .../android/views/reader/VotdWidget.kt | 2 +- build.gradle.kts | 4 + gradle.properties | 11 ++- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- inventory/versions/resources_versions.json | 3 +- inventory/wbw/available_wbw_info.json | 76 +++++++++++++++++++ peacedesign/build.gradle | 7 +- 26 files changed, 182 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/com/quranapp/android/db/ExternalQuranDatabase.kt create mode 100644 app/src/main/java/com/quranapp/android/db/dao/WbwDao.kt rename app/src/main/java/com/quranapp/android/{db => repository}/QuranRepository.kt (98%) rename app/src/main/java/com/quranapp/android/{db => repository}/UserRepository.kt (98%) create mode 100644 inventory/wbw/available_wbw_info.json diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cf112a854..3bc415d4c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,14 +15,13 @@ android { applicationId = "com.quranapp.android" minSdk = 24 targetSdk = 35 + // I don't know why I've used such a weird versioning scheme in the beginning, // but I can't change it now as the app is already in the Play Store // now just incrementing from there versionCode = 23_11_11_134 versionName = "2026.04.16.2" - setProperty("archivesBaseName", versionName) - resValue("string", "app_name", "QuranApp") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -52,6 +51,7 @@ android { applicationIdSuffix = ".debug" versionNameSuffix = "-debug" + resValue("string", "app_name", "QuranApp Debug") /* ---------------------------------------------------------------- */ @@ -62,6 +62,7 @@ android { isDebuggable = false isMinifyEnabled = true isShrinkResources = true + proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -90,6 +91,10 @@ android { } } +base { + archivesName = android.defaultConfig.versionName +} + dependencies { testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") diff --git a/app/src/main/java/com/quranapp/android/activities/ActivitySearch.java b/app/src/main/java/com/quranapp/android/activities/ActivitySearch.java index e0e804804..3b2c96869 100644 --- a/app/src/main/java/com/quranapp/android/activities/ActivitySearch.java +++ b/app/src/main/java/com/quranapp/android/activities/ActivitySearch.java @@ -44,7 +44,7 @@ import com.quranapp.android.compose.utils.preferences.ReaderPreferences; import com.quranapp.android.databinding.ActivitySearchBinding; import com.quranapp.android.db.DatabaseProvider; -import com.quranapp.android.db.UserRepository; +import com.quranapp.android.repository.UserRepository; import com.quranapp.android.db.search.SearchHistoryDBHelper; import com.quranapp.android.frags.search.FragSearchResult; import com.quranapp.android.frags.search.FragSearchSuggestions; diff --git a/app/src/main/java/com/quranapp/android/adapters/search/ADPVerseResults.java b/app/src/main/java/com/quranapp/android/adapters/search/ADPVerseResults.java index ca54307ae..f9577e578 100644 --- a/app/src/main/java/com/quranapp/android/adapters/search/ADPVerseResults.java +++ b/app/src/main/java/com/quranapp/android/adapters/search/ADPVerseResults.java @@ -48,7 +48,7 @@ import com.quranapp.android.components.search.VerseResultModel; import com.quranapp.android.databinding.LytReaderJuzSpinnerItemBinding; import com.quranapp.android.databinding.LytSearchResultItemBinding; -import com.quranapp.android.db.UserRepository; +import com.quranapp.android.repository.UserRepository; import com.quranapp.android.frags.search.FragSearchResult; import com.quranapp.android.interfaceUtils.Destroyable; import com.quranapp.android.utils.extensions.ContextKt; diff --git a/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt b/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt index c4846826f..7ed2a0fed 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt @@ -91,7 +91,6 @@ fun VerseOfTheDay() { @Composable private fun VotdContent() { val context = LocalContext.current - val coroutineScope = rememberCoroutineScope() val colors = colorScheme val type = typography @@ -163,9 +162,8 @@ private fun VotdContent() { return } - val userRepository = remember(context) { DatabaseProvider.getUserRepository(context) } val verse = votdState.verse - val isBookmarked by userRepository.isBookmarkedFlow( + val isBookmarked by vm.userRepository.isBookmarkedFlow( verse.chapterNo, verse.verseNo..verse.verseNo ).collectAsStateWithLifecycle(false) diff --git a/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionFeaturedReading.kt b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionFeaturedReading.kt index b9ce56857..7e6e31b2c 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionFeaturedReading.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionFeaturedReading.kt @@ -38,7 +38,7 @@ import com.quranapp.android.R import com.quranapp.android.compose.utils.appLocale import com.quranapp.android.compose.theme.alpha import com.quranapp.android.db.DatabaseProvider -import com.quranapp.android.db.QuranRepository +import com.quranapp.android.repository.QuranRepository import com.quranapp.android.utils.reader.factory.ReaderFactory private data class FeaturedQuranModel( diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/BookmarkViewerSheet.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/BookmarkViewerSheet.kt index f790cf46a..c624a2b59 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/BookmarkViewerSheet.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/BookmarkViewerSheet.kt @@ -50,7 +50,7 @@ import com.quranapp.android.compose.components.dialogs.AlertDialogAction import com.quranapp.android.compose.components.dialogs.AlertDialogActionStyle import com.quranapp.android.compose.theme.alpha import com.quranapp.android.db.DatabaseProvider -import com.quranapp.android.db.UserRepository +import com.quranapp.android.repository.UserRepository import com.quranapp.android.utils.extensions.orMinusOne import com.quranapp.android.utils.quran.QuranUtils import com.quranapp.android.utils.reader.factory.ReaderFactory diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt index ad9dfdee0..c76f5e142 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt @@ -49,7 +49,7 @@ import com.quranapp.android.compose.extensions.bottomBorder import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.DatabaseProvider -import com.quranapp.android.db.UserRepository +import com.quranapp.android.repository.UserRepository import com.quranapp.android.utils.reader.FontResolver import com.quranapp.android.utils.reader.LocalVerseActions import com.quranapp.android.utils.reader.ReaderItemsBuilder diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/VerseShareSheet.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/VerseShareSheet.kt index 928a45aeb..89b9525e6 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/VerseShareSheet.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/VerseShareSheet.kt @@ -46,7 +46,7 @@ import com.quranapp.android.compose.components.common.Chip import com.quranapp.android.compose.components.dialogs.BottomSheetHeader import com.quranapp.android.compose.theme.alpha import com.quranapp.android.db.DatabaseProvider -import com.quranapp.android.db.QuranRepository +import com.quranapp.android.repository.QuranRepository import com.quranapp.android.utils.extensions.copyToClipboard import com.quranapp.android.utils.reader.QuranScriptUtils import com.quranapp.android.utils.reader.factory.QuranTranslationFactory diff --git a/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt index c1599e325..f33b46147 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt @@ -71,7 +71,7 @@ import com.quranapp.android.compose.components.reader.dialogs.QuickReferenceVers import com.quranapp.android.compose.components.reader.dialogs.parseVerses import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences -import com.quranapp.android.db.QuranRepository +import com.quranapp.android.repository.QuranRepository import com.quranapp.android.db.entities.BookmarkKey import com.quranapp.android.utils.extensions.isSingleValue import com.quranapp.android.utils.reader.LocalVerseActions diff --git a/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt b/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt index 029dc3e4f..69f393846 100644 --- a/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt +++ b/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt @@ -2,15 +2,14 @@ package com.quranapp.android.db import android.content.Context import androidx.room.Room +import com.quranapp.android.repository.QuranRepository +import com.quranapp.android.repository.UserRepository object DatabaseProvider { @Volatile private var userDatabase: UserDatabase? = null - @Volatile - private var quranDatabase: QuranDatabase? = null - @Volatile private var userRepository: UserRepository? = null @@ -40,25 +39,32 @@ object DatabaseProvider { } } - fun getQuranDatabase(context: Context): QuranDatabase { - return quranDatabase ?: synchronized(this) { - quranDatabase ?: Room.databaseBuilder( - context.applicationContext, - QuranDatabase::class.java, - "quranapp" - ) - .createFromAsset("db/quranapp.db") - .fallbackToDestructiveMigration(true) - .build() - .also { quranDatabase = it } - } + private fun getQuranDatabase(context: Context): QuranDatabase { + return Room.databaseBuilder( + context.applicationContext, + QuranDatabase::class.java, + "quranapp" + ) + .createFromAsset("db/quranapp.db") + .fallbackToDestructiveMigration(true) + .build() + } + + private fun getExternalQuranDatabase(context: Context): ExternalQuranDatabase { + return Room.databaseBuilder( + context.applicationContext, + ExternalQuranDatabase::class.java, + "quranapp_external" + ) + .fallbackToDestructiveMigration(false) + .build() } fun getQuranRepository(context: Context): QuranRepository { return quranRepository ?: synchronized(this) { quranRepository ?: QuranRepository( - context.applicationContext, - getQuranDatabase(context) + getQuranDatabase(context), + getExternalQuranDatabase(context) ).also { quranRepository = it } } } diff --git a/app/src/main/java/com/quranapp/android/db/ExternalQuranDatabase.kt b/app/src/main/java/com/quranapp/android/db/ExternalQuranDatabase.kt new file mode 100644 index 000000000..366253c7a --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/ExternalQuranDatabase.kt @@ -0,0 +1,18 @@ +package com.quranapp.android.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.quranapp.android.db.converters.QuranConverters +import com.quranapp.android.db.dao.WbwDao + +@Database( + entities = [ + ], + version = 1, + exportSchema = false +) +@TypeConverters(QuranConverters::class) +abstract class ExternalQuranDatabase : RoomDatabase() { + abstract fun wbwDao(): WbwDao +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/db/dao/WbwDao.kt b/app/src/main/java/com/quranapp/android/db/dao/WbwDao.kt new file mode 100644 index 000000000..57e293559 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/dao/WbwDao.kt @@ -0,0 +1,13 @@ +package com.quranapp.android.db.dao + +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Transaction +import com.quranapp.android.db.entities.quran.SurahEntity +import com.quranapp.android.db.entities.quran.SurahLocalizationEntity +import com.quranapp.android.db.relations.SurahWithLocalizations +import kotlinx.coroutines.flow.Flow + +@Dao +interface WbwDao { +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/db/QuranRepository.kt b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt similarity index 98% rename from app/src/main/java/com/quranapp/android/db/QuranRepository.kt rename to app/src/main/java/com/quranapp/android/repository/QuranRepository.kt index 205cae715..b0e43ed3b 100644 --- a/app/src/main/java/com/quranapp/android/db/QuranRepository.kt +++ b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt @@ -1,8 +1,10 @@ -package com.quranapp.android.db +package com.quranapp.android.repository -import android.content.Context import com.quranapp.android.compose.utils.appFallbackLanguageCodes import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.db.ChapterVerseBatch +import com.quranapp.android.db.ExternalQuranDatabase +import com.quranapp.android.db.QuranDatabase import com.quranapp.android.db.entities.quran.AyahEntity import com.quranapp.android.db.entities.quran.AyahWordEntity import com.quranapp.android.db.entities.quran.MushafLineType @@ -20,8 +22,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine class QuranRepository( - @Suppress("UNUSED_PARAMETER") context: Context, - private val database: QuranDatabase + private val database: QuranDatabase, + private val extDatabase: ExternalQuranDatabase ) { private val mushafDao get() = database.mushafDao() private val ayahDao get() = database.ayahDao() diff --git a/app/src/main/java/com/quranapp/android/db/UserRepository.kt b/app/src/main/java/com/quranapp/android/repository/UserRepository.kt similarity index 98% rename from app/src/main/java/com/quranapp/android/db/UserRepository.kt rename to app/src/main/java/com/quranapp/android/repository/UserRepository.kt index 9299b8857..8881e85a7 100644 --- a/app/src/main/java/com/quranapp/android/db/UserRepository.kt +++ b/app/src/main/java/com/quranapp/android/repository/UserRepository.kt @@ -1,4 +1,4 @@ -package com.quranapp.android.db +package com.quranapp.android.repository import android.content.Context import android.widget.Toast @@ -6,6 +6,7 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import com.quranapp.android.R +import com.quranapp.android.db.UserDatabase import com.quranapp.android.db.entities.BookmarkEntity import com.quranapp.android.db.entities.ReadHistoryEntity import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationServiceState.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationServiceState.kt index 0b8283795..6457ec498 100644 --- a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationServiceState.kt +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationServiceState.kt @@ -3,7 +3,7 @@ package com.quranapp.android.utils.mediaplayer import android.os.Bundle import com.quranapp.android.components.reader.ChapterVersePair import com.quranapp.android.compose.components.player.dialogs.AudioOption -import com.quranapp.android.db.QuranRepository +import com.quranapp.android.repository.QuranRepository import com.quranapp.android.utils.quran.QuranMeta enum class PlayerInterationSource { diff --git a/app/src/main/java/com/quranapp/android/utils/quran/parser/ParserUtils.kt b/app/src/main/java/com/quranapp/android/utils/quran/parser/ParserUtils.kt index a8c0f5413..f26765fd4 100644 --- a/app/src/main/java/com/quranapp/android/utils/quran/parser/ParserUtils.kt +++ b/app/src/main/java/com/quranapp/android/utils/quran/parser/ParserUtils.kt @@ -5,7 +5,7 @@ package com.quranapp.android.utils.quran.parser import android.content.Context import com.quranapp.android.R -import com.quranapp.android.db.QuranRepository +import com.quranapp.android.repository.QuranRepository object ParserUtils { @JvmStatic diff --git a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt index 97869c176..fd8096381 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt @@ -13,7 +13,7 @@ import com.quranapp.android.compose.components.reader.QuranPageLineItem import com.quranapp.android.compose.components.reader.ReaderLayoutItem import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences -import com.quranapp.android.db.QuranRepository +import com.quranapp.android.repository.QuranRepository import com.quranapp.android.db.entities.quran.AyahWordEntity import com.quranapp.android.db.entities.quran.MushafLineType import com.quranapp.android.db.entities.quran.MushafMapEntity diff --git a/app/src/main/java/com/quranapp/android/utils/verse/VerseUtils.kt b/app/src/main/java/com/quranapp/android/utils/verse/VerseUtils.kt index 0276ebad6..41ffaab8c 100644 --- a/app/src/main/java/com/quranapp/android/utils/verse/VerseUtils.kt +++ b/app/src/main/java/com/quranapp/android/utils/verse/VerseUtils.kt @@ -3,7 +3,7 @@ package com.quranapp.android.utils.verse import android.content.Context import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.compose.utils.preferences.VersePreferences -import com.quranapp.android.db.QuranRepository +import com.quranapp.android.repository.QuranRepository import com.quranapp.android.db.relations.VerseWithDetails import com.quranapp.android.utils.others.ShortcutUtils import com.quranapp.android.utils.quran.QuranMeta diff --git a/app/src/main/java/com/quranapp/android/views/reader/VotdWidget.kt b/app/src/main/java/com/quranapp/android/views/reader/VotdWidget.kt index 3a4848f45..a00381ed3 100644 --- a/app/src/main/java/com/quranapp/android/views/reader/VotdWidget.kt +++ b/app/src/main/java/com/quranapp/android/views/reader/VotdWidget.kt @@ -22,7 +22,7 @@ import com.quranapp.android.R import com.quranapp.android.activities.ActivityReader import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.DatabaseProvider -import com.quranapp.android.db.QuranRepository +import com.quranapp.android.repository.QuranRepository import com.quranapp.android.db.relations.VerseWithDetails import com.quranapp.android.utils.extensions.dp2px import com.quranapp.android.utils.extensions.getFont diff --git a/build.gradle.kts b/build.gradle.kts index 13ec8f4d6..914a29e1c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. + +plugins { +} + buildscript { extra["kotlin_version"] = "2.2.21" diff --git a/gradle.properties b/gradle.properties index eaee8bfd2..99a42a002 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,15 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding\=UTF-8 android.useAndroidX=true android.enableJetifier=true -android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false android.nonFinalResIds=false +android.defaults.buildfeatures.resvalues=true +android.sdk.defaultTargetSdkToCompileSdkIfUnset=false +android.enableAppCompileTimeRClass=false +android.usesSdkInManifest.disallowed=false +android.uniquePackageNames=false +android.dependency.useConstraints=true +android.r8.strictFullModeForKeepRules=false +android.r8.optimizedResourceShrinking=false +android.builtInKotlin=false +android.newDsl=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 31c3520e9..d8a496e77 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] coreKtx = "1.15.0" -gradle = "8.11.2" +gradle = "9.1.0" kotlinGradlePlugin = "2.2.21" lifecycle = "2.8.7" desugaring = "2.1.4" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1dd7ae2f6..17e0ec1a8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Feb 05 09:53:30 IST 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/inventory/versions/resources_versions.json b/inventory/versions/resources_versions.json index b7e10604e..adf01f35f 100644 --- a/inventory/versions/resources_versions.json +++ b/inventory/versions/resources_versions.json @@ -3,5 +3,6 @@ "translations": 16, "recitations": 4, "recitationTranslations": 7, - "tafsirs": 1 + "tafsirs": 1, + "wbw": 1 } diff --git a/inventory/wbw/available_wbw_info.json b/inventory/wbw/available_wbw_info.json new file mode 100644 index 000000000..80aa8604c --- /dev/null +++ b/inventory/wbw/available_wbw_info.json @@ -0,0 +1,76 @@ +{ + "wbw": [ + { + "id": "wbw_en", + "lang_code": "en", + "lang_name": "English", + "has_translation": true, + "has_transliteration": true, + "url": "ghraw://AlfaazPlus/QuranAppInventory/master/wbw/wbw_en.json.gz", + "version": 1 + }, + { + "id": "wbw_bn", + "lang_code": "bn", + "lang_name": "বাংলা", + "has_translation": true, + "has_transliteration": false, + "url": "ghraw://AlfaazPlus/QuranAppInventory/master/wbw/wbw_bn.json.gz", + "version": 1 + }, + { + "id": "wbw_fa", + "lang_code": "fa", + "lang_name": "فارسی", + "has_translation": true, + "has_transliteration": false, + "url": "ghraw://AlfaazPlus/QuranAppInventory/master/wbw/wbw_fa.json.gz", + "version": 1 + }, + { + "id": "wbw_fr", + "lang_code": "fr", + "lang_name": "Français", + "has_translation": true, + "has_transliteration": false, + "url": "ghraw://AlfaazPlus/QuranAppInventory/master/wbw/wbw_fr.json.gz", + "version": 1 + }, + { + "id": "wbw_hi", + "lang_code": "hi", + "lang_name": "हिंदी", + "has_translation": true, + "has_transliteration": false, + "url": "ghraw://AlfaazPlus/QuranAppInventory/master/wbw/wbw_hi.json.gz", + "version": 1 + }, + { + "id": "wbw_id", + "lang_code": "id", + "lang_name": "Bahasa Indonesia", + "has_translation": true, + "has_transliteration": false, + "url": "ghraw://AlfaazPlus/QuranAppInventory/master/wbw/wbw_id.json.gz", + "version": 1 + }, + { + "id": "wbw_tr", + "lang_code": "tr", + "lang_name": "Türkçe", + "has_translation": true, + "has_transliteration": false, + "url": "ghraw://AlfaazPlus/QuranAppInventory/master/wbw/wbw_tr.json.gz", + "version": 1 + }, + { + "id": "wbw_ur", + "lang_code": "ur", + "lang_name": "اردو", + "has_translation": true, + "has_transliteration": false, + "url": "ghraw://AlfaazPlus/QuranAppInventory/master/wbw/wbw_ur.json.gz", + "version": 1 + } + ] +} diff --git a/peacedesign/build.gradle b/peacedesign/build.gradle index 364abf3d2..5f6bb8ab5 100644 --- a/peacedesign/build.gradle +++ b/peacedesign/build.gradle @@ -7,7 +7,6 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -31,6 +30,12 @@ android { } } namespace 'com.peacedesign' + lint { + targetSdk 34 + } + testOptions { + targetSdk 34 + } } dependencies { From fc2895016f91a4ba399c471d109ef1976725ad2a Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Sat, 18 Apr 2026 03:37:42 +0530 Subject: [PATCH 02/16] Wbw - translation / transliteration / audio --- app/build.gradle.kts | 10 +- .../1.json | 67 +++ .../com/quranapp/android/api/ApiConfig.kt | 4 +- .../com/quranapp/android/api/GithubApi.kt | 4 + .../com/quranapp/android/api/GithubLikeApi.kt | 2 +- .../api/models/wbw/AvailableWbwInfoModel.kt | 41 ++ .../compose/components/VerseOfTheDay.kt | 6 +- .../compose/components/common/SwitchItem.kt | 6 +- .../compose/components/reader/Mushaf.kt | 4 +- .../compose/components/reader/QuranText.kt | 21 - .../compose/components/reader/QuranTextWbw.kt | 129 ++++++ .../compose/components/reader/ReaderLayout.kt | 62 +-- .../components/reader/ReaderProvider.kt | 50 ++- .../components/reader/TextStyleProvider.kt | 70 +++ .../compose/components/reader/VerseView.kt | 24 +- .../reader/dialogs/FootnotePresenter.kt | 3 +- .../reader/dialogs/QuickReference.kt | 38 +- .../compose/navigation/SettingRoutes.kt | 1 + .../screens/reference/ReferenceScreen.kt | 145 +++--- .../screens/settings/SettingsMainScreen.kt | 7 + .../screens/settings/SettingsScreen.kt | 1 + .../screens/settings/SettingsWbwScreen.kt | 412 ++++++++++++++++++ .../utils/preferences/ReaderPreferences.kt | 135 ++++++ .../quranapp/android/db/DatabaseProvider.kt | 2 +- .../android/db/ExternalQuranDatabase.kt | 4 +- .../com/quranapp/android/db/dao/WbwDao.kt | 54 ++- .../android/db/entities/wbw/WbwWordEntity.kt | 25 ++ .../android/repository/QuranRepository.kt | 26 +- .../utils/managers/WbwDownloadManager.kt | 130 ++++++ .../mediaplayer/RecitationAudioRepository.kt | 2 +- .../utils/mediaplayer/WbwAudioPlayer.kt | 110 +++++ .../utils/reader/ReaderItemsBuilder.kt | 156 +++---- .../utils/reader/ReaderTextSizeUtils.kt | 12 +- .../android/utils/reader/wbw/WbwManager.kt | 147 +++++++ .../utils/reader/wbw/WbwVersionStore.kt | 29 ++ .../utils/workers/WbwDownloadWorker.kt | 217 +++++++++ .../android/viewModels/ReaderViewModel.kt | 31 +- .../viewModels/WbwSettingsViewModel.kt | 214 +++++++++ app/src/main/res/values/strings.xml | 18 + gradle/libs.versions.toml | 10 - inventory/versions/resources_versions.json | 3 +- inventory/wbw/available_wbw_info.json | 1 + 42 files changed, 2140 insertions(+), 293 deletions(-) create mode 100644 app/schemas/com.quranapp.android.db.ExternalQuranDatabase/1.json create mode 100644 app/src/main/java/com/quranapp/android/api/models/wbw/AvailableWbwInfoModel.kt delete mode 100644 app/src/main/java/com/quranapp/android/compose/components/reader/QuranText.kt create mode 100644 app/src/main/java/com/quranapp/android/compose/components/reader/QuranTextWbw.kt create mode 100644 app/src/main/java/com/quranapp/android/compose/components/reader/TextStyleProvider.kt create mode 100644 app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsWbwScreen.kt create mode 100644 app/src/main/java/com/quranapp/android/db/entities/wbw/WbwWordEntity.kt create mode 100644 app/src/main/java/com/quranapp/android/utils/managers/WbwDownloadManager.kt create mode 100644 app/src/main/java/com/quranapp/android/utils/mediaplayer/WbwAudioPlayer.kt create mode 100644 app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwManager.kt create mode 100644 app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwVersionStore.kt create mode 100644 app/src/main/java/com/quranapp/android/utils/workers/WbwDownloadWorker.kt create mode 100644 app/src/main/java/com/quranapp/android/viewModels/WbwSettingsViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3bc415d4c..00aa03362 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -41,6 +41,7 @@ android { viewBinding = true dataBinding = true compose = true + buildConfig = true } buildTypes { @@ -144,15 +145,6 @@ dependencies { implementation(libs.kotlinxSerialization) implementation(libs.kotlinxRetrofit) - /* SmoothRefreshLayout */ - implementation(libs.srlCore) - implementation(libs.srlExtClassics) - implementation(libs.srlExtMaterial) - implementation(libs.srlExtDynamicRebound) - implementation(libs.srlExtHorizontal) - implementation(libs.srlExtTwoLevel) - implementation(libs.srlExtUtil) - implementation(libs.commonsCompress) implementation(libs.workManager) implementation(libs.dataStore) diff --git a/app/schemas/com.quranapp.android.db.ExternalQuranDatabase/1.json b/app/schemas/com.quranapp.android.db.ExternalQuranDatabase/1.json new file mode 100644 index 000000000..1e310598f --- /dev/null +++ b/app/schemas/com.quranapp.android.db.ExternalQuranDatabase/1.json @@ -0,0 +1,67 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "1509d1159d73e7c5d7d0599bf134c6a3", + "entities": [ + { + "tableName": "wbw_words", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`ayah_id` INTEGER NOT NULL, `word_index` INTEGER NOT NULL, `wbw_id` TEXT NOT NULL, `translation` TEXT, `transliteration` TEXT, PRIMARY KEY(`wbw_id`, `ayah_id`, `word_index`))", + "fields": [ + { + "fieldPath": "ayahId", + "columnName": "ayah_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "wordIndex", + "columnName": "word_index", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "wbwId", + "columnName": "wbw_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "translation", + "columnName": "translation", + "affinity": "TEXT" + }, + { + "fieldPath": "transliteration", + "columnName": "transliteration", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "wbw_id", + "ayah_id", + "word_index" + ] + }, + "indices": [ + { + "name": "idx_wbw_words_ayah_wbw", + "unique": false, + "columnNames": [ + "ayah_id", + "wbw_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `idx_wbw_words_ayah_wbw` ON `${TABLE_NAME}` (`ayah_id`, `wbw_id`)" + } + ] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1509d1159d73e7c5d7d0599bf134c6a3')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/api/ApiConfig.kt b/app/src/main/java/com/quranapp/android/api/ApiConfig.kt index 16c6de0a4..e9abc64e9 100644 --- a/app/src/main/java/com/quranapp/android/api/ApiConfig.kt +++ b/app/src/main/java/com/quranapp/android/api/ApiConfig.kt @@ -10,9 +10,9 @@ object ApiConfig { const val GH_PROXY_ROOT = "https://gh-proxy.alfaazplus.com/" const val JS_DELIVR_ROOT = "https://cdn.jsdelivr.net/gh/" const val GH_RAW_ROOT = "https://raw.githubusercontent.com/" - const val GH_PROXY_BASE_URL = "${GH_PROXY_ROOT}AlfaazPlus/QuranApp/master/" + const val GH_PROXY_BASE_URL = "${GH_PROXY_ROOT}AlfaazPlus/QuranApp/wbw/" // fixme const val JS_DELIVR_BASE_URL = "${JS_DELIVR_ROOT}AlfaazPlus/QuranApp@latest/" - const val GH_RAW_BASE_URL = "${GH_RAW_ROOT}AlfaazPlus/QuranApp/master/" + const val GH_RAW_BASE_URL = "${GH_RAW_ROOT}AlfaazPlus/QuranApp/wbw/" // fixme const val GITHUB_REPOSITORY_URL = "https://github.com/AlfaazPlus/QuranApp" const val GITHUB_ISSUES_BUG_REPORT_URL = "https://github.com/AlfaazPlus/QuranApp/issues/new?template=bug_report.yml" diff --git a/app/src/main/java/com/quranapp/android/api/GithubApi.kt b/app/src/main/java/com/quranapp/android/api/GithubApi.kt index 8a8e35934..bc9d0ca12 100644 --- a/app/src/main/java/com/quranapp/android/api/GithubApi.kt +++ b/app/src/main/java/com/quranapp/android/api/GithubApi.kt @@ -3,6 +3,7 @@ package com.quranapp.android.api import com.quranapp.android.api.models.AppUpdate import com.quranapp.android.api.models.AppUrls import com.quranapp.android.api.models.ResourcesVersions +import com.quranapp.android.api.models.wbw.AvailableWbwInfoModel import okhttp3.ResponseBody import retrofit2.Response import retrofit2.http.GET @@ -31,4 +32,7 @@ interface GithubApi { @GET("inventory/recitations/available_recitation_translations_info_v2.json") suspend fun getAvailableRecitationTranslations(): ResponseBody + + @GET("inventory/wbw/available_wbw_info.json") + suspend fun getAvailableWbwInfo(): AvailableWbwInfoModel } diff --git a/app/src/main/java/com/quranapp/android/api/GithubLikeApi.kt b/app/src/main/java/com/quranapp/android/api/GithubLikeApi.kt index 8a03e5169..8caa73f5c 100644 --- a/app/src/main/java/com/quranapp/android/api/GithubLikeApi.kt +++ b/app/src/main/java/com/quranapp/android/api/GithubLikeApi.kt @@ -10,7 +10,7 @@ import retrofit2.http.Streaming interface GithubLikeApi { @GET("{path}") @Streaming - suspend fun getRecitationTimingMetadata( + suspend fun getRawContent( @Path(value = "path", encoded = true) path: String, ): Response } diff --git a/app/src/main/java/com/quranapp/android/api/models/wbw/AvailableWbwInfoModel.kt b/app/src/main/java/com/quranapp/android/api/models/wbw/AvailableWbwInfoModel.kt new file mode 100644 index 000000000..48542e6e8 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/api/models/wbw/AvailableWbwInfoModel.kt @@ -0,0 +1,41 @@ +package com.quranapp.android.api.models.wbw + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AvailableWbwInfoModel( + @SerialName("version") + val version: Int?, + @SerialName("wbw") + val wbw: List, +) + +@Serializable +data class WbwLanguageInfo( + @SerialName("id") + val id: String, + @SerialName("lang_code") + val langCode: String, + @SerialName("lang_name") + val langName: String, + @SerialName("has_translation") + val hasTranslation: Boolean, + @SerialName("has_transliteration") + val hasTransliteration: Boolean, + @SerialName("url") + val url: String, + @SerialName("version") + val version: Int, +) + +@Serializable +data class WbwPayloadModel( + @SerialName("version") + val version: Int, + + // verse id -> ordered words + // each word -> [string, string] -> [translation, transliteration]; + @SerialName("verses") + val verses: Map>>, +) diff --git a/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt b/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt index 7ed2a0fed..a74ac40f2 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt @@ -29,7 +29,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -53,13 +52,12 @@ import com.quranapp.android.components.quran.subcomponents.Translation import com.quranapp.android.components.reader.ChapterVersePair import com.quranapp.android.compose.components.common.IconButton import com.quranapp.android.compose.components.common.Loader -import com.quranapp.android.compose.components.reader.LocalRecitationState +import com.quranapp.android.compose.components.reader.LocalRecitation import com.quranapp.android.compose.components.reader.ReaderProvider import com.quranapp.android.compose.components.settings.DailyReminderSheet import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.compose.utils.preferences.VersePreferences -import com.quranapp.android.db.DatabaseProvider import com.quranapp.android.db.relations.VerseWithDetails import com.quranapp.android.utils.reader.LocalVerseActions import com.quranapp.android.utils.reader.QuranTextStyleParams @@ -229,7 +227,7 @@ private fun VotdContent() { val iconTint = Color.White.alpha(0.7f) - val recState = LocalRecitationState.current + val recState = LocalRecitation.current val isVersePlaying = recState.isAnyPlaying && recState.playingVerse.doesEqual(verse) Box( diff --git a/app/src/main/java/com/quranapp/android/compose/components/common/SwitchItem.kt b/app/src/main/java/com/quranapp/android/compose/components/common/SwitchItem.kt index 18b7257d5..41347e622 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/common/SwitchItem.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/common/SwitchItem.kt @@ -18,12 +18,15 @@ fun SwitchItem( title: Int, subtitle: Int? = null, checked: Boolean, + enabled: Boolean = true, onCheckedChange: (Boolean) -> Unit ) { Row( modifier = modifier .clip(MaterialTheme.shapes.small) - .clickable { onCheckedChange(!checked) } + .clickable(enabled = enabled) { + if (enabled) onCheckedChange(!checked) + } .padding(horizontal = 16.dp, vertical = 10.dp), verticalAlignment = androidx.compose.ui.Alignment.CenterVertically ) { @@ -38,6 +41,7 @@ fun SwitchItem( .height(24.dp), checked = checked, onCheckedChange = onCheckedChange, + enabled = enabled, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt index aed67fdfc..2b10a43f3 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt @@ -195,7 +195,7 @@ fun ReaderLayoutPageMode( readerVm.requestPageNavigation(targetPage) } - val playerState = LocalRecitationState.current + val playerState = LocalRecitation.current val isPlayingMushaf = playerState.isAnyPlaying val playingVerseMushaf = playerState.playingVerse var playerVerseSync by readerVm.playerVerseSync @@ -276,7 +276,7 @@ private fun PageModePage( val scrollState = rememberScrollState() - val playerState = LocalRecitationState.current + val playerState = LocalRecitation.current val isPlaying = playerState.isAnyPlaying val playingVerse = playerState.playingVerse diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/QuranText.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/QuranText.kt deleted file mode 100644 index bf1db053d..000000000 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/QuranText.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.quranapp.android.compose.components.reader - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.unit.dp - -@Composable -fun QuranText( - verseUi: ReaderLayoutItem.VerseUI, -) { - Text( - text = verseUi.parsedQuranText ?: buildAnnotatedString { }, - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/QuranTextWbw.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/QuranTextWbw.kt new file mode 100644 index 000000000..a1d36026e --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/QuranTextWbw.kt @@ -0,0 +1,129 @@ +package com.quranapp.android.compose.components.reader + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp + +@Composable +fun QuranTextWbw(verseUi: ReaderLayoutItem.VerseUI) { + val wbwMap = verseUi.wbwByWordIndex ?: emptyMap() + val textStyles = LocalQuranTextStyle.current + val recitation = LocalRecitation.current + + val arabicStyle = textStyles.quran(verseUi.verse.pageNo) ?: TextStyle.Default + val dividerColor = colorScheme.outlineVariant + + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + FlowRow( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + for (word in verseUi.verse.words) { + if (word.isLastWordOfAyah) { + Text( + text = word.text, + style = arabicStyle, + ) + } else { + val wbw = wbwMap[word.wordIndex] + val isThisWordLoading = recitation.isWbwAudioLoading( + verseUi.verse.chapterNo, + verseUi.verse.verseNo, + word.wordIndex + ) + + Column( + modifier = Modifier + .wrapContentWidth(Alignment.CenterHorizontally) + .clickable { + recitation.playWord( + verseUi.verse.chapterNo, + verseUi.verse.verseNo, + word.wordIndex + ) + }, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + val hasTransliteration = !wbw?.transliteration.isNullOrBlank() + val hasTranslation = !wbw?.translation.isNullOrBlank() + val showDividerUnderArabic = hasTransliteration || hasTranslation + + if (isThisWordLoading) { + LinearProgressIndicator( + modifier = Modifier + .widthIn(min = 28.dp, max = 40.dp) + .height(2.dp), + color = colorScheme.primary, + trackColor = colorScheme.surfaceVariant, + ) + } + + Text( + text = word.text, + style = arabicStyle, + modifier = if (showDividerUnderArabic) { + Modifier.drawBehind { + val stroke = 1.dp.toPx() + val y = size.height + stroke * 0.5f + drawLine( + color = dividerColor, + strokeWidth = stroke, + start = Offset(0f, y), + end = Offset(size.width, y), + ) + } + } else { + Modifier + }, + ) + + Spacer(Modifier.height(3.dp)) + + if (hasTransliteration) { + Text( + text = wbw.transliteration, + style = textStyles.wbwTrltStyle ?: TextStyle.Default, + textAlign = TextAlign.Center, + ) + } + + if (hasTranslation) { + Text( + text = wbw.translation, + style = textStyles.wbwTrStyle ?: TextStyle.Default, + textAlign = TextAlign.Center, + modifier = Modifier.widthIn(max = textStyles.wbwMaxWith), + ) + } + } + } + + } + } + } +} diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt index 1b9328c2d..12eee0eab 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt @@ -22,11 +22,13 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.quranapp.android.compose.components.common.Loader import com.quranapp.android.db.entities.BookmarkKey +import com.quranapp.android.db.entities.wbw.WbwWordEntity import com.quranapp.android.db.relations.VerseWithDetails import com.quranapp.android.utils.reader.MUSHAF_FONT_WIDTH_DP_MAX import com.quranapp.android.viewModels.ReaderUiState @@ -61,13 +63,19 @@ sealed class ReaderLayoutItem() { data class ChapterTitle(val chapterNo: Int, override val key: String) : ReaderLayoutItem() data class VerseUI( val verse: VerseWithDetails, - val parsedQuranText: AnnotatedString? = null, val parsedTranslationTexts: List> = emptyList(), + val wbwByWordIndex: Map? = null, val isLastInGroup: Boolean = false, override val key: String ) : ReaderLayoutItem() } +data class ReaderPreparedData( + val items: List, + /** Quran text style per mushaf page for Arabic in this reader session. */ + val textStyles: Map = emptyMap(), +) + @OptIn(ExperimentalFoundationApi::class) @Composable fun ReaderLayout( @@ -131,7 +139,9 @@ private fun ReaderLayoutVerseMode( onSyncStateChanged: (Boolean) -> Unit, ) { val listState = rememberLazyListState() - val items by readerVm.verseByVerseItems.collectAsStateWithLifecycle() + val prepared by readerVm.verseByVersePrepared.collectAsStateWithLifecycle() + val items = prepared.items + val allBookmarks by readerVm.userRepository.getBookmarksFlow() .collectAsStateWithLifecycle(initialValue = emptyList()) @@ -151,7 +161,7 @@ private fun ReaderLayoutVerseMode( .collect { readerVm.updateLastKnownVerseFromItems(it) } } - LaunchedEffect(items) { + LaunchedEffect(prepared) { if (items.isNotEmpty()) { readerVm.updateLastKnownVerseFromItems(listState.firstVisibleItemIndex) } @@ -166,7 +176,7 @@ private fun ReaderLayoutVerseMode( var autoScrollSpeed by readerVm.autoScrollSpeed var playerVerseSync by readerVm.playerVerseSync - val playerState = LocalRecitationState.current + val playerState = LocalRecitation.current val isPlaying = playerState.isAnyPlaying val playingVerse = playerState.playingVerse @@ -227,28 +237,30 @@ private fun ReaderLayoutVerseMode( } } - LazyColumn( - state = listState, - modifier = Modifier - .fillMaxSize() - .nestedScroll(nestedScrollConnection) - .pointerInput(autoScrollSpeed) { - awaitPointerEventScope { - while (true) { - val event = awaitPointerEvent() - if (event.changes.any { it.pressed }) { - autoScrollSpeed = null + TextStyleProvider(prepared.textStyles) { + LazyColumn( + state = listState, + modifier = Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection) + .pointerInput(autoScrollSpeed) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + if (event.changes.any { it.pressed }) { + autoScrollSpeed = null + } } } - } - }, - contentPadding = PaddingValues(top = 16.dp, bottom = 240.dp) - ) { - items( - items = items, - key = { item -> item.key }, - ) { item -> - TranslationRow(readerVm, item, bookmarkedVerseKeys) + }, + contentPadding = PaddingValues(top = 16.dp, bottom = 240.dp) + ) { + items( + items = items, + key = { item -> item.key }, + ) { item -> + TranslationRow(readerVm, item, bookmarkedVerseKeys) + } } } } @@ -257,7 +269,7 @@ private fun ReaderLayoutVerseMode( private fun TranslationRow( readerVm: ReaderViewModel, item: ReaderLayoutItem, - bookmarkedVerseKeys: Set + bookmarkedVerseKeys: Set, ) { when (item) { is ReaderLayoutItem.Bismillah -> Bismillah() diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderProvider.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderProvider.kt index 340a38722..7e58a6c85 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderProvider.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderProvider.kt @@ -11,14 +11,18 @@ import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import com.quranapp.android.components.reader.ChapterVersePair +import com.quranapp.android.compose.components.reader.dialogs.BookmarkViewerData +import com.quranapp.android.compose.components.reader.dialogs.BookmarkViewerSheet import com.quranapp.android.compose.components.reader.dialogs.FootnotePresenter import com.quranapp.android.compose.components.reader.dialogs.FootnotePresenterData import com.quranapp.android.compose.components.reader.dialogs.QuickReference import com.quranapp.android.compose.components.reader.dialogs.QuickReferenceData import com.quranapp.android.compose.components.reader.dialogs.VerseOptionsSheet -import com.quranapp.android.compose.components.reader.dialogs.BookmarkViewerData -import com.quranapp.android.compose.components.reader.dialogs.BookmarkViewerSheet +import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.relations.VerseWithDetails +import com.quranapp.android.utils.mediaplayer.RecitationController +import com.quranapp.android.utils.mediaplayer.WbwAudioPlayer import com.quranapp.android.utils.reader.LocalVerseActions import com.quranapp.android.utils.reader.VerseActions import com.quranapp.android.utils.reader.factory.ReaderFactory @@ -30,12 +34,25 @@ val LocalReaderViewModel = staticCompositionLocalOf { error("ReaderProviderViewModel not provided") } +data class LocalRecitationStateData( + val controller: RecitationController, + val isAnyPlaying: Boolean, + val playingVerse: ChapterVersePair, + val playWord: (Int, Int, Int) -> Unit, + val isWbwAudioLoading: (Int, Int, Int) -> Boolean, +) + +val LocalRecitation = staticCompositionLocalOf { + error("LocalRecitationState not provided") +} + @Composable fun ReaderProvider( content: @Composable () -> Unit ) { val viewModel = viewModel() + val context = LocalContext.current val coroutineScope = rememberCoroutineScope() val controller = viewModel.controller @@ -47,7 +64,8 @@ fun ReaderProvider( var verseOptionsVerse by remember { mutableStateOf(null) } var quickReferenceData by remember { mutableStateOf(null) } - val context = LocalContext.current + val wbwRecitationEnabled = ReaderPreferences.observeWbwRecitationEnabled() + var wbwWordLoadingKey by remember { mutableStateOf(null) } CompositionLocalProvider( LocalReaderViewModel provides viewModel, @@ -86,10 +104,34 @@ fun ReaderProvider( } ), - LocalRecitationState provides LocalRecitationStateData( + LocalRecitation provides LocalRecitationStateData( controller = controller, isAnyPlaying = isPlaying, playingVerse = recitationState.currentVerse, + playWord = { chapterNo, verseNo, wordIndex -> + if (wbwRecitationEnabled) { + coroutineScope.launch { + val key = "$chapterNo:$verseNo:$wordIndex" + wbwWordLoadingKey = key + + try { + WbwAudioPlayer.play( + context, + chapterNo, + verseNo, + wordIndex, + ) + } finally { + if (wbwWordLoadingKey == key) { + wbwWordLoadingKey = null + } + } + } + } + }, + isWbwAudioLoading = { chapterNo, verseNo, wordIndex -> + wbwWordLoadingKey == "$chapterNo:$verseNo:$wordIndex" + } ) ) { content() diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/TextStyleProvider.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/TextStyleProvider.kt new file mode 100644 index 000000000..e40997f36 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/TextStyleProvider.kt @@ -0,0 +1,70 @@ +package com.quranapp.android.compose.components.reader + +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.compose.utils.preferences.ReaderPreferences + + +data class QuranTextStyle( + val quran: (Int) -> TextStyle?, + val wbwTrltStyle: TextStyle?, + val wbwTrStyle: TextStyle?, + val wbwMaxWith: Dp, +) + +val LocalQuranTextStyle = staticCompositionLocalOf { + error("QuranTextStyle not provided") +} + +@Composable +fun TextStyleProvider( + pageTextStyles: Map, + content: @Composable () -> Unit +) { + val wbwMult = ReaderPreferences.observeWbwTextSizeMultiplier() + + val transliterationBase = typography.bodySmall.copy( + color = colorScheme.onSurface.alpha(0.65f) + ) + val translationBase = typography.bodySmall.copy( + color = colorScheme.onSurface + ) + + val transliterationStyle = remember(translationBase, wbwMult) { + val fs = (transliterationBase.fontSize.value * wbwMult).sp + + transliterationBase.copy( + fontSize = fs, + lineHeight = fs * 1.35f, + ) + } + val translationStyle = remember(translationBase, wbwMult) { + val fs = (translationBase.fontSize.value * wbwMult).sp + + translationBase.copy( + fontSize = fs, + lineHeight = fs * 1.35f, + ) + } + + + CompositionLocalProvider( + LocalQuranTextStyle provides QuranTextStyle( + quran = { pageTextStyles[it] }, + wbwTrltStyle = transliterationStyle, + wbwTrStyle = translationStyle, + wbwMaxWith = (80 * wbwMult / 100).coerceAtLeast(80F).dp + ) + ) { + content() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt index 666ba9c88..4e5acbffa 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt @@ -19,7 +19,6 @@ import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -39,29 +38,18 @@ import com.quranapp.android.compose.components.dialogs.SimpleTooltip import com.quranapp.android.compose.theme.alpha import com.quranapp.android.db.relations.VerseWithDetails import com.quranapp.android.utils.extensions.copyToClipboard -import com.quranapp.android.utils.mediaplayer.RecitationController import com.quranapp.android.utils.reader.LocalVerseActions import com.quranapp.android.utils.reader.factory.ReaderFactory -data class LocalRecitationStateData( - val controller: RecitationController, - val isAnyPlaying: Boolean, - val playingVerse: ChapterVersePair, -) - -val LocalRecitationState = staticCompositionLocalOf { - error("LocalRecitationState not provided") -} - @Composable fun VerseView( verseUi: ReaderLayoutItem.VerseUI, isBookmarked: Boolean, - showDivider: Boolean = false + showDivider: Boolean = false, ) { val verse = verseUi.verse - val recState = LocalRecitationState.current + val recState = LocalRecitation.current val isVersePlaying = recState.isAnyPlaying && recState.playingVerse.doesEqual(verse) Box { @@ -74,7 +62,11 @@ fun VerseView( .padding(horizontal = 12.dp, vertical = 16.dp) ) { VerseActionBar(verse = verse, isVersePlaying, isBookmarked) - QuranText(verseUi = verseUi) + + QuranTextWbw( + verseUi, + ) + TranslationText(verseUi = verseUi) } @@ -96,7 +88,7 @@ private fun VerseActionBar( ) { val context = LocalContext.current val verseActions = LocalVerseActions.current - val recitationState = LocalRecitationState.current + val recitationState = LocalRecitation.current val controller = recitationState.controller val iconTint = colorScheme.onBackground.alpha(0.7f) diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/FootnotePresenter.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/FootnotePresenter.kt index 663cc8650..c27c4e353 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/FootnotePresenter.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/FootnotePresenter.kt @@ -99,8 +99,7 @@ private fun PresentSheetContent(data: FootnotePresenterData) { Column( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(.9f), + .fillMaxWidth(), ) { Header(translFactory, verse, singleFootnote) diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt index c76f5e142..7c20ed88f 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt @@ -43,7 +43,9 @@ import com.quranapp.android.compose.components.dialogs.AlertDialog import com.quranapp.android.compose.components.dialogs.AlertDialogAction import com.quranapp.android.compose.components.dialogs.AlertDialogActionStyle import com.quranapp.android.compose.components.reader.ReaderLayoutItem +import com.quranapp.android.compose.components.reader.ReaderPreparedData import com.quranapp.android.compose.components.reader.ReaderProvider +import com.quranapp.android.compose.components.reader.TextStyleProvider import com.quranapp.android.compose.components.reader.VerseView import com.quranapp.android.compose.extensions.bottomBorder import com.quranapp.android.compose.theme.alpha @@ -207,7 +209,7 @@ private fun QuickReferenceContent( val verseRange = remember(parsed) { parsedVersesToIntRange(parsed) } val title = remember(data.chapterNo, parsed) { formatTitle(data.chapterNo, parsed) } - var items by remember { mutableStateOf>(emptyList()) } + var prepared by remember { mutableStateOf(null) } var isLoading by remember { mutableStateOf(true) } val isBookmarked by if (verseRange != null) { bookmarksRepo @@ -234,7 +236,7 @@ private fun QuickReferenceContent( slugs = ReaderPreferences.getTranslations(), ) - items = ReaderItemsBuilder.buildQuickReferenceItems( + prepared = ReaderItemsBuilder.buildQuickReferenceItems( context, params, repository, data.chapterNo, verseNos ) } @@ -277,20 +279,24 @@ private fun QuickReferenceContent( CircularProgressIndicator() } } else { - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - ) { - itemsIndexed( - items.filterIsInstance(), - key = { _, item -> item.key } - ) { index, verseUi -> - VerseViewWrapped( - bookmarksRepo, - verseUi = verseUi, - showDivier = index < items.size - 1, - ) + val verseRows = prepared?.items.orEmpty().filterIsInstance() + + TextStyleProvider(prepared?.textStyles ?: emptyMap()) { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + ) { + itemsIndexed( + verseRows, + key = { _, item -> item.key } + ) { index, verseUi -> + VerseViewWrapped( + bookmarksRepo, + verseUi = verseUi, + showDivier = index < verseRows.lastIndex, + ) + } } } } diff --git a/app/src/main/java/com/quranapp/android/compose/navigation/SettingRoutes.kt b/app/src/main/java/com/quranapp/android/compose/navigation/SettingRoutes.kt index 18a5ddd6d..4c4429946 100644 --- a/app/src/main/java/com/quranapp/android/compose/navigation/SettingRoutes.kt +++ b/app/src/main/java/com/quranapp/android/compose/navigation/SettingRoutes.kt @@ -26,6 +26,7 @@ object SettingRoutes { const val TRANSLATIONS_DOWNLOAD = "settings.translations_download" const val TAFSIR = "settings.tafsir" const val SCRIPT = "settings.script" + const val WWB = "settings.wbw" const val RECITATION_DOWNLOAD = "settings.recitation_download" const val APP_LOGS = "settings.app_logs" } \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt index f33b46147..fb743bf97 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt @@ -50,6 +50,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -66,13 +67,14 @@ import com.quranapp.android.compose.components.player.RecitationPlayerSheet import com.quranapp.android.compose.components.reader.LocalReaderViewModel import com.quranapp.android.compose.components.reader.ReaderLayoutItem import com.quranapp.android.compose.components.reader.ReaderProvider +import com.quranapp.android.compose.components.reader.TextStyleProvider import com.quranapp.android.compose.components.reader.VerseView import com.quranapp.android.compose.components.reader.dialogs.QuickReferenceVerses import com.quranapp.android.compose.components.reader.dialogs.parseVerses import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences -import com.quranapp.android.repository.QuranRepository import com.quranapp.android.db.entities.BookmarkKey +import com.quranapp.android.repository.QuranRepository import com.quranapp.android.utils.extensions.isSingleValue import com.quranapp.android.utils.reader.LocalVerseActions import com.quranapp.android.utils.reader.ReaderItemsBuilder @@ -87,6 +89,7 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext import java.util.concurrent.ConcurrentHashMap +import kotlin.collections.buildMap private sealed class ReferenceRow { data class Description(val title: String, val desc: String?) : ReferenceRow() @@ -96,7 +99,10 @@ private sealed class ReferenceRow { val titleText: String, ) : ReferenceRow() - data class VerseRow(val verseUi: ReaderLayoutItem.VerseUI) : ReferenceRow() + data class VerseRow( + val verseUi: ReaderLayoutItem.VerseUI, + val quranTextStyle: TextStyle? = null, + ) : ReferenceRow() } @OptIn(ExperimentalMaterial3Api::class) @@ -303,72 +309,84 @@ private fun ReferenceScreenContent( ) } + val referencePageTextStyles = remember(rows) { + buildMap { + for (row in rows) { + if (row is ReferenceRow.VerseRow) { + row.quranTextStyle?.let { put(row.verseUi.verse.pageNo, it) } + } + } + } + } + when { loading -> { Loader(fill = true) } else -> { - LazyColumn( - state = listState, - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(bottom = 64.dp) - ) { - items( - items = rows, - key = { row -> + TextStyleProvider(referencePageTextStyles) { + LazyColumn( + state = listState, + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = 64.dp) + ) { + items( + items = rows, + key = { row -> + when (row) { + is ReferenceRow.Description -> "desc" + is ReferenceRow.SectionTitle -> row.segmentKey + is ReferenceRow.VerseRow -> row.verseUi.key + } + }, + ) { row -> when (row) { - is ReferenceRow.Description -> "desc" - is ReferenceRow.SectionTitle -> row.segmentKey - is ReferenceRow.VerseRow -> row.verseUi.key - } - }, - ) { row -> - when (row) { - is ReferenceRow.Description -> ReferenceDescription( - row.title, - row.desc - ) - - is ReferenceRow.SectionTitle -> ReferenceSectionTitle( - row = row, - isBookmarked = bookmarkedKeys.contains( - BookmarkKey( - row.ref.chapterNo, - row.ref.range.first, - row.ref.range.last, + is ReferenceRow.Description -> ReferenceDescription( + row.title, + row.desc + ) + + is ReferenceRow.SectionTitle -> ReferenceSectionTitle( + row = row, + isBookmarked = bookmarkedKeys.contains( + BookmarkKey( + row.ref.chapterNo, + row.ref.range.first, + row.ref.range.last, + ), ), - ), - onOpenInReader = { chapterNo, range -> - val i = ReaderFactory.prepareVerseRangeIntent( - chapterNo, - range.first, - range.last - ) - .setClass(context, ActivityReader::class.java) - .putExtra( - Keys.READER_KEY_TRANSL_SLUGS, - translationSlugs.toTypedArray() - ) - .putExtra( - Keys.READER_KEY_SAVE_TRANSL_CHANGES, - false + onOpenInReader = { chapterNo, range -> + val i = ReaderFactory.prepareVerseRangeIntent( + chapterNo, + range.first, + range.last ) + .setClass(context, ActivityReader::class.java) + .putExtra( + Keys.READER_KEY_TRANSL_SLUGS, + translationSlugs.toTypedArray() + ) + .putExtra( + Keys.READER_KEY_SAVE_TRANSL_CHANGES, + false + ) + + context.startActivity(i) + }, + ) - context.startActivity(i) - }, - ) - - is ReferenceRow.VerseRow -> ReferenceVerseViewWrapped( - verseUi = row.verseUi, - isBookmarked = bookmarkedKeys.contains( - BookmarkKey( - row.verseUi.verse.chapterNo, - row.verseUi.verse.verseNo, - row.verseUi.verse.verseNo, + is ReferenceRow.VerseRow -> ReferenceVerseViewWrapped( + verseUi = row.verseUi, + isBookmarked = bookmarkedKeys.contains( + BookmarkKey( + row.verseUi.verse.chapterNo, + row.verseUi.verse.verseNo, + row.verseUi.verse.verseNo, + ), ), - ), - ) + ) + } } } } @@ -572,19 +590,19 @@ private suspend fun buildReferenceRows( val built = segments.map { seg -> async { val verseNos = seg.ref.range.toList() - val items = ReaderItemsBuilder.buildQuickReferenceItems( + val prepared = ReaderItemsBuilder.buildQuickReferenceItems( context, params, repository, seg.chapterNo, verseNos, - ) - val verseUis = items.filterIsInstance() - seg to verseUis + ) ?: return@async Triple(seg, emptyList(), emptyMap()) + val verseUis = prepared.items.filterIsInstance() + Triple(seg, verseUis, prepared.textStyles) } }.awaitAll() - for ((seg, verseUis) in built) { + for ((seg, verseUis, textStyles) in built) { val titleText = if (seg.ref.range.isSingleValue) { "${seg.chapterName} ${seg.chapterNo}:${seg.ref.range.first}" } else { @@ -602,10 +620,11 @@ private suspend fun buildReferenceRows( for ((i, v) in verseUis.withIndex()) { out.add( ReferenceRow.VerseRow( - v.copy( + verseUi = v.copy( key = "ref-${seg.segmentIndex}-${v.key}", isLastInGroup = i == verseUis.lastIndex, ), + quranTextStyle = textStyles[v.verse.pageNo], ), ) } diff --git a/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsMainScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsMainScreen.kt index 8631ef4a5..34fa9430e 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsMainScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsMainScreen.kt @@ -180,6 +180,13 @@ fun SettingsMainScreen( navController.navigate(SettingRoutes.SCRIPT) } + SettingsItem( + title = R.string.wordByWord, + icon = R.drawable.dr_icon_quran_script, + ) { + navController.navigate(SettingRoutes.WWB) + } + SettingsItem( title = R.string.downloadRecitations, icon = R.drawable.dr_icon_download, diff --git a/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsScreen.kt index a22ca22f1..ff1b54f3e 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsScreen.kt @@ -98,6 +98,7 @@ fun SettingsScreen(intent: Intent?, isNewIntent: Boolean) { route(SettingRoutes.TRANSLATIONS_DOWNLOAD) { TranslationDownloadScreen() } route(SettingRoutes.TAFSIR) { TafsirSelectionScreen() } route(SettingRoutes.SCRIPT) { ScriptsScreen() } + route(SettingRoutes.WWB) { SettingsWbwScreen() } route(SettingRoutes.RECITATION_DOWNLOAD) { RecitationDownloadScreen() } route(SettingRoutes.APP_LOGS) { AppLogsScreen() diff --git a/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsWbwScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsWbwScreen.kt new file mode 100644 index 000000000..07484eb61 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsWbwScreen.kt @@ -0,0 +1,412 @@ +package com.quranapp.android.compose.screens.settings + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import com.quranapp.android.R +import com.quranapp.android.compose.components.common.AlertCard +import com.quranapp.android.compose.components.common.AppBar +import com.quranapp.android.compose.components.common.ErrorMessageCard +import com.quranapp.android.compose.components.common.SwitchItem +import com.quranapp.android.compose.components.dialogs.AlertDialog +import com.quranapp.android.compose.components.dialogs.AlertDialogAction +import com.quranapp.android.compose.components.dialogs.AlertDialogActionStyle +import com.quranapp.android.compose.components.settings.ListItemCategoryLabel +import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.utils.managers.ResourceDownloadStatus +import com.quranapp.android.utils.reader.ReaderTextSizeUtils +import com.quranapp.android.viewModels.WbwSettingsUiState +import com.quranapp.android.viewModels.WbwSettingsViewModel +import com.quranapp.android.viewModels.WbwUiModel +import kotlinx.coroutines.launch +import com.quranapp.android.compose.components.common.IconButton as AppIconButton + +@Composable +fun SettingsWbwScreen() { + val viewModel = viewModel() + val uiState = viewModel.uiState.collectAsStateWithLifecycle().value + + Scaffold( + topBar = { + AppBar( + title = stringResource(R.string.wordByWord), + actions = { + AppIconButton( + painterResource(R.drawable.dr_icon_refresh) + ) { + viewModel.load(true) + } + } + ) + } + ) { padding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(padding) + ) { + when { + uiState.isLoading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + uiState.error != null -> ErrorMessageCard( + error = uiState.error, + onRetry = { + viewModel.load(true) + }, + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) + + else -> { + WbwRows( + viewModel, + uiState, + ) + } + } + } + } +} + +@Composable +private fun WbwRows(viewModel: WbwSettingsViewModel, uiState: WbwSettingsUiState) { + var deleteDialogData by remember { mutableStateOf(null) } + val downloadStates = uiState.downloadStates + val rows = uiState.rows + + val isAnyDownloading = downloadStates.values.any { + it is ResourceDownloadStatus.Started || it is ResourceDownloadStatus.InProgress + } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(top = 16.dp, bottom = 64.dp), + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + item { + Configurations() + } + items(rows, key = { it.info.id }) { row -> + val status = downloadStates[row.info.id] ?: ResourceDownloadStatus.Idle + WbwRow( + row, + status, + viewModel, + uiState, + isAnyDownloading, + onDeleteRequest = { + deleteDialogData = row + } + ) + } + } + + AlertDialog( + isOpen = deleteDialogData != null, + onClose = { deleteDialogData = null }, + title = stringResource(R.string.deleteData), + actions = listOf( + AlertDialogAction( + text = stringResource(R.string.strLabelCancel) + ), + AlertDialogAction( + text = stringResource(R.string.strLabelDelete), + style = AlertDialogActionStyle.Danger, + dismissOnClick = false, + onClick = { + if (deleteDialogData != null) { + viewModel.deleteWbwData(deleteDialogData!!.info.id) + } + + deleteDialogData = null + } + ) + ) + ) { + Text(deleteDialogData?.info?.langName ?: "") + } +} + +@Composable +private fun WbwRow( + row: WbwUiModel, + downloadStatus: ResourceDownloadStatus, + viewModel: WbwSettingsViewModel, + uiState: WbwSettingsUiState, + isAnyDownloading: Boolean, + onDeleteRequest: () -> Unit +) { + val isDownloaded = row.isDownloaded + val isSelected = row.info.id == uiState.selectedWbwId + + val canSelect = isDownloaded + val isDownloading = downloadStatus is ResourceDownloadStatus.Started || + downloadStatus is ResourceDownloadStatus.InProgress + + val onSelect = { + viewModel.selectLanguage(row.info.id) + } + + val onDownloadOrUpdate = { + viewModel.startDownload(row.info.id) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + if (canSelect) onSelect() + } + .padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = isSelected, + onClick = { + if (canSelect) onSelect() + }, + enabled = canSelect, + colors = RadioButtonDefaults.colors( + selectedColor = MaterialTheme.colorScheme.primary + ) + ) + + Column( + modifier = Modifier + .weight(1f) + .padding(horizontal = 8.dp) + ) { + Text( + text = row.info.langName, + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + Spacer(modifier = Modifier.height(2.dp)) + + val subtitle = when { + downloadStatus is ResourceDownloadStatus.InProgress -> "${stringResource(R.string.textDownloading)} ${downloadStatus.progress}%" + downloadStatus is ResourceDownloadStatus.Started -> stringResource(R.string.textDownloading) + row.isUpdateAvailable -> stringResource(R.string.strLabelUpdate) + isDownloaded -> stringResource(R.string.strLabelDownloaded) + else -> null + } + + if (subtitle != null) { + Text( + text = subtitle, + style = MaterialTheme.typography.bodySmall, + color = if (downloadStatus is ResourceDownloadStatus.Failed) + MaterialTheme.colorScheme.error + else + MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + + when (downloadStatus) { + is ResourceDownloadStatus.InProgress -> { + Box( + modifier = Modifier.size(40.dp), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + progress = { downloadStatus.progress / 100f }, + modifier = Modifier.size(22.dp), + strokeWidth = 2.dp + ) + IconButton( + onClick = { + viewModel.cancelDownload(row.info.id) + }, + modifier = Modifier.size(36.dp) + ) { + Icon( + painter = painterResource(R.drawable.dr_icon_close), + contentDescription = stringResource(R.string.strLabelCancel), + modifier = Modifier.size(12.dp) + ) + } + } + } + + is ResourceDownloadStatus.Started -> { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + strokeWidth = 2.dp + ) + } + + is ResourceDownloadStatus.Failed -> { + AppIconButton( + painterResource(R.drawable.dr_icon_refresh), + onClick = onDownloadOrUpdate + ) + } + + else -> { + if (row.isUpdateAvailable || !isDownloaded) { + val icon = if (row.isUpdateAvailable) R.drawable.dr_icon_refresh + else R.drawable.dr_icon_download + + AppIconButton( + painter = painterResource(icon), + enabled = !isAnyDownloading || isDownloading, + onClick = onDownloadOrUpdate, + ) + } else { + AppIconButton( + painter = painterResource(R.drawable.dr_icon_delete), + enabled = !isAnyDownloading, + tint = colorScheme.error, + onClick = onDeleteRequest + ) + } + } + } + } +} + +@Composable +private fun Configurations() { + val coroutineScope = rememberCoroutineScope() + val showTranslation = ReaderPreferences.observeWbwShowTranslation() + val showTransliteration = ReaderPreferences.observeWbwShowTransliteration() + val recitation = ReaderPreferences.observeWbwRecitationEnabled() + val wbwTextMult = ReaderPreferences.observeWbwTextSizeMultiplier() + + val min = 100 + val max = 160 + val steps = max - min + val wbwProgress = wbwTextMult * 100 + + Column(modifier = Modifier.fillMaxWidth()) { + SwitchItem( + title = R.string.wbwShowTranslation, + checked = showTranslation, + onCheckedChange = { checked -> + coroutineScope.launch { + ReaderPreferences.setWbwShowTranslation(checked) + } + }, + ) + + SwitchItem( + title = R.string.wbwShowTransliteration, + subtitle = R.string.wbwShowTransliterationMgs, + checked = showTransliteration, + onCheckedChange = { checked -> + coroutineScope.launch { + ReaderPreferences.setWbwShowTransliteration(checked) + } + }, + ) + + SwitchItem( + title = R.string.wbwRecitation, + subtitle = R.string.wbwRecitationMsg, + checked = recitation, + onCheckedChange = { checked -> + coroutineScope.launch { + ReaderPreferences.setWbwRecitationEnabled(checked) + } + }, + ) + + Spacer(Modifier.height(12.dp)) + + ListItemCategoryLabel(stringResource(R.string.wbwTextSize)) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Slider( + modifier = Modifier.weight(1f), + value = wbwProgress, + onValueChange = { v -> + coroutineScope.launch { + ReaderPreferences.setWbwTextSizeMultiplier( + ReaderTextSizeUtils.calculateMultiplier(v.toInt(), min, max), + ) + } + }, + valueRange = min.toFloat()..max.toFloat(), + steps = steps, + ) + Text( + text = "${wbwProgress.toInt()}%", + modifier = Modifier.padding(start = 10.dp), + style = MaterialTheme.typography.labelSmall, + ) + } + + HorizontalDivider(Modifier.padding(top = 24.dp, bottom = 12.dp)) + + ListItemCategoryLabel(stringResource(R.string.selectWbwLanguage)) + + Spacer(Modifier.height(8.dp)) + + AlertCard( + Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp) + ) { + Text( + stringResource(R.string.noWbwAvailable), + style = typography.bodyMedium + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt b/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt index 83fcd2aef..92ba2a091 100644 --- a/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt +++ b/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt @@ -6,6 +6,7 @@ import android.content.Context import androidx.compose.runtime.Composable import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.floatPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringSetPreferencesKey import com.alfaazplus.sunnah.ui.utils.shared_preference.DataStoreManager @@ -74,6 +75,27 @@ object ReaderPreferences { val KEY_TAFSIR = PrefKey(stringPreferencesKey(TafsirUtils.KEY_TAFSIR), "") + val KEY_WBW = + PrefKey(stringPreferencesKey("key.wbw"), "") + + val KEY_WBW_CONTENT_EPOCH = + PrefKey(longPreferencesKey("reader.wbw.content_epoch"), 0L) + + val KEY_WBW_SHOW_TRANSLATION = + PrefKey(booleanPreferencesKey("reader.wbw.show_translation"), false) + + val KEY_WBW_SHOW_TRANSLITERATION = + PrefKey(booleanPreferencesKey("reader.wbw.show_transliteration"), false) + + val KEY_WBW_RECITATION = + PrefKey(booleanPreferencesKey("reader.wbw.recitation"), true) + + val KEY_TEXT_SIZE_MULT_WBW = + PrefKey( + floatPreferencesKey(ReaderTextSizeUtils.KEY_TEXT_SIZE_MULT_WBW), + ReaderTextSizeUtils.TEXT_SIZE_MULT_WBW_DEFAULT + ) + val KEY_LEGACY_MIGRATED = PrefKey(booleanPreferencesKey("reader.prefs.legacy_migrated_v1"), false) @@ -320,4 +342,117 @@ object ReaderPreferences { val raw = DataStoreManager.observe(KEY_TAFSIR) return raw.ifEmpty { null } } + + fun getWbwId(): String? { + return DataStoreManager.read(KEY_WBW).takeIf { it.isNotEmpty() } + } + + suspend fun setWbwId(id: String) { + DataStoreManager.write(KEY_WBW, id) + } + + fun wbwIdFlow(): Flow { + return DataStoreManager.flow(KEY_WBW) + } + + @Composable + fun observeWbwId(): String { + return DataStoreManager.observe(KEY_WBW) + } + + fun getWbwContentEpoch(): Long { + return DataStoreManager.read(KEY_WBW_CONTENT_EPOCH) + } + + suspend fun bumpWbwContentEpoch() { + val next = DataStoreManager.read(KEY_WBW_CONTENT_EPOCH) + 1L + DataStoreManager.write(KEY_WBW_CONTENT_EPOCH, next) + } + + fun getWbwShowTranslation(): Boolean { + return DataStoreManager.read(KEY_WBW_SHOW_TRANSLATION) + } + + suspend fun setWbwShowTranslation(show: Boolean) { + DataStoreManager.write(KEY_WBW_SHOW_TRANSLATION, show) + } + + @Composable + fun observeWbwShowTranslation(): Boolean { + return DataStoreManager.observe(KEY_WBW_SHOW_TRANSLATION) + } + + fun wbwShowTranslationFlow(): Flow { + return DataStoreManager.flow(KEY_WBW_SHOW_TRANSLATION) + } + + fun getWbwShowTransliteration(): Boolean { + return DataStoreManager.read(KEY_WBW_SHOW_TRANSLITERATION) + } + + suspend fun setWbwShowTransliteration(show: Boolean) { + DataStoreManager.write(KEY_WBW_SHOW_TRANSLITERATION, show) + } + + @Composable + fun observeWbwShowTransliteration(): Boolean { + return DataStoreManager.observe(KEY_WBW_SHOW_TRANSLITERATION) + } + + fun wbwShowTransliterationFlow(): Flow { + return DataStoreManager.flow(KEY_WBW_SHOW_TRANSLITERATION) + } + + fun getWbwRecitationEnabled(): Boolean { + return DataStoreManager.read(KEY_WBW_RECITATION) + } + + suspend fun setWbwRecitationEnabled(enabled: Boolean) { + DataStoreManager.write(KEY_WBW_RECITATION, enabled) + } + + @Composable + fun observeWbwRecitationEnabled(): Boolean { + return DataStoreManager.observe(KEY_WBW_RECITATION) + } + + fun wbwRecitationEnabledFlow(): Flow { + return DataStoreManager.flow(KEY_WBW_RECITATION) + } + + fun getWbwTextSizeMultiplier(): Float { + return DataStoreManager.read(KEY_TEXT_SIZE_MULT_WBW) + } + + suspend fun setWbwTextSizeMultiplier(sizeMult: Float) { + DataStoreManager.write(KEY_TEXT_SIZE_MULT_WBW, sizeMult) + } + + @Composable + fun observeWbwTextSizeMultiplier(): Float { + return DataStoreManager.observe(KEY_TEXT_SIZE_MULT_WBW) + } + + fun wbwTextSizeMultiplierFlow(): Flow { + return DataStoreManager.flow(KEY_TEXT_SIZE_MULT_WBW) + } + + fun validateWbwId( + id: String?, + availableIds: Set, + fallback: String? = null, + ): String? { + val normalizedId = id?.takeIf { it.isNotBlank() } + val normalizedFallback = fallback?.takeIf { it.isNotBlank() } + + if (normalizedId != null && availableIds.contains(normalizedId)) { + return normalizedId + } + + if (normalizedFallback != null && availableIds.contains(normalizedFallback)) { + return normalizedFallback + } + + return null + } } diff --git a/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt b/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt index 69f393846..c501fa57c 100644 --- a/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt +++ b/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt @@ -50,7 +50,7 @@ object DatabaseProvider { .build() } - private fun getExternalQuranDatabase(context: Context): ExternalQuranDatabase { + fun getExternalQuranDatabase(context: Context): ExternalQuranDatabase { return Room.databaseBuilder( context.applicationContext, ExternalQuranDatabase::class.java, diff --git a/app/src/main/java/com/quranapp/android/db/ExternalQuranDatabase.kt b/app/src/main/java/com/quranapp/android/db/ExternalQuranDatabase.kt index 366253c7a..e76d28e1e 100644 --- a/app/src/main/java/com/quranapp/android/db/ExternalQuranDatabase.kt +++ b/app/src/main/java/com/quranapp/android/db/ExternalQuranDatabase.kt @@ -5,12 +5,14 @@ import androidx.room.RoomDatabase import androidx.room.TypeConverters import com.quranapp.android.db.converters.QuranConverters import com.quranapp.android.db.dao.WbwDao +import com.quranapp.android.db.entities.wbw.WbwWordEntity @Database( entities = [ + WbwWordEntity::class, ], version = 1, - exportSchema = false + exportSchema = true ) @TypeConverters(QuranConverters::class) abstract class ExternalQuranDatabase : RoomDatabase() { diff --git a/app/src/main/java/com/quranapp/android/db/dao/WbwDao.kt b/app/src/main/java/com/quranapp/android/db/dao/WbwDao.kt index 57e293559..a1c9cc10c 100644 --- a/app/src/main/java/com/quranapp/android/db/dao/WbwDao.kt +++ b/app/src/main/java/com/quranapp/android/db/dao/WbwDao.kt @@ -1,13 +1,59 @@ package com.quranapp.android.db.dao import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction -import com.quranapp.android.db.entities.quran.SurahEntity -import com.quranapp.android.db.entities.quran.SurahLocalizationEntity -import com.quranapp.android.db.relations.SurahWithLocalizations -import kotlinx.coroutines.flow.Flow +import com.quranapp.android.db.entities.wbw.WbwWordEntity @Dao interface WbwDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun upsertWords(words: List) + + @Query("DELETE FROM wbw_words WHERE wbw_id = :wbwId") + suspend fun deleteByWbwId(wbwId: String) + + @Transaction + suspend fun replaceByWbwId(wbwId: String, words: List) { + deleteByWbwId(wbwId) + + if (words.isNotEmpty()) { + upsertWords(words) + } + } + + @Query( + """ + SELECT * FROM wbw_words + WHERE ayah_id = :ayahId AND wbw_id = :wbwId + ORDER BY word_index ASC + """ + ) + suspend fun getWordsForAyah( + ayahId: Int, + wbwId: String + ): List + + @Query( + """ + SELECT * FROM wbw_words + WHERE wbw_id = :wbwId AND ayah_id IN (:ayahIds) + ORDER BY ayah_id ASC, word_index ASC + """ + ) + suspend fun getWordsForAyahs( + wbwId: String, + ayahIds: List, + ): List + + @Query( + """ + SELECT DISTINCT wbw_id + FROM wbw_words + WHERE wbw_id IN (:wbwIds) + """ + ) + suspend fun getDownloadedWbwIds(wbwIds: List): List } \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/db/entities/wbw/WbwWordEntity.kt b/app/src/main/java/com/quranapp/android/db/entities/wbw/WbwWordEntity.kt new file mode 100644 index 000000000..cebeeb5a1 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/entities/wbw/WbwWordEntity.kt @@ -0,0 +1,25 @@ +package com.quranapp.android.db.entities.wbw + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index + +@Entity( + tableName = "wbw_words", + primaryKeys = ["wbw_id", "ayah_id", "word_index"], + indices = [ + Index(value = ["ayah_id", "wbw_id"], name = "idx_wbw_words_ayah_wbw") + ] +) +data class WbwWordEntity( + @ColumnInfo(name = "ayah_id") + val ayahId: Int, + @ColumnInfo(name = "word_index") + val wordIndex: Int, + @ColumnInfo(name = "wbw_id") + val wbwId: String, + @ColumnInfo(name = "translation") + val translation: String?, + @ColumnInfo(name = "transliteration") + val transliteration: String?, +) diff --git a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt index b0e43ed3b..d8d2fd51b 100644 --- a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt +++ b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt @@ -11,6 +11,7 @@ import com.quranapp.android.db.entities.quran.MushafLineType import com.quranapp.android.db.entities.quran.MushafMapEntity import com.quranapp.android.db.entities.quran.NavigationType import com.quranapp.android.db.entities.quran.SurahEntity +import com.quranapp.android.db.entities.wbw.WbwWordEntity import com.quranapp.android.db.relations.NavigationUnit import com.quranapp.android.db.relations.NavigationUnitRange import com.quranapp.android.db.relations.SurahWithLocalizations @@ -31,6 +32,7 @@ class QuranRepository( private val surahDao get() = database.surahDao() private val surahSearchDao get() = database.surahSearchDao() private val navigationDao get() = database.navigationDao() + private val wbwDao get() = extDatabase.wbwDao() suspend fun getNumberOfPages(mushafId: Int): Int { if (mushafId <= 0) return 0 @@ -238,11 +240,25 @@ class QuranRepository( } } - /** - * Resolves [MushafMapEntity] ayah line to ordered word texts for [scriptCode] - * (must match [com.quranapp.android.db.entities.quran.ScriptEntity.code]). - * When [wordCache] is provided (full ayah word lists), avoids DB reads. - */ + suspend fun getWbwWordsForAyahs( + wbwId: String, + ayahIds: List, + ): Map> { + if (wbwId.isBlank() || ayahIds.isEmpty()) return emptyMap() + + val rows = wbwDao.getWordsForAyahs(wbwId, ayahIds.distinct()) + + if (rows.isEmpty()) return emptyMap() + + val byAyah = LinkedHashMap>() + + for (row in rows) { + byAyah.getOrPut(row.ayahId) { LinkedHashMap() }[row.wordIndex] = row + } + + return byAyah.mapValues { it.value.toMap() } + } + suspend fun resolveMushafLineWords( row: MushafMapEntity, scriptCode: String, diff --git a/app/src/main/java/com/quranapp/android/utils/managers/WbwDownloadManager.kt b/app/src/main/java/com/quranapp/android/utils/managers/WbwDownloadManager.kt new file mode 100644 index 000000000..29404422a --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/managers/WbwDownloadManager.kt @@ -0,0 +1,130 @@ +package com.quranapp.android.utils.managers + +import WbwDownloadWorker +import android.content.Context +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.workDataOf +import com.quranapp.android.api.models.wbw.WbwLanguageInfo +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.util.UUID + +object WbwDownloadManager { + private const val TAG = "download_wbw" + private const val ITEM_TAG_PREFIX = "DownloadWbw:" + + private val downloadStates = MutableLiveData>(emptyMap()) + + fun initialize(context: Context) { + val wm = WorkManager.getInstance(context) + wm.getWorkInfosByTagLiveData(TAG).observeForever { workInfos -> + val map = mutableMapOf() + for (info in workInfos) { + if (info.state.isFinished) continue + val idTag = info.tags.firstOrNull { it.startsWith(ITEM_TAG_PREFIX) } ?: continue + val id = idTag.substringAfter(ITEM_TAG_PREFIX) + map[id] = info + } + downloadStates.postValue(map) + } + } + + fun startDownload(context: Context, info: WbwLanguageInfo) { + val data = workDataOf("wbwInfo" to Json.encodeToString(info)) + val itemTag = "${ITEM_TAG_PREFIX}${info.id}" + + val request = OneTimeWorkRequestBuilder() + .setInputData(data) + .addTag(TAG) + .addTag(itemTag) + .build() + + val wm = WorkManager.getInstance(context) + wm.enqueueUniqueWork( + itemTag, + ExistingWorkPolicy.REPLACE, + request + ) + + observeWork(context, info.id, request.id) + } + + fun stopDownload(context: Context, id: String) { + WorkManager.getInstance(context).cancelUniqueWork("${ITEM_TAG_PREFIX}$id") + } + + private fun observeWork(context: Context, id: String, workId: UUID) { + val wm = WorkManager.getInstance(context) + val liveData = wm.getWorkInfoByIdLiveData(workId) + val observer = object : Observer { + override fun onChanged(value: WorkInfo?) { + if (value != null) { + updateState(id, value) + if (value.state.isFinished) { + liveData.removeObserver(this) + } + } + } + } + liveData.observeForever(observer) + } + + private fun updateState(id: String, info: WorkInfo) { + val current = downloadStates.value?.toMutableMap() ?: mutableMapOf() + current[id] = info + downloadStates.postValue(current) + } + + private fun removeState(id: String) { + val current = downloadStates.value?.toMutableMap() ?: mutableMapOf() + current.remove(id) + downloadStates.postValue(current) + } + + fun observeDownloadsAsFlow(): Flow> = callbackFlow { + val observer = Observer> { map -> + for ((id, workInfo) in map) { + val status = when (workInfo.state) { + WorkInfo.State.ENQUEUED -> ResourceDownloadStatus.Started + WorkInfo.State.RUNNING -> { + val progress = workInfo.progress.getInt("progress", 0) + ResourceDownloadStatus.InProgress(progress) + } + + WorkInfo.State.SUCCEEDED -> { + removeState(id) + ResourceDownloadStatus.Completed + } + + WorkInfo.State.FAILED -> { + val error = workInfo.outputData.getString("error") + removeState(id) + ResourceDownloadStatus.Failed(error) + } + + WorkInfo.State.CANCELLED -> { + removeState(id) + ResourceDownloadStatus.Cancelled + } + + else -> null + } + + if (status != null) { + trySend(id to status) + } + } + } + + downloadStates.observeForever(observer) + awaitClose { downloadStates.removeObserver(observer) } + } +} diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt index 958e19526..ec1197b6f 100644 --- a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt @@ -491,7 +491,7 @@ class RecitationAudioRepository(private val context: Context) { timingUrl: String, ) = withContext(Dispatchers.IO) { val response = if (timingUrl.startsWith("ghraw://")) { - RetrofitInstance.githubLike.getRecitationTimingMetadata( + RetrofitInstance.githubLike.getRawContent( timingUrl.removePrefix("ghraw://").trimStart('/') ) } else { diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/WbwAudioPlayer.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/WbwAudioPlayer.kt new file mode 100644 index 000000000..631225eb1 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/WbwAudioPlayer.kt @@ -0,0 +1,110 @@ +package com.quranapp.android.utils.mediaplayer + +import android.content.Context +import android.net.Uri +import androidx.media3.common.AudioAttributes +import androidx.media3.common.C +import androidx.media3.common.MediaItem +import androidx.media3.common.Player +import androidx.media3.common.PlaybackException +import androidx.media3.exoplayer.ExoPlayer +import com.quranapp.android.api.RetrofitInstance +import com.quranapp.android.utils.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import java.io.File +import java.io.IOException + +object WbwAudioPlayer { + + private const val BASE = "https://audio.qurancdn.com/wbw/" + private const val CACHE_SUBDIR = "wbw_audio" + + private val mutex = Mutex() + private var player: ExoPlayer? = null + + private fun segment(n: Int): String = String.format("%03d", n) + + private fun buildUrl(chapterNo: Int, verseNo: Int, urlWordIndex: Int): String = + "$BASE${segment(chapterNo)}_${segment(verseNo)}_${segment(urlWordIndex)}.mp3" + + private fun cacheFile(context: Context, chapterNo: Int, verseNo: Int, urlWordIndex: Int): File { + val dir = File(context.cacheDir, CACHE_SUBDIR).apply { mkdirs() } + return File(dir, "${segment(chapterNo)}_${segment(verseNo)}_${segment(urlWordIndex)}.mp3") + } + + private suspend fun downloadIfMissing(file: File, url: String) { + if (file.exists() && file.length() > 0L) return + val dir = file.parentFile ?: return + dir.mkdirs() + val tmp = File(dir, "${file.name}.tmp") + withContext(Dispatchers.IO) { + val response = RetrofitInstance.any.downloadStreaming(url) + if (!response.isSuccessful) { + throw IOException("Wbw audio failed: HTTP ${response.code()}") + } + val body = response.body() ?: throw IOException("Wbw audio body is null") + body.byteStream().use { input -> + tmp.outputStream().buffered().use { output -> + input.copyTo(output) + } + } + } + if (!tmp.renameTo(file)) { + tmp.delete() + throw IOException("Wbw audio could not finalize cache file") + } + } + + private fun getOrCreatePlayer(context: Context): ExoPlayer { + player?.let { return it } + val app = context.applicationContext + + return ExoPlayer.Builder(app).build().apply { + setAudioAttributes( + AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.AUDIO_CONTENT_TYPE_SPEECH) + .build(), + true, + ) + repeatMode = Player.REPEAT_MODE_OFF + addListener( + object : Player.Listener { + override fun onPlayerError(error: PlaybackException) { + Log.saveError(error, "WbwWordAudioPlayer") + } + }, + ) + }.also { player = it } + } + + suspend fun play( + context: Context, + chapterNo: Int, + verseNo: Int, + appWordIndex: Int, + ) { + val urlWordIndex = appWordIndex + 1 + val file = cacheFile(context.applicationContext, chapterNo, verseNo, urlWordIndex) + val url = buildUrl(chapterNo, verseNo, urlWordIndex) + + mutex.withLock { + try { + downloadIfMissing(file, url) + } catch (e: Exception) { + Log.saveError(e, "WbwWordAudioPlayer.download") + return + } + + val p = getOrCreatePlayer(context) + p.stop() + p.clearMediaItems() + p.setMediaItem(MediaItem.fromUri(Uri.fromFile(file))) + p.prepare() + p.playWhenReady = true + } + } +} diff --git a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt index fd8096381..84edbb71e 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt @@ -3,7 +3,6 @@ package com.quranapp.android.utils.reader import android.content.Context import androidx.compose.ui.text.ParagraphStyle import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle @@ -11,13 +10,14 @@ import com.alfaazplus.sunnah.ui.theme.fontUrdu import com.quranapp.android.compose.components.reader.QuranPageItem import com.quranapp.android.compose.components.reader.QuranPageLineItem import com.quranapp.android.compose.components.reader.ReaderLayoutItem +import com.quranapp.android.compose.components.reader.ReaderPreparedData import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences -import com.quranapp.android.repository.QuranRepository import com.quranapp.android.db.entities.quran.AyahWordEntity import com.quranapp.android.db.entities.quran.MushafLineType import com.quranapp.android.db.entities.quran.MushafMapEntity import com.quranapp.android.db.relations.VerseWithDetails +import com.quranapp.android.repository.QuranRepository import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.reader.factory.QuranTranslationFactory @@ -27,15 +27,18 @@ object ReaderItemsBuilder { params: TextBuilderParams, quranRepository: QuranRepository, chapterNo: Int, - ): List { - if (!QuranMeta.isChapterValid(chapterNo)) return emptyList() + ): ReaderPreparedData? { + if (!QuranMeta.isChapterValid(chapterNo)) { + return null + } val verseCount = quranRepository.getChapterVerseCount(chapterNo) - if (verseCount <= 0) return emptyList() + if (verseCount <= 0) return null val translationFactory = QuranTranslationFactory(context) val out = ArrayList() + val textStyles = HashMap() out.add(ReaderLayoutItem.ChapterInfo(chapterNo, key = "chapterInfo-$chapterNo")) @@ -47,6 +50,7 @@ object ReaderItemsBuilder { buildVerses( params, out, + textStyles, it, quranRepository, chapterNo, @@ -55,7 +59,7 @@ object ReaderItemsBuilder { ) } - return out + return ReaderPreparedData(out, textStyles) } suspend fun buildJuzVersesForTranslationMode( @@ -63,8 +67,10 @@ object ReaderItemsBuilder { params: TextBuilderParams, quranRepository: QuranRepository, juzNo: Int - ): List { - if (!QuranMeta.isJuzValid(juzNo)) return emptyList() + ): ReaderPreparedData? { + if (!QuranMeta.isJuzValid(juzNo)) { + return null + } return buildGroupedVerses( context, params, quranRepository, @@ -77,8 +83,11 @@ object ReaderItemsBuilder { params: TextBuilderParams, quranRepository: QuranRepository, hizbNo: Int - ): List { - if (!QuranMeta.isHizbValid(hizbNo)) return emptyList() + ): ReaderPreparedData? { + if (!QuranMeta.isHizbValid(hizbNo)) { + return null + } + return buildGroupedVerses( context, params, quranRepository, quranRepository.getChapterVerseRangesInHizb(hizbNo) @@ -90,11 +99,12 @@ object ReaderItemsBuilder { params: TextBuilderParams, quranRepository: QuranRepository, chapterRanges: List>, - ): List { - if (chapterRanges.isEmpty()) return emptyList() + ): ReaderPreparedData? { + if (chapterRanges.isEmpty()) return null val translationFactory = QuranTranslationFactory(context) val out = ArrayList() + val textStyles = HashMap() translationFactory.use { for ((chapterNo, verseRange) in chapterRanges) { @@ -112,24 +122,29 @@ object ReaderItemsBuilder { } buildVerses( - params, out, it, quranRepository, + params, out, textStyles, it, quranRepository, chapterNo, verseRange.first, verseRange.last, ) } } - return out + return ReaderPreparedData(out, textStyles) } private suspend fun buildVerses( params: TextBuilderParams, out: ArrayList, + textStyles: MutableMap, factory: QuranTranslationFactory, quranRepository: QuranRepository, chapterNo: Int, fromVerse: Int, toVerse: Int ) { + val wbwTranslationEnabled = ReaderPreferences.getWbwShowTranslation() + val wbwTransliterationEnabled = ReaderPreferences.getWbwShowTransliteration() + val wbwId = ReaderPreferences.getWbwId() + val scriptCode = ReaderPreferences.getQuranScript() val batch = quranRepository.loadChapterVerseBatch(chapterNo, fromVerse, toVerse, scriptCode) ?: return @@ -143,11 +158,9 @@ object ReaderItemsBuilder { toVerse, ) - val arabicWrapByPage = HashMap>() - - fun arabicWrapForPage(pageNo: Int): Pair = - arabicWrapByPage.getOrPut(pageNo) { - val style = getQuranTextStyle( + fun ensureQuranTextStyleForPage(pageNo: Int) { + textStyles.getOrPut(pageNo) { + getQuranTextStyle( QuranTextStyleParams( context = params.context, fontResolver = params.fontResolver, @@ -158,8 +171,8 @@ object ReaderItemsBuilder { sizeMultiplier = params.arabicSizeMultiplier, ) ) - style.toParagraphStyle() to style.toSpanStyle() } + } val translationWrapStyles = booksInfo.keys.associateWith { slug -> val ts = getTranslationTextStyle( @@ -182,11 +195,13 @@ object ReaderItemsBuilder { fontFamily = fontUrdu, ) - val wbwStyles = TextLinkStyles( - focusedStyle = SpanStyle(color = params.colors.primary), - pressedStyle = SpanStyle(color = params.colors.primary), - hoveredStyle = SpanStyle(color = params.colors.primary), - ) + val wbwByAyah = + if (wbwId != null && (wbwTranslationEnabled || wbwTransliterationEnabled)) { + val ids = + (fromVerse..toVerse).mapNotNull { vn -> batch.ayahByVerseNo[vn]?.ayahId } + if (ids.isEmpty()) emptyMap() + else quranRepository.getWbwWordsForAyahs(wbwId, ids) + } else emptyMap() for (verseNo in fromVerse..toVerse) { val translations = @@ -198,6 +213,7 @@ object ReaderItemsBuilder { if (words.isEmpty()) continue val pageNo = batch.pageByVerseNo[verseNo] ?: -1 + ensureQuranTextStyleForPage(pageNo) val verse = VerseWithDetails( words = words, @@ -212,35 +228,6 @@ object ReaderItemsBuilder { out.add(ReaderLayoutItem.IsVotd(key = "isVotd-$chapterNo:$verseNo")) } - val parsedQuranText = if (params.arabicEnabled) buildAnnotatedString { - val (paragraphStyle, spanStyle) = arabicWrapForPage(verse.pageNo) - - withStyle(paragraphStyle) { - withStyle(spanStyle) { - verse.words.forEachIndexed { index, word -> - /*withLink( - LinkAnnotation.Clickable( - tag = "wbw", - styles = wbwStyles - ) { - MessageUtils.showRemovableToast( - params.context, - word.text, - Toast.LENGTH_LONG - ) - } - ) {}*/ - - append(word.text) - - if (!word.isLastWordOfAyah) { - append(" ") - } - } - } - } - } else null - val parsedTranslationTexts = translations.mapNotNull { translation -> val bookInfo = booksInfo[translation.bookSlug] ?: return@mapNotNull null @@ -283,8 +270,8 @@ object ReaderItemsBuilder { out.add( ReaderLayoutItem.VerseUI( verse = verse, - parsedQuranText = parsedQuranText, parsedTranslationTexts = parsedTranslationTexts, + wbwByWordIndex = wbwByAyah[verse.id]?.takeIf { it.isNotEmpty() }, isLastInGroup = verseNo == toVerse, key = "verse-$chapterNo:${verse.verseNo}${params.toKey()}" ) @@ -298,23 +285,27 @@ object ReaderItemsBuilder { repository: QuranRepository, chapterNo: Int, verseNos: List, - ): List { + ): ReaderPreparedData? { + val wbwTranslationEnabled = ReaderPreferences.getWbwShowTranslation() + val wbwTransliterationEnabled = ReaderPreferences.getWbwShowTransliteration() + val wbwId = ReaderPreferences.getWbwId() val scriptCode = ReaderPreferences.getQuranScript() + val batch = repository.loadQuickReferenceBatch(chapterNo, verseNos, scriptCode) - ?: return emptyList() + ?: return null val surah = batch.surah val translationFactory = QuranTranslationFactory(context) val out = ArrayList(verseNos.size) + val textStyles = HashMap() + translationFactory.use { factory -> val booksInfo = factory.getTranslationBooksInfoValidated(params.slugs) - val arabicWrapByPage = HashMap>() - - fun arabicWrapForPage(pageNo: Int): Pair = - arabicWrapByPage.getOrPut(pageNo) { - val style = getQuranTextStyle( + fun ensureQuranTextStyleForPage(pageNo: Int) { + textStyles.getOrPut(pageNo) { + getQuranTextStyle( QuranTextStyleParams( context = params.context, fontResolver = params.fontResolver, @@ -325,8 +316,8 @@ object ReaderItemsBuilder { sizeMultiplier = params.arabicSizeMultiplier, ) ) - style.toParagraphStyle() to style.toSpanStyle() } + } val translationWrapStyles = booksInfo.keys.associateWith { slug -> val ts = getTranslationTextStyle( @@ -345,11 +336,12 @@ object ReaderItemsBuilder { fontFamily = fontUrdu, ) - /*val wbwStyles = TextLinkStyles( - focusedStyle = SpanStyle(color = params.colors.primary), - pressedStyle = SpanStyle(color = params.colors.primary), - hoveredStyle = SpanStyle(color = params.colors.primary), - )*/ + val wbwByAyah = + if (wbwId != null && (wbwTranslationEnabled || wbwTransliterationEnabled)) { + val ids = verseNos.mapNotNull { batch.ayahByVerseNo[it]?.ayahId } + if (ids.isEmpty()) emptyMap() + else repository.getWbwWordsForAyahs(wbwId, ids) + } else emptyMap() for ((idx, verseNo) in verseNos.withIndex()) { val ayah = batch.ayahByVerseNo[verseNo] ?: continue @@ -357,6 +349,7 @@ object ReaderItemsBuilder { if (words.isEmpty()) continue val pageNo = batch.pageByVerseNo[verseNo] ?: -1 + ensureQuranTextStyleForPage(pageNo) val translations = factory.getTranslationsVerseRange( params.slugs, chapterNo, verseNo, verseNo @@ -372,29 +365,6 @@ object ReaderItemsBuilder { includeChapterNameInSerial = true } - val parsedQuranText = if (params.arabicEnabled) buildAnnotatedString { - val (paragraphStyle, spanStyle) = arabicWrapForPage(pageNo) - withStyle(paragraphStyle) { - withStyle(spanStyle) { - verse.words.forEachIndexed { index, word -> - /*withLink( - LinkAnnotation.Clickable( - tag = "wbw", styles = wbwStyles - ) { - MessageUtils.showRemovableToast( - params.context, word.text, Toast.LENGTH_LONG - ) - } - ) {}*/ - - append(word.text) - - if (!word.isLastWordOfAyah) append(" ") - } - } - } - } else null - val parsedTranslationTexts = translations.mapNotNull { translation -> val bookInfo = booksInfo[translation.bookSlug] ?: return@mapNotNull null val (paragraphStyle, translationSpanStyle) = @@ -429,8 +399,8 @@ object ReaderItemsBuilder { out.add( ReaderLayoutItem.VerseUI( verse = verse, - parsedQuranText = parsedQuranText, parsedTranslationTexts = parsedTranslationTexts, + wbwByWordIndex = wbwByAyah[verse.id]?.takeIf { it.isNotEmpty() }, isLastInGroup = idx == verseNos.lastIndex, key = "qref-$chapterNo:$verseNo${params.toKey()}" ) @@ -438,7 +408,7 @@ object ReaderItemsBuilder { } } - return out + return ReaderPreparedData(out, textStyles) } /** diff --git a/app/src/main/java/com/quranapp/android/utils/reader/ReaderTextSizeUtils.kt b/app/src/main/java/com/quranapp/android/utils/reader/ReaderTextSizeUtils.kt index e0b38a151..aa580e6ca 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/ReaderTextSizeUtils.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/ReaderTextSizeUtils.kt @@ -7,6 +7,7 @@ object ReaderTextSizeUtils { const val KEY_TEXT_SIZE_MULT_ARABIC: String = "key.textsize.mult.arabic" const val KEY_TEXT_SIZE_MULT_TRANSL: String = "key.textsize.mult.translation" const val KEY_TEXT_SIZE_MULT_TAFSIR: String = "key.textsize.mult.tafsir" + const val KEY_TEXT_SIZE_MULT_WBW: String = "key.textsize.mult.wbw" const val TEXT_SIZE_MIN_PROGRESS: Int = 50 const val TEXT_SIZE_MAX_PROGRESS: Int = 200 @@ -14,6 +15,7 @@ object ReaderTextSizeUtils { const val TEXT_SIZE_MULT_AR_DEFAULT: Float = 1.0f const val TEXT_SIZE_MULT_TRANSL_DEFAULT: Float = 1.0f const val TEXT_SIZE_MULT_TAFSIR_DEFAULT: Float = 1.0f + const val TEXT_SIZE_MULT_WBW_DEFAULT: Float = 1.0f @JvmStatic val maxProgress: Int @@ -25,10 +27,14 @@ object ReaderTextSizeUtils { } @JvmStatic - fun calculateMultiplier(progress: Int): Float { + fun calculateMultiplier( + progress: Int, + min: Int = TEXT_SIZE_MIN_PROGRESS, + max: Int = TEXT_SIZE_MAX_PROGRESS + ): Float { var progress = progress - progress = max(progress, TEXT_SIZE_MIN_PROGRESS) - progress = min(progress, TEXT_SIZE_MAX_PROGRESS) + progress = max(progress, min) + progress = min(progress, max) return progress.toFloat() / 100 } diff --git a/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwManager.kt b/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwManager.kt new file mode 100644 index 000000000..a402b771e --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwManager.kt @@ -0,0 +1,147 @@ +package com.quranapp.android.utils.reader.wbw + +import android.content.Context +import com.quranapp.android.api.JsonHelper +import com.quranapp.android.api.RetrofitInstance +import com.quranapp.android.api.models.wbw.AvailableWbwInfoModel +import com.quranapp.android.api.models.wbw.WbwLanguageInfo +import com.quranapp.android.utils.Log +import com.quranapp.android.utils.app.AppUtils +import com.quranapp.android.utils.univ.FileUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import kotlinx.serialization.encodeToString +import java.io.File + +object WbwManager { + const val DIR_NAME = "wbw" + const val MANIFEST_FILENAME = "available_wbw_info.json" + + val ROOT_DIR_PATH: String = FileUtils.createPath( + AppUtils.BASE_APP_DOWNLOADED_SAVED_DATA_DIR, + DIR_NAME + ) + + @Volatile + private var cachedManifest: AvailableWbwInfoModel? = null + + private val lock = Mutex() + + private fun getRootDir(context: Context): File { + val dir = File(context.applicationContext.filesDir, ROOT_DIR_PATH) + if (!dir.exists()) { + dir.mkdirs() + } + return dir + } + + private fun getManifestFile(context: Context): File { + return File(getRootDir(context), MANIFEST_FILENAME) + } + + fun getTempDownloadFile(context: Context, id: String): File { + return File(getRootDir(context), "${id}.tmp") + } + + suspend fun getAvailable( + context: Context, + forceRefresh: Boolean = false + ): AvailableWbwInfoModel? { + val inMemory = cachedManifest + if (!forceRefresh && inMemory != null) { + return inMemory + } + + return lock.withLock { + val recheck = cachedManifest + if (!forceRefresh && recheck != null) { + return@withLock recheck + } + + if (!forceRefresh) { + loadLocal(context)?.let { + cachedManifest = it + return@withLock it + } + } + + val network = loadNetwork(context) ?: return@withLock null + cachedManifest = network + network + } + } + + suspend fun getStaleResources( + context: Context, + forceManifestRefresh: Boolean = false + ): List { + val available = getAvailable(context, forceManifestRefresh) ?: return emptyList() + val store = WbwVersionStore(context) + return available.wbw.filter { item -> + item.version > store.getItemVersion(item.id) + } + } + + suspend fun isManifestUpdated( + context: Context, + forceManifestRefresh: Boolean = false + ): Boolean { + val available = getAvailable(context, forceManifestRefresh) ?: return false + val localVersion = WbwVersionStore(context).getManifestVersion() + return (available.version ?: 1) > localVersion + } + + fun markResourceVersion( + context: Context, + id: String, + version: Int + ) { + WbwVersionStore(context).setItemVersion(id, version) + } + + fun getResourceVersion( + context: Context, + id: String + ): Int { + return WbwVersionStore(context).getItemVersion(id) + } + + private suspend fun loadLocal( + context: Context + ): AvailableWbwInfoModel? = withContext(Dispatchers.IO) { + val file = getManifestFile(context) + if (!file.exists() || file.length() <= 0) return@withContext null + + return@withContext try { + JsonHelper.json.decodeFromString(file.readText()) + } catch (e: Exception) { + Log.saveError(e, "WbwManager.loadLocal") + null + } + } + + private suspend fun loadNetwork( + context: Context + ): AvailableWbwInfoModel? = withContext(Dispatchers.IO) { + val manifest = try { + RetrofitInstance.github.getAvailableWbwInfo() + } catch (e: Exception) { + Log.saveError(e, "WbwManager.loadNetwork") + return@withContext null + } + + val file = getManifestFile(context) + file.parentFile?.mkdirs() + file.writeText(JsonHelper.json.encodeToString(manifest)) + + val store = WbwVersionStore(context) + + if ((manifest.version ?: 1) > store.getManifestVersion()) { + store.setManifestVersion(manifest.version) + } + + manifest + } +} diff --git a/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwVersionStore.kt b/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwVersionStore.kt new file mode 100644 index 000000000..c561a6b1f --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwVersionStore.kt @@ -0,0 +1,29 @@ +package com.quranapp.android.utils.reader.wbw + +import android.content.Context +import androidx.core.content.edit + +class WbwVersionStore( + context: Context +) { + companion object { + private const val KEY_MANIFEST_VERSION = "wbw.manifest.version" + private const val KEY_ITEM_VERSION_PREFIX = "wbw.item.version." + } + + private val appContext = context.applicationContext + + private fun sp() = appContext.getSharedPreferences("sp_wbw_versions", Context.MODE_PRIVATE) + + fun getManifestVersion(): Int = sp().getInt(KEY_MANIFEST_VERSION, 0) + + fun setManifestVersion(version: Int?) { + sp().edit { putInt(KEY_MANIFEST_VERSION, version ?: 1) } + } + + fun getItemVersion(id: String): Int = sp().getInt(KEY_ITEM_VERSION_PREFIX + id, 0) + + fun setItemVersion(id: String, version: Int) { + sp().edit { putInt(KEY_ITEM_VERSION_PREFIX + id, version) } + } +} diff --git a/app/src/main/java/com/quranapp/android/utils/workers/WbwDownloadWorker.kt b/app/src/main/java/com/quranapp/android/utils/workers/WbwDownloadWorker.kt new file mode 100644 index 000000000..0d776b54e --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/workers/WbwDownloadWorker.kt @@ -0,0 +1,217 @@ +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.ServiceInfo +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.work.CoroutineWorker +import androidx.work.ForegroundInfo +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import com.quranapp.android.R +import com.quranapp.android.activities.ActivitySettings +import com.quranapp.android.api.JsonHelper +import com.quranapp.android.api.RetrofitInstance +import com.quranapp.android.api.models.wbw.WbwLanguageInfo +import com.quranapp.android.api.models.wbw.WbwPayloadModel +import com.quranapp.android.compose.navigation.SettingRoutes +import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.db.DatabaseProvider +import com.quranapp.android.db.entities.wbw.WbwWordEntity +import com.quranapp.android.utils.app.NotificationUtils +import com.quranapp.android.utils.extensions.isGzip +import com.quranapp.android.utils.reader.wbw.WbwManager +import com.quranapp.android.utils.univ.Keys +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.util.zip.GZIPInputStream + +class WbwDownloadWorker( + private val ctx: Context, + params: WorkerParameters +) : CoroutineWorker(ctx, params) { + + override suspend fun doWork(): Result { + val wbwInfoJson = inputData.getString("wbwInfo") ?: return Result.failure() + val info = Json.decodeFromString(wbwInfoJson) + + setForeground(createForegroundInfo(info, 0)) + + return try { + downloadAndStore(info) + Result.success() + } catch (e: Exception) { + Result.failure(workDataOf("error" to (e.message ?: "WBW download failed"))) + } + } + + private suspend fun downloadAndStore( + info: WbwLanguageInfo + ) = withContext(Dispatchers.IO) { + val tmpFile = WbwManager.getTempDownloadFile(ctx, info.id) + + try { + downloadToFile( + url = info.url, + dest = tmpFile + ) { progress -> + if (!isStopped) { + setProgressAsync(workDataOf("progress" to (progress ?: 0))) + setForeground(createForegroundInfo(info, progress)) + } + } + + val payload = decodePayload(tmpFile) + val entities = toEntities(payload, info.id) + + val db = DatabaseProvider.getExternalQuranDatabase(ctx) + db.wbwDao().replaceByWbwId(info.id, entities) + ReaderPreferences.bumpWbwContentEpoch() + + WbwManager.markResourceVersion( + context = ctx, + id = info.id, + version = payload.version + ) + } finally { + if (tmpFile.exists()) { + tmpFile.delete() + } + } + } + + private suspend fun downloadToFile( + url: String, + dest: File, + setProgress: suspend (Int?) -> Unit, + ) = withContext(Dispatchers.IO) { + val response = if (url.startsWith("ghraw://")) { + RetrofitInstance.githubLike.getRawContent( + url.removePrefix("ghraw://").trimStart('/') + ) + } else { + RetrofitInstance.any.downloadStreaming(url) + } + + if (!response.isSuccessful) { + throw IOException("WBW download failed: HTTP ${response.code()}") + } + + val body = response.body() ?: throw IOException("WBW response body is null") + val totalBytes = body.contentLength() + var downloaded = 0L + + body.byteStream().use { input -> + dest.outputStream().buffered().use { output -> + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + while (true) { + ensureActive() + val bytes = input.read(buffer) + if (bytes <= 0) break + + output.write(buffer, 0, bytes) + downloaded += bytes + + val progress = if (totalBytes > 0) { + ((downloaded * 100) / totalBytes).toInt() + } else { + null + } + setProgress(progress) + } + output.flush() + } + } + } + + private suspend fun decodePayload(source: File): WbwPayloadModel = withContext(Dispatchers.IO) { + if (!source.exists()) throw IOException("Source file does not exist") + + val inputFactory: () -> InputStream = if (source.isGzip()) { + { GZIPInputStream(source.inputStream().buffered()) } + } else { + { source.inputStream().buffered() } + } + + inputFactory().use { stream -> + val content = stream.reader().use { it.readText() } + JsonHelper.json.decodeFromString(content) + } + } + + private fun createForegroundInfo( + info: WbwLanguageInfo, + progress: Int? + ): ForegroundInfo { + val channelId = NotificationUtils.CHANNEL_ID_DOWNLOADS + val builder = NotificationCompat.Builder(ctx, channelId).apply { + setAutoCancel(false) + setOngoing(true) + setShowWhen(false) + setSmallIcon(R.drawable.dr_logo) + setContentTitle(ctx.getString(R.string.textDownloading)) + setContentText(info.langName) + setCategory(NotificationCompat.CATEGORY_PROGRESS) + setProgress(100, progress ?: 0, progress == null) + } + + var flag = PendingIntent.FLAG_UPDATE_CURRENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + flag = flag or PendingIntent.FLAG_IMMUTABLE + } + + val activityIntent = Intent(ctx, ActivitySettings::class.java).apply { + putExtra(Keys.NAV_DESTINATION, SettingRoutes.TRANSLATIONS_DOWNLOAD) + } + val pendingIntent = PendingIntent.getActivity( + ctx, + info.id.hashCode(), + activityIntent, + flag + ) + builder.setContentIntent(pendingIntent) + + val cancelIntent = WorkManager.getInstance(applicationContext).createCancelPendingIntent(id) + builder.addAction( + R.drawable.dr_icon_close, + ctx.getString(R.string.strLabelCancel), + cancelIntent + ) + + return ForegroundInfo( + info.id.hashCode(), + builder.build(), + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC + ) + } + + private fun toEntities(payload: WbwPayloadModel, wbwId: String): List { + if (payload.verses.isEmpty()) return emptyList() + + val out = ArrayList() + for ((ayahId, words) in payload.verses) { + if (ayahId <= 0) continue + + words.forEachIndexed { index, pair -> + val translation = pair.getOrNull(0)?.takeIf { it.isNotBlank() } + val transliteration = pair.getOrNull(1)?.takeIf { it.isNotBlank() } + out.add( + WbwWordEntity( + ayahId = ayahId, + wordIndex = index, + wbwId = wbwId, + translation = translation, + transliteration = transliteration, + ) + ) + } + } + return out + } +} diff --git a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt index 7364d737c..225b69283 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt @@ -16,6 +16,7 @@ import com.quranapp.android.compose.components.reader.QuranPageItem import com.quranapp.android.compose.components.reader.QuranPageLineItem import com.quranapp.android.compose.components.reader.ReaderLayoutItem import com.quranapp.android.compose.components.reader.ReaderMode +import com.quranapp.android.compose.components.reader.ReaderPreparedData import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.entities.ReadHistoryEntity import com.quranapp.android.utils.Log @@ -41,6 +42,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -104,9 +106,21 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic private val _navigateToVerse = MutableStateFlow(null) val navigateToVerse: StateFlow = _navigateToVerse.asStateFlow() - private val _verseByVerseItems = MutableStateFlow>(emptyList()) + private val _verseByVersePrepared = MutableStateFlow( + ReaderPreparedData(emptyList(), emptyMap()), + ) + + val verseByVersePrepared: StateFlow = + _verseByVersePrepared.asStateFlow() + val verseByVerseItems: StateFlow> = - _verseByVerseItems.asStateFlow() + _verseByVersePrepared + .map { it.items } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = emptyList(), + ) val pageItems = mutableStateMapOf() val pageCounts = mutableStateMapOf() @@ -157,6 +171,8 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic ReaderPreferences.KEY_TEXT_SIZE_MULT_ARABIC, ReaderPreferences.KEY_TRANSLATIONS, ReaderPreferences.KEY_ARABIC_TEXT_ENABLED, + ReaderPreferences.KEY_WBW, + ReaderPreferences.KEY_WBW_CONTENT_EPOCH, ), readerMode, ) { uiState, prefs, readerMode -> @@ -323,7 +339,8 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic fun updateLastKnownVerseFromItems(firstVisibleIndex: Int) { - val items = _verseByVerseItems.value + val items = verseByVerseItems.value + for (i in firstVisibleIndex until items.size) { val item = items[i] if (item is ReaderLayoutItem.VerseUI) { @@ -475,7 +492,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } ReaderMode.VerseByVerse -> { - val isInView = _verseByVerseItems.value.any { item -> + val isInView = verseByVerseItems.value.any { item -> item is ReaderLayoutItem.VerseUI && item.verse.chapterNo == chapterNo && item.verse.verseNo == verseNo @@ -500,7 +517,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic state: ReaderUiState, readerMode: ReaderMode, ) { - _verseByVerseItems.value = withContext(Dispatchers.IO) { + _verseByVersePrepared.value = withContext(Dispatchers.IO) { when (val vt = state.viewType) { is ReaderViewType.Juz -> ReaderItemsBuilder.buildJuzVersesForTranslationMode( context, params, repository, vt.juzNo @@ -514,9 +531,9 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic context, params, repository, vt.chapterNo, ) - null -> emptyList() + null -> ReaderPreparedData(emptyList(), emptyMap()) } - } + } ?: ReaderPreparedData(emptyList(), emptyMap()) } suspend fun resolvePageNo(chapterNo: Int, verseNo: Int = 1, mushafCode: String? = null) = diff --git a/app/src/main/java/com/quranapp/android/viewModels/WbwSettingsViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/WbwSettingsViewModel.kt new file mode 100644 index 000000000..83d5fe363 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/viewModels/WbwSettingsViewModel.kt @@ -0,0 +1,214 @@ +package com.quranapp.android.viewModels + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.quranapp.android.api.models.wbw.WbwLanguageInfo +import com.quranapp.android.compose.utils.DataLoadError +import com.quranapp.android.compose.utils.appFallbackLanguageCodes +import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.db.DatabaseProvider +import com.quranapp.android.utils.managers.ResourceDownloadStatus +import com.quranapp.android.utils.managers.WbwDownloadManager +import com.quranapp.android.utils.reader.wbw.WbwManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +data class WbwUiModel( + val info: WbwLanguageInfo, + val isDownloaded: Boolean, + val isUpdateAvailable: Boolean, +) + +data class WbwSettingsUiState( + val isLoading: Boolean = true, + val error: DataLoadError? = null, + val selectedWbwId: String? = null, + val rows: List = emptyList(), + val downloadStates: Map = emptyMap(), +) + +class WbwSettingsViewModel( + application: Application +) : AndroidViewModel(application) { + private val db = DatabaseProvider.getExternalQuranDatabase(context) + private val context get() = getApplication() + + private val _uiState = MutableStateFlow(WbwSettingsUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + WbwDownloadManager.initialize(context) + observeSelection() + observeDownloads() + load(force = false) + } + + private fun observeSelection() { + viewModelScope.launch { + ReaderPreferences.wbwIdFlow().collect { selected -> + _uiState.update { it.copy(selectedWbwId = selected) } + } + } + } + + private fun observeDownloads() { + viewModelScope.launch { + WbwDownloadManager.observeDownloadsAsFlow().collect { (id, status) -> + _uiState.update { state -> + val next = state.downloadStates.toMutableMap() + when (status) { + is ResourceDownloadStatus.Completed, + is ResourceDownloadStatus.Cancelled -> next.remove(id) + + else -> next[id] = status + } + state.copy(downloadStates = next) + } + + if (status is ResourceDownloadStatus.Completed) { + refreshRows() + } + } + } + } + + fun load(force: Boolean) { + _uiState.update { it.copy(isLoading = true, error = null) } + + viewModelScope.launch { + val manifest = WbwManager.getAvailable(context, forceRefresh = force) + if (manifest == null) { + _uiState.update { it.copy(isLoading = false, error = DataLoadError.Failed) } + return@launch + } + + val rows = buildRows(manifest.wbw) + val selected = resolveSelectedId(rows) + + _uiState.update { + it.copy( + isLoading = false, + error = if (rows.isEmpty()) DataLoadError.NoData else null, + rows = rows, + selectedWbwId = selected, + ) + } + } + } + + private suspend fun refreshRows() { + val rows = _uiState.value.rows + if (rows.isEmpty()) return + val refreshed = buildRows(rows.map { it.info }) + val selected = resolveSelectedId(refreshed) + + _uiState.update { + it.copy( + rows = refreshed, + selectedWbwId = selected, + error = if (refreshed.isEmpty()) DataLoadError.NoData else null, + ) + } + } + + private suspend fun buildRows( + languages: List + ): List = withContext(Dispatchers.IO) { + if (languages.isEmpty()) return@withContext emptyList() + + val wbwIds = languages.map { it.id }.distinct() + val downloadedIds = db + .wbwDao() + .getDownloadedWbwIds(wbwIds) + .toSet() + + return@withContext languages + .sortedWith( + compareBy { + if (it.langCode.equals("en", ignoreCase = true)) 0 else 1 + }.thenBy { it.langName }.thenBy { it.langCode } + ) + .map { info -> + val isDownloaded = downloadedIds.contains(info.id) + val localVersion = WbwManager.getResourceVersion(context, info.id) + val isUpdateAvailable = isDownloaded && info.version > localVersion + WbwUiModel( + info = info, + isDownloaded = isDownloaded, + isUpdateAvailable = isUpdateAvailable + ) + } + } + + private fun resolveSelectedId(rows: List): String? { + val availableIds = rows.map { it.info.id }.toSet() + val downloadedIds = rows.filter { it.isDownloaded }.map { it.info.id }.toSet() + val preferred = ReaderPreferences.getWbwId() + val preferredByLocaleDownloaded = rows.findLocalePreferredId() + + return if (downloadedIds.isNotEmpty()) { + ReaderPreferences.validateWbwId(preferred, downloadedIds, preferredByLocaleDownloaded) + } else { + ReaderPreferences.validateWbwId(preferred, availableIds, null) + } + } + + private fun List.findLocalePreferredId(): String? { + if (isEmpty()) return null + + val candidates = appFallbackLanguageCodes().toList() + for (candidate in candidates) { + val normalized = candidate.lowercase() + val match = firstOrNull { row -> + (row.isDownloaded) && + row.info.langCode.lowercase() == normalized + } ?: firstOrNull { row -> + (row.isDownloaded) && + normalized.startsWith("${row.info.langCode.lowercase()}-") + } + + if (match != null) { + return match.info.id + } + } + + return firstOrNull { it.isDownloaded }?.info?.id + } + + fun selectLanguage(id: String) { + val selectedRow = _uiState.value.rows.firstOrNull { it.info.id == id } ?: return + if (!selectedRow.isDownloaded) return + + viewModelScope.launch { + ReaderPreferences.setWbwId(id) + } + } + + fun startDownload(id: String) { + val info = _uiState.value.rows.firstOrNull { it.info.id == id }?.info ?: return + WbwDownloadManager.startDownload(context, info) + _uiState.update { + it.copy(downloadStates = it.downloadStates + (id to ResourceDownloadStatus.Started)) + } + } + + fun cancelDownload(id: String) { + WbwDownloadManager.stopDownload(context, id) + _uiState.update { + it.copy(downloadStates = it.downloadStates - id) + } + } + + fun deleteWbwData(id: String) { + viewModelScope.launch { + db.wbwDao().deleteByWbwId(id) + refreshRows() + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3bbdcb9e1..20d27f394 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -360,4 +360,22 @@ Learn more + + Word by Word + + + Select language + + Show translation + Show transliteration + When available for the selected language + Recitation + Play audio when a word is clicked + Text size + + Download a language pack below to turn on word-by-word features. + + + Delete data? + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d8a496e77..b5d5226ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,6 @@ viewbinding = "8.8.0" browser = "1.8.0" exoplayer = "2.19.1" media3 = "1.5.1" -swipeRefreshLayout = "1.7.2.4" retrofit = "3.0.0" kotlinxSerialization = "1.4.1" kotlinxRetrofit = "0.8.0" @@ -74,15 +73,6 @@ media3ExoPlayer = { module = "androidx.media3:media3-exoplayer", version.ref = " media3Session = { module = "androidx.media3:media3-session", version.ref = "media3" } media3UI = { module = "androidx.media3:media3-ui", version.ref = "media3" } -# SmoothRefreshLayout -srlCore = { group = "com.github.dkzwm", name = 'srl-core', version.ref = "swipeRefreshLayout" } -srlExtClassics = { group = "com.github.dkzwm", name = 'srl-ext-classics', version.ref = "swipeRefreshLayout" } -srlExtMaterial = { group = "com.github.dkzwm", name = 'srl-ext-material', version.ref = "swipeRefreshLayout" } -srlExtDynamicRebound = { group = "com.github.dkzwm", name = 'srl-ext-dynamic-rebound', version.ref = "swipeRefreshLayout" } -srlExtHorizontal = { group = "com.github.dkzwm", name = 'srl-ext-horizontal', version.ref = "swipeRefreshLayout" } -srlExtTwoLevel = { group = "com.github.dkzwm", name = 'srl-ext-two-level', version.ref = "swipeRefreshLayout" } -srlExtUtil = { group = "com.github.dkzwm", name = 'srl-ext-util', version.ref = "swipeRefreshLayout" } - retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } kotlinxSerialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" } kotlinxRetrofit = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "kotlinxRetrofit" } diff --git a/inventory/versions/resources_versions.json b/inventory/versions/resources_versions.json index adf01f35f..b7e10604e 100644 --- a/inventory/versions/resources_versions.json +++ b/inventory/versions/resources_versions.json @@ -3,6 +3,5 @@ "translations": 16, "recitations": 4, "recitationTranslations": 7, - "tafsirs": 1, - "wbw": 1 + "tafsirs": 1 } diff --git a/inventory/wbw/available_wbw_info.json b/inventory/wbw/available_wbw_info.json index 80aa8604c..57a144ac3 100644 --- a/inventory/wbw/available_wbw_info.json +++ b/inventory/wbw/available_wbw_info.json @@ -1,4 +1,5 @@ { + "version": 1, "wbw": [ { "id": "wbw_en", From 4cb1a7c9b424780cd594220315defc1c56f64767 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Sat, 18 Apr 2026 20:11:06 +0530 Subject: [PATCH 03/16] Wbw bottom sheet --- .../components/reader/ChapterVersePair.kt | 2 +- .../compose/components/reader/Mushaf.kt | 106 ++-- .../compose/components/reader/QuranTextWbw.kt | 29 +- .../components/reader/ReaderProvider.kt | 31 +- .../compose/components/reader/VerseView.kt | 3 + .../components/reader/dialogs/WbwSheet.kt | 502 ++++++++++++++++++ .../android/utils/quran/QuranUtils.java | 2 +- .../android/viewModels/ReaderViewModel.kt | 18 +- app/src/main/res/values/strings.xml | 5 + 9 files changed, 634 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/WbwSheet.kt diff --git a/app/src/main/java/com/quranapp/android/components/reader/ChapterVersePair.kt b/app/src/main/java/com/quranapp/android/components/reader/ChapterVersePair.kt index e525c72be..06115b020 100644 --- a/app/src/main/java/com/quranapp/android/components/reader/ChapterVersePair.kt +++ b/app/src/main/java/com/quranapp/android/components/reader/ChapterVersePair.kt @@ -14,7 +14,7 @@ data class ChapterVersePair(val chapterNo: Int, val verseNo: Int) : Serializable } fun doesEqual(ayahId: Int): Boolean { - val pair = QuranUtils.getVerseNo(ayahId) + val pair = QuranUtils.getVerseNoFromAyahId(ayahId) return doesEqual(pair.first, pair.second) } diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt index 2b10a43f3..21fedc49b 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt @@ -1,6 +1,7 @@ package com.quranapp.android.compose.components.reader import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -22,6 +23,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -45,10 +47,13 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.quranapp.android.compose.components.common.Loader +import com.quranapp.android.compose.components.reader.dialogs.WbwSheet +import com.quranapp.android.compose.components.reader.dialogs.WbwSheetData import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.entities.quran.AyahWordEntity import com.quranapp.android.utils.Log -import com.quranapp.android.utils.mediaplayer.RecitationController +import com.quranapp.android.utils.quran.QuranUtils import com.quranapp.android.utils.reader.MUSHAF_PAGE_HORIZONTAL_PADDING import com.quranapp.android.utils.reader.PageBuilderParams import com.quranapp.android.utils.reader.mushafShowsRuledPageDecoration @@ -241,23 +246,50 @@ fun ReaderLayoutPageMode( } } + var wbwSheetData by remember { mutableStateOf(null) } + val wbwIdRaw = ReaderPreferences.observeWbwId() + val wbwRecitationOn = ReaderPreferences.observeWbwRecitationEnabled() + val mushafWordTapEnabled = wbwIdRaw.isNotBlank() || wbwRecitationOn + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { - HorizontalPager( - state = pagerState, - beyondViewportPageCount = 1, + Box( modifier = Modifier .fillMaxWidth() - .fillMaxHeight() - ) { page -> - PageModePage( - readerVm = readerVm, - pageNo = page + 1, - contentWidth, - ruledPageDecoration, - nestedScrollConnection, - ) + .fillMaxHeight(), + ) { + HorizontalPager( + state = pagerState, + beyondViewportPageCount = 1, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + ) { page -> + PageModePage( + readerVm = readerVm, + pageNo = page + 1, + contentWidth, + ruledPageDecoration, + nestedScrollConnection, + onMushafWordClick = { word -> + val pair = QuranUtils.getVerseNoFromAyahId(word.ayahId) + + wbwSheetData = WbwSheetData( + chapterNo = pair.first, + verseNo = pair.second, + wordIndex = word.wordIndex + ) + }, + mushafWordTapEnabled = mushafWordTapEnabled, + ) + } + } } + + WbwSheet( + data = wbwSheetData, + onDismiss = { wbwSheetData = null }, + ) } @Composable @@ -267,6 +299,8 @@ private fun PageModePage( contentWidth: Dp, ruledPageDecoration: Boolean, nestedScrollConnection: NestedScrollConnection, + onMushafWordClick: (AyahWordEntity) -> Unit, + mushafWordTapEnabled: Boolean, ) { val item = readerVm.pageItems[pageNo] @@ -368,16 +402,18 @@ private fun PageModePage( MushafLineContent( line = line, playingWordKeys = playingWordKeys, - controller = playerState.controller, - ruledPageDecoration + ruledPageDecoration = ruledPageDecoration, + onMushafWordClick = onMushafWordClick, + mushafWordTapEnabled = mushafWordTapEnabled, ) } } else { MushafLineContent( line = line, playingWordKeys = playingWordKeys, - controller = playerState.controller, ruledPageDecoration = ruledPageDecoration, + onMushafWordClick = onMushafWordClick, + mushafWordTapEnabled = mushafWordTapEnabled, ) } } @@ -395,8 +431,9 @@ private fun PageModePage( private fun MushafLineContent( line: QuranPageLineItem, playingWordKeys: Set>, - controller: RecitationController, ruledPageDecoration: Boolean, + onMushafWordClick: (AyahWordEntity) -> Unit, + mushafWordTapEnabled: Boolean, ) { when (line) { is QuranPageLineItem.Title -> ChapterTitle(line.chapterNo, ruledPageDecoration) @@ -405,7 +442,8 @@ private fun MushafLineContent( textLine = line, layout = line.layout, playingWordKeys = playingWordKeys, - controller = controller, + onMushafWordClick = onMushafWordClick, + mushafWordTapEnabled = mushafWordTapEnabled, ) } } @@ -415,7 +453,8 @@ private fun MushafLineText( textLine: QuranPageLineItem.Text, layout: MushafLineLayout, playingWordKeys: Set>, - controller: RecitationController, + onMushafWordClick: (AyahWordEntity) -> Unit, + mushafWordTapEnabled: Boolean, ) { val words = textLine.words val fittedStyle = layout.fittedStyle @@ -435,7 +474,10 @@ private fun MushafLineText( word, fittedStyle, isHighlighted = (word.ayahId to word.wordIndex) in playingWordKeys, - controller + mushafWordTapEnabled = mushafWordTapEnabled, + onClick = { + onMushafWordClick(word) + }, ) } } @@ -451,7 +493,10 @@ private fun MushafLineText( word, fittedStyle, isHighlighted = (word.ayahId to word.wordIndex) in playingWordKeys, - controller + mushafWordTapEnabled = mushafWordTapEnabled, + onClick = { + onMushafWordClick(word) + }, ) } } @@ -463,10 +508,9 @@ private fun Word( word: AyahWordEntity, fittedStyle: TextStyle, isHighlighted: Boolean, - controller: RecitationController + onClick: () -> Unit, + mushafWordTapEnabled: Boolean, ) { -// val context = LocalContext.current - Text( text = word.text, color = colorScheme.onBackground, @@ -478,13 +522,13 @@ private fun Word( if (isHighlighted) colorScheme.primary.alpha(0.4f) else Color.Transparent ) - /*.clickable { - if (word.isLastWordOfAyah) { - MessageUtils.showRemovableToast(context, "LAST WORD", Toast.LENGTH_LONG) - } else { - MessageUtils.showRemovableToast(context, word.text, Toast.LENGTH_LONG) - } - },*/ + .then( + if (mushafWordTapEnabled) { + Modifier.clickable { onClick() } + } else { + Modifier + } + ) ) } diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/QuranTextWbw.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/QuranTextWbw.kt index a1d36026e..b8851412e 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/QuranTextWbw.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/QuranTextWbw.kt @@ -24,9 +24,14 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.db.entities.quran.AyahWordEntity @Composable -fun QuranTextWbw(verseUi: ReaderLayoutItem.VerseUI) { +fun QuranTextWbw( + verseUi: ReaderLayoutItem.VerseUI, + onWordClick: ((AyahWordEntity) -> Unit)? +) { val wbwMap = verseUi.wbwByWordIndex ?: emptyMap() val textStyles = LocalQuranTextStyle.current val recitation = LocalRecitation.current @@ -34,6 +39,18 @@ fun QuranTextWbw(verseUi: ReaderLayoutItem.VerseUI) { val arabicStyle = textStyles.quran(verseUi.verse.pageNo) ?: TextStyle.Default val dividerColor = colorScheme.outlineVariant + fun handleWordClick(word: AyahWordEntity) { + if (onWordClick != null) { + onWordClick(word) + } else if (ReaderPreferences.getWbwRecitationEnabled() && !word.isLastWordOfAyah) { + recitation.playWord( + verseUi.verse.chapterNo, + verseUi.verse.verseNo, + word.wordIndex + ) + } + } + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { FlowRow( modifier = Modifier @@ -47,6 +64,10 @@ fun QuranTextWbw(verseUi: ReaderLayoutItem.VerseUI) { Text( text = word.text, style = arabicStyle, + modifier = Modifier + .clickable { + handleWordClick(word) + }, ) } else { val wbw = wbwMap[word.wordIndex] @@ -60,11 +81,7 @@ fun QuranTextWbw(verseUi: ReaderLayoutItem.VerseUI) { modifier = Modifier .wrapContentWidth(Alignment.CenterHorizontally) .clickable { - recitation.playWord( - verseUi.verse.chapterNo, - verseUi.verse.verseNo, - word.wordIndex - ) + handleWordClick(word) }, horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(2.dp), diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderProvider.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderProvider.kt index 7e58a6c85..1ada04385 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderProvider.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderProvider.kt @@ -64,7 +64,6 @@ fun ReaderProvider( var verseOptionsVerse by remember { mutableStateOf(null) } var quickReferenceData by remember { mutableStateOf(null) } - val wbwRecitationEnabled = ReaderPreferences.observeWbwRecitationEnabled() var wbwWordLoadingKey by remember { mutableStateOf(null) } CompositionLocalProvider( @@ -109,22 +108,20 @@ fun ReaderProvider( isAnyPlaying = isPlaying, playingVerse = recitationState.currentVerse, playWord = { chapterNo, verseNo, wordIndex -> - if (wbwRecitationEnabled) { - coroutineScope.launch { - val key = "$chapterNo:$verseNo:$wordIndex" - wbwWordLoadingKey = key - - try { - WbwAudioPlayer.play( - context, - chapterNo, - verseNo, - wordIndex, - ) - } finally { - if (wbwWordLoadingKey == key) { - wbwWordLoadingKey = null - } + coroutineScope.launch { + val key = "$chapterNo:$verseNo:$wordIndex" + wbwWordLoadingKey = key + + try { + WbwAudioPlayer.play( + context, + chapterNo, + verseNo, + wordIndex, + ) + } finally { + if (wbwWordLoadingKey == key) { + wbwWordLoadingKey = null } } } diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt index 4e5acbffa..64886503a 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt @@ -36,6 +36,7 @@ import com.quranapp.android.R import com.quranapp.android.components.reader.ChapterVersePair import com.quranapp.android.compose.components.dialogs.SimpleTooltip import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.db.entities.quran.AyahWordEntity import com.quranapp.android.db.relations.VerseWithDetails import com.quranapp.android.utils.extensions.copyToClipboard import com.quranapp.android.utils.reader.LocalVerseActions @@ -46,6 +47,7 @@ fun VerseView( verseUi: ReaderLayoutItem.VerseUI, isBookmarked: Boolean, showDivider: Boolean = false, + onWordClick: ((AyahWordEntity) -> Unit)? = null ) { val verse = verseUi.verse @@ -65,6 +67,7 @@ fun VerseView( QuranTextWbw( verseUi, + onWordClick ) TranslationText(verseUi = verseUi) diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/WbwSheet.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/WbwSheet.kt new file mode 100644 index 000000000..c7fdf5101 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/WbwSheet.kt @@ -0,0 +1,502 @@ +package com.quranapp.android.compose.components.reader.dialogs + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.LocalResources +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.quranapp.android.R +import com.quranapp.android.compose.components.common.IconButton +import com.quranapp.android.compose.components.common.Loader +import com.quranapp.android.compose.components.reader.LocalReaderViewModel +import com.quranapp.android.compose.components.reader.LocalRecitation +import com.quranapp.android.compose.components.reader.ReaderLayoutItem +import com.quranapp.android.compose.components.reader.TextStyleProvider +import com.quranapp.android.compose.components.reader.VerseView +import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.db.entities.quran.AyahWordEntity +import com.quranapp.android.db.entities.wbw.WbwWordEntity +import com.quranapp.android.repository.QuranRepository +import com.quranapp.android.repository.UserRepository +import com.quranapp.android.utils.extensions.copyToClipboard +import com.quranapp.android.utils.quran.QuranMeta +import com.quranapp.android.utils.quran.QuranUtils +import com.quranapp.android.utils.reader.LocalVerseActions +import com.quranapp.android.utils.reader.QuranScriptUtils +import com.quranapp.android.utils.reader.ReaderItemsBuilder +import com.quranapp.android.utils.reader.TextBuilderParams +import com.quranapp.android.utils.univ.MessageUtils +import com.quranapp.android.viewModels.ReaderProviderViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +data class WbwSheetData( + val chapterNo: Int, + val verseNo: Int, + val wordIndex: Int +) + +private data class WordInfoContent( + val verseUi: ReaderLayoutItem.VerseUI, + val textStyles: Map, + val word: AyahWordEntity, + val wbwWord: WbwWordEntity?, + val chapterName: String, + val prev: WbwSheetData?, + val next: WbwSheetData?, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun WbwSheet( + data: WbwSheetData?, + onDismiss: () -> Unit, +) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + if (data == null) return + + ModalBottomSheet( + onDismissRequest = onDismiss, + sheetState = sheetState, + scrimColor = colorScheme.scrim.alpha(0.5f), + containerColor = colorScheme.surface, + contentColor = colorScheme.onSurface, + ) { + Content(data) + } +} + +@Composable +private fun Content(data: WbwSheetData) { + var currentData by remember { mutableStateOf(data) } + + val context = LocalContext.current + val colors = colorScheme + val type = typography + val vm = LocalReaderViewModel.current + val verseActions = LocalVerseActions.current + + val content by produceState( + null, + currentData, + context, + colors, + type, + verseActions + ) { + withContext(Dispatchers.IO) { + val script = ReaderPreferences.getQuranScript() + val wbwId = ReaderPreferences.getWbwId() + + val words = vm.repository.getWordsForAyah( + currentData.chapterNo, + currentData.verseNo, script + ) + + val theWord = words[currentData.wordIndex] + + val wbwRow = wbwId?.let { + val map = vm.repository.getWbwWordsForAyahs(wbwId, listOf(theWord.ayahId)) + map[theWord.ayahId]?.get(theWord.wordIndex) + } + + val chapterNo = currentData.chapterNo + val verseNo = currentData.verseNo + val wordIndex = currentData.wordIndex + val verseCount = vm.repository.getChapterVerseCount(chapterNo) + + val prev = when { + wordIndex > 0 -> WbwSheetData(chapterNo, verseNo, wordIndex - 1) + verseNo > 1 -> { + val prevWords = vm.repository.getWordsForAyah(chapterNo, verseNo - 1, script) + if (prevWords.isEmpty()) null + else WbwSheetData(chapterNo, verseNo - 1, prevWords.lastIndex) + } + + chapterNo > 1 -> { + val prevCh = chapterNo - 1 + val lastVerse = vm.repository.getChapterVerseCount(prevCh) + if (lastVerse <= 0) null + else { + val prevWords = vm.repository.getWordsForAyah(prevCh, lastVerse, script) + if (prevWords.isEmpty()) null + else WbwSheetData(prevCh, lastVerse, prevWords.lastIndex) + } + } + + else -> null + } + + val next = when { + wordIndex < words.lastIndex -> WbwSheetData(chapterNo, verseNo, wordIndex + 1) + verseNo < verseCount -> { + val nextWords = vm.repository.getWordsForAyah(chapterNo, verseNo + 1, script) + if (nextWords.isEmpty()) null + else WbwSheetData(chapterNo, verseNo + 1, 0) + } + + chapterNo < QuranMeta.chapterRange.last -> { + val nextWords = vm.repository.getWordsForAyah(chapterNo + 1, 1, script) + if (nextWords.isEmpty()) null + else WbwSheetData(chapterNo + 1, 1, 0) + } + + else -> null + } + + val prepared = ReaderItemsBuilder.buildQuickReferenceItems( + context, + params = TextBuilderParams( + context = context, + fontResolver = vm.fontResolver, + verseActions = verseActions, + colors = colors, + type = type, + arabicEnabled = ReaderPreferences.getArabicTextEnabled(), + script = ReaderPreferences.getQuranScript(), + arabicSizeMultiplier = ReaderPreferences.getArabicTextSizeMultiplier(), + translationSizeMultiplier = ReaderPreferences.getTranslationTextSizeMultiplier(), + slugs = ReaderPreferences.getTranslations(), + ), + repository = vm.repository, + chapterNo = chapterNo, + verseNos = listOf(currentData.verseNo) + ) + + val verseRows = prepared?.items.orEmpty().filterIsInstance() + if (prepared == null || verseRows.isEmpty()) return@withContext + + value = WordInfoContent( + verseUi = verseRows.first(), + textStyles = prepared.textStyles, + word = theWord, + wbwWord = wbwRow, + chapterName = vm.repository.getChapterName(currentData.chapterNo), + prev = prev, + next = next, + ) + } + } + + if (content == null) { + return Loader() + } + + TextStyleProvider( + content!!.textStyles + ) { + WordContent(vm, content!!) { + currentData = it + } + } +} + +@Composable +private fun WordContent( + vm: ReaderProviderViewModel, + content: WordInfoContent, + onWordChange: (WbwSheetData) -> Unit +) { + val word = content.word + val wbwRow = content.wbwWord + val verseUi = content.verseUi + val chapterNo = verseUi.verse.chapterNo + val verseNo = verseUi.verse.verseNo + + val recitation = LocalRecitation.current + val wbwRecitationEnabled = ReaderPreferences.observeWbwRecitationEnabled() + val copyScope = rememberCoroutineScope() + val context = LocalContext.current + val resources = LocalResources.current + + LaunchedEffect(word.ayahId, word.wordIndex, wbwRecitationEnabled) { + if (!wbwRecitationEnabled || word.isLastWordOfAyah) return@LaunchedEffect + + recitation.playWord(chapterNo, verseNo, word.wordIndex) + } + + + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .padding(bottom = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + VerseContextHeader( + chapterName = content.chapterName, + chapterNo = chapterNo, + verseNo = verseNo, + word = word, + onCopyWord = { + copyScope.launch { + val text = resolveWordTextForCopy( + vm.repository, + chapterNo, + verseNo, + word, + ) + + context.copyToClipboard(text) + + MessageUtils.showClipboardMessage( + context, + resources.getString(R.string.copiedToClipboard), + ) + } + }, + onPlayWord = { + recitation.playWord(chapterNo, verseNo, word.wordIndex) + } + ) + + ArabicWordCard( + word = word, + wbwRow = wbwRow, + textStyle = content.textStyles.get(verseUi.verse.pageNo) ?: TextStyle.Default, + hasPrev = content.prev != null, + hasNext = content.next != null, + onPrev = { + if (content.prev != null) onWordChange(content.prev) + }, + onNext = { + if (content.next != null) onWordChange(content.next) + }, + ) + + + HorizontalDivider( + modifier = Modifier.padding(top = 24.dp), + color = colorScheme.outlineVariant, + ) + + VerseWrapped( + bookmarksRepo = vm.userRepository, + verseUi = verseUi, + onWordClick = { + val pair = QuranUtils.getVerseNoFromAyahId(it.ayahId) + onWordChange( + WbwSheetData( + pair.first, + pair.second, + it.wordIndex + ) + ) + } + ) + } +} + +@Composable +private fun VerseContextHeader( + chapterName: String, + chapterNo: Int, + verseNo: Int, + word: AyahWordEntity, + onCopyWord: () -> Unit, + onPlayWord: () -> Unit, +) { + val parts = buildList { + if (chapterName.isNotBlank()) add(chapterName) + add("$chapterNo:$verseNo") + add(stringResource(R.string.wordNo, word.wordIndex + 1)) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text( + text = parts.joinToString(" · "), + style = typography.labelLarge, + color = colorScheme.onSurfaceVariant.alpha(0.8f), + modifier = Modifier.weight(1f) + ) + + if (!word.isLastWordOfAyah) { + IconButton( + painterResource(R.drawable.ic_play), + contentDescription = stringResource(R.string.playWord), + onClick = onPlayWord, + tint = colorScheme.onSurfaceVariant.alpha(0.8f), + small = true + ) + } + + IconButton( + painterResource(R.drawable.icon_copy), + contentDescription = stringResource(R.string.strLabelCopy), + onClick = onCopyWord, + tint = colorScheme.onSurfaceVariant.alpha(0.8f), + small = true + ) + } +} + +@Composable +private fun ArabicWordCard( + word: AyahWordEntity, + hasPrev: Boolean, + hasNext: Boolean, + onPrev: () -> Unit, + onNext: () -> Unit, + textStyle: TextStyle, + wbwRow: WbwWordEntity?, +) { + val transliteration = wbwRow?.transliteration?.takeIf { !it.isNullOrBlank() } + val translation = wbwRow?.translation?.takeIf { !it.isNullOrBlank() } + + Box( + modifier = Modifier + .padding(horizontal = 12.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .background(colorScheme.surfaceVariant.alpha(0.4f)) + .padding(vertical = 24.dp, horizontal = 16.dp), + contentAlignment = Alignment.Center, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Rtl + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton( + painter = painterResource(R.drawable.dr_icon_chevron_left), + contentDescription = stringResource(R.string.previousWord), + onClick = onPrev, + enabled = hasPrev, + tint = colorScheme.onSurfaceVariant.alpha(if (hasPrev) 0.9f else 0.35f), + small = true, + ) + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.Center, + ) { + Text( + text = word.text, + style = textStyle.copy( + fontSize = 40.sp + ), + color = colorScheme.onSurface, + textAlign = TextAlign.Center, + ) + } + IconButton( + painter = painterResource(R.drawable.dr_icon_chevron_right), + contentDescription = stringResource(R.string.nextWord), + onClick = onNext, + enabled = hasNext, + tint = colorScheme.onSurfaceVariant.alpha(if (hasNext) 0.9f else 0.35f), + small = true, + ) + } + } + + if (transliteration != null) { + Text( + transliteration, + style = typography.bodyMedium.copy( + color = colorScheme.onSurface.alpha(0.65f) + ) + ) + } + + if (translation != null) { + Text( + translation, + style = typography.bodyMedium + ) + } + } + } +} + + +@Composable +private fun VerseWrapped( + bookmarksRepo: UserRepository, + verseUi: ReaderLayoutItem.VerseUI, + onWordClick: (AyahWordEntity) -> Unit +) { + val verse = verseUi.verse + + val isBookmarked by bookmarksRepo + .isBookmarkedFlow(verse.chapterNo, verse.verseNo..verse.verseNo) + .collectAsStateWithLifecycle(false) + + VerseView( + verseUi = verseUi, + isBookmarked = isBookmarked, + showDivider = false, + onWordClick = onWordClick + ) +} + +private suspend fun resolveWordTextForCopy( + repository: QuranRepository, + chapterNo: Int, + verseNo: Int, + word: AyahWordEntity, +): String { + val script = ReaderPreferences.getQuranScript() + + if (script == QuranScriptUtils.SCRIPT_UTHMANI) return word.text + + return withContext(Dispatchers.IO) { + val uthmaniWords = repository.getWordsForAyah( + chapterNo, + verseNo, + QuranScriptUtils.SCRIPT_UTHMANI, + ) + + uthmaniWords.find { it.wordIndex == word.wordIndex }?.text ?: word.text + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/utils/quran/QuranUtils.java b/app/src/main/java/com/quranapp/android/utils/quran/QuranUtils.java index e3fd95049..5ddd1b4d9 100644 --- a/app/src/main/java/com/quranapp/android/utils/quran/QuranUtils.java +++ b/app/src/main/java/com/quranapp/android/utils/quran/QuranUtils.java @@ -11,7 +11,7 @@ public static int getAyahId(int chapterNo, int verseNo) { return chapterNo * 1000 + verseNo; } - public static Pair getVerseNo(int ayahId) { + public static Pair getVerseNoFromAyahId(int ayahId) { int chapterNo = ayahId / 1000; int ayahNo = ayahId % 1000; diff --git a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt index 225b69283..7ae768c57 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt @@ -19,7 +19,6 @@ import com.quranapp.android.compose.components.reader.ReaderMode import com.quranapp.android.compose.components.reader.ReaderPreparedData import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.entities.ReadHistoryEntity -import com.quranapp.android.utils.Log import com.quranapp.android.utils.others.ShortcutUtils import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.quran.QuranUtils @@ -202,7 +201,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic val oldLayoutKey = mushafLayoutKey.value if (oldLayoutKey != newLayoutKey) { - Log.d("CLEARING ITEMS", mushafLayoutKey) + val pageInPreviousMushaf = _uiState.value.currentPageNo mushafPagesInFlight.clear() pageItems.clear() @@ -210,7 +209,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic pageRestoreOnMushafChangeJob?.cancel() pageRestoreOnMushafChangeJob = viewModelScope.launch { - restorePageOnMushafChange(oldLayoutKey) + restorePageOnMushafChange(oldLayoutKey, pageInPreviousMushaf) } } } @@ -357,7 +356,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic .filterIsInstance() .firstOrNull()?.words?.firstOrNull() if (firstWord != null) { - lastKnownVerse = QuranUtils.getVerseNo(firstWord.ayahId).let { + lastKnownVerse = QuranUtils.getVerseNoFromAyahId(firstWord.ayahId).let { ChapterVersePair(it.first, it.second) } return @@ -365,7 +364,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } viewModelScope.launch(Dispatchers.IO) { val ayahId = repository.getFirstAyahIdOnPage(pageNo) ?: return@launch - lastKnownVerse = QuranUtils.getVerseNo(ayahId).let { + lastKnownVerse = QuranUtils.getVerseNoFromAyahId(ayahId).let { ChapterVersePair(it.first, it.second) } } @@ -598,19 +597,22 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic * Resolves reading position after script/mushaf change: [lastKnownVerse] if valid, else first ayah * on the current page using the **previous** mushaf layout, then maps that verse to a page in the new mushaf. */ - private suspend fun restorePageOnMushafChange(oldLayoutKey: QuranScript) { + private suspend fun restorePageOnMushafChange( + oldLayoutKey: QuranScript, + pageInPreviousMushaf: Int?, + ) { val verseFromMemory = withContext(Dispatchers.Main) { lastKnownVerse?.takeIf { it.isValid } } val versePair = verseFromMemory ?: run { val oldMushafId = oldLayoutKey.scriptCode.toQuranMushafId(oldLayoutKey.variant) - val currentPage = _uiState.value.currentPageNo + val currentPage = pageInPreviousMushaf if (currentPage == null || currentPage <= 0 || oldMushafId <= 0) return val ayahId = repository.getFirstAyahIdOnPage(oldMushafId, currentPage) ?: return - val (c, v) = QuranUtils.getVerseNo(ayahId) + val (c, v) = QuranUtils.getVerseNoFromAyahId(ayahId) ChapterVersePair(c, v) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20d27f394..0eb2eaaef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -287,6 +287,7 @@ Select a commentary to read alongside verses. You can download and manage tafsirs in settings. Arabic Translation + Transliteration Favourites Remove from favourites Add a chapter to favourites to see here. @@ -378,4 +379,8 @@ Delete data? + Word %d + Play word + Previous word + Next word From 80002a0bab58678d89cc3c68e6d300b09d2a51e2 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Sun, 19 Apr 2026 01:06:39 +0530 Subject: [PATCH 04/16] Section Markers --- .../compose/components/reader/Mushaf.kt | 287 +++++++++++++----- .../compose/components/reader/ReaderLayout.kt | 52 +++- .../screens/reference/ReferenceScreen.kt | 4 +- .../compose/screens/settings/ScriptsScreen.kt | 3 +- .../StorageCleanupScriptsScreen.kt | 6 +- .../utils/preferences/DataStoreManager.kt | 7 + .../utils/preferences/ReaderPreferences.kt | 10 + .../utils/reader/ReaderItemsBuilder.kt | 213 ++++++++++++- .../android/viewModels/ReaderViewModel.kt | 64 ++-- app/src/main/res/values/strings.xml | 5 + 10 files changed, 539 insertions(+), 112 deletions(-) diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt index 21fedc49b..9108cd003 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt @@ -1,6 +1,5 @@ package com.quranapp.android.compose.components.reader -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -21,8 +20,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember @@ -32,11 +33,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection @@ -52,7 +56,6 @@ import com.quranapp.android.compose.components.reader.dialogs.WbwSheetData import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.entities.quran.AyahWordEntity -import com.quranapp.android.utils.Log import com.quranapp.android.utils.quran.QuranUtils import com.quranapp.android.utils.reader.MUSHAF_PAGE_HORIZONTAL_PADDING import com.quranapp.android.utils.reader.PageBuilderParams @@ -122,11 +125,47 @@ fun ReaderLayoutPageMode( initialPage = uiState.currentPageNo?.let { it - 1 } ?: 0, pageCount = { pageCount }, ) + + // After script/mushaf change, [pageItems] is cleared while the pager can still be on an old + // index; only realign when the layout key changes so we don't fight user swipes. + var previousMushafKey by remember { mutableStateOf(mushafLayoutKey) } + + LaunchedEffect(mushafLayoutKey, pageCount, uiState.currentPageNo) { + val layoutJustChanged = previousMushafKey != mushafLayoutKey + + if (!layoutJustChanged) return@LaunchedEffect + + if (pageCount <= 0) return@LaunchedEffect + + val p = uiState.currentPageNo ?: return@LaunchedEffect + + val idx = p.coerceIn(1, pageCount) - 1 + + if (pagerState.currentPage != idx) { + pagerState.scrollToPage(idx) + } + + previousMushafKey = mushafLayoutKey + } + val textMeasurer = rememberTextMeasurer(cacheSize = 2048) val colors = MaterialTheme.colorScheme val typography = MaterialTheme.typography val density = LocalDensity.current + val pageBuilderParams = remember(colors, typography, textMeasurer, density, contentWidth) { + PageBuilderParams( + context = context, + colors = colors, + type = typography, + textMeasurer = textMeasurer, + density = density, + contentWidthPx = with(density) { + (contentWidth - MUSHAF_PAGE_HORIZONTAL_PADDING * 2).roundToPx() + } + ) + } + LaunchedEffect(pagerState, pageCount, mushafLayoutKey) { snapshotFlow { listOf( @@ -139,21 +178,22 @@ fun ReaderLayoutPageMode( .collect { anchorPages -> if (pageCount > 0) { readerVm.fetchMushafPages( - context, anchorPages, pageCount, PageBuilderParams( - context = context, - colors = colors, - type = typography, - textMeasurer = textMeasurer, - density = density, - contentWidthPx = with(density) { - (contentWidth - MUSHAF_PAGE_HORIZONTAL_PADDING * 2).roundToPx() - } - ) + context, anchorPages, pageCount, pageBuilderParams ) } } } + // Pager anchors can lag [currentPageNo] while programmatic navigation is pending; always + // prefetch the page the ViewModel is trying to show. + LaunchedEffect(uiState.currentPageNo, pageCount, mushafLayoutKey, pageBuilderParams) { + val vmPage = uiState.currentPageNo ?: return@LaunchedEffect + + readerVm.fetchMushafPages( + context, listOf(vmPage), pageCount, pageBuilderParams + ) + } + val navigateToPage by readerVm.navigateToPage.collectAsStateWithLifecycle() LaunchedEffect(context, pagerState, pageCount, navigateToPage) { @@ -182,10 +222,19 @@ fun ReaderLayoutPageMode( LaunchedEffect(navigateToPage, pageCount) { val targetPage = navigateToPage ?: return@LaunchedEffect - if (targetPage in 1..pageCount) { - pagerState.scrollToPage(targetPage - 1) + + if (pageCount <= 0) return@LaunchedEffect + + val clamped = targetPage.coerceIn(1, pageCount) + + try { + pagerState.scrollToPage(clamped - 1) + + if (clamped != targetPage) { + readerVm.updateState { it.copy(currentPageNo = clamped) } + } + } finally { readerVm.consumePageNavigation() - Log.d("scrollToPage", targetPage) } } @@ -195,8 +244,6 @@ fun ReaderLayoutPageMode( val targetPage = readerVm.resolvePageNo(targetVerse.chapterNo, targetVerse.verseNo) ?: return@LaunchedEffect - Log.d("requestPageNavigation", targetPage) - readerVm.requestPageNavigation(targetPage) } @@ -264,23 +311,25 @@ fun ReaderLayoutPageMode( .fillMaxWidth() .fillMaxHeight() ) { page -> - PageModePage( - readerVm = readerVm, - pageNo = page + 1, - contentWidth, - ruledPageDecoration, - nestedScrollConnection, - onMushafWordClick = { word -> - val pair = QuranUtils.getVerseNoFromAyahId(word.ayahId) - - wbwSheetData = WbwSheetData( - chapterNo = pair.first, - verseNo = pair.second, - wordIndex = word.wordIndex - ) - }, - mushafWordTapEnabled = mushafWordTapEnabled, - ) + key(mushafLayoutKey, page) { + PageModePage( + readerVm = readerVm, + pageNo = page + 1, + contentWidth, + ruledPageDecoration, + nestedScrollConnection, + onMushafWordClick = { word -> + val pair = QuranUtils.getVerseNoFromAyahId(word.ayahId) + + wbwSheetData = WbwSheetData( + chapterNo = pair.first, + verseNo = pair.second, + wordIndex = word.wordIndex + ) + }, + mushafWordTapEnabled = mushafWordTapEnabled, + ) + } } } @@ -302,8 +351,11 @@ private fun PageModePage( onMushafWordClick: (AyahWordEntity) -> Unit, mushafWordTapEnabled: Boolean, ) { - val item = readerVm.pageItems[pageNo] + val i by remember(pageNo) { + derivedStateOf { readerVm.pageItems[pageNo] } + } + val item = i if (item == null) { return Loader(true) } @@ -462,43 +514,78 @@ private fun MushafLineText( if (textLine.centered) { Box(Modifier.fillMaxWidth()) { - Row( - modifier = Modifier.align(Alignment.Center), + MushafWordsRow( + words = words, + fittedStyle = fittedStyle, + playingWordKeys = playingWordKeys, horizontalArrangement = Arrangement.spacedBy( centeredGap, Alignment.CenterHorizontally ), - verticalAlignment = Alignment.CenterVertically, - ) { - for (word in words) { - Word( - word, - fittedStyle, - isHighlighted = (word.ayahId to word.wordIndex) in playingWordKeys, - mushafWordTapEnabled = mushafWordTapEnabled, - onClick = { - onMushafWordClick(word) - }, - ) - } - } + modifier = Modifier.align(Alignment.Center), + onMushafWordClick = onMushafWordClick, + mushafWordTapEnabled = mushafWordTapEnabled, + ) } } else { - Row( - modifier = Modifier.fillMaxWidth(), + MushafWordsRow( + words = words, + fittedStyle = fittedStyle, + playingWordKeys = playingWordKeys, horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - for (word in words) { - Word( - word, - fittedStyle, - isHighlighted = (word.ayahId to word.wordIndex) in playingWordKeys, - mushafWordTapEnabled = mushafWordTapEnabled, - onClick = { - onMushafWordClick(word) - }, + modifier = Modifier.fillMaxWidth(), + onMushafWordClick = onMushafWordClick, + mushafWordTapEnabled = mushafWordTapEnabled, + ) + } +} + +@Composable +private fun MushafWordsRow( + words: List, + fittedStyle: TextStyle, + playingWordKeys: Set>, + horizontalArrangement: Arrangement.Horizontal, + modifier: Modifier = Modifier, + onMushafWordClick: (AyahWordEntity) -> Unit, + mushafWordTapEnabled: Boolean, +) { + val wordRects = remember(words) { + mutableStateListOf().apply { repeat(words.size) { add(null) } } + } + + val highlightRects = mergedMushafHighlightRects(words, wordRects, playingWordKeys) + val highlightColor = colorScheme.primary.alpha(0.3f) + + Row( + modifier = modifier.drawBehind { + for (rect in highlightRects) { + drawRoundRect( + color = highlightColor, + topLeft = Offset(rect.left, rect.top), + size = Size(rect.width, rect.height), ) } + }, + horizontalArrangement = horizontalArrangement, + verticalAlignment = Alignment.CenterVertically, + ) { + words.forEachIndexed { index, word -> + Word( + word = word, + fittedStyle = fittedStyle, + mushafWordTapEnabled = mushafWordTapEnabled, + onClick = { onMushafWordClick(word) }, + modifier = Modifier.onGloballyPositioned { coordinates -> + val pos = coordinates.positionInParent() + val sz = coordinates.size + wordRects[index] = Rect( + pos.x, + pos.y, + pos.x + sz.width, + pos.y + sz.height, + ) + }, + ) } } } @@ -507,9 +594,9 @@ private fun MushafLineText( private fun Word( word: AyahWordEntity, fittedStyle: TextStyle, - isHighlighted: Boolean, onClick: () -> Unit, mushafWordTapEnabled: Boolean, + modifier: Modifier = Modifier, ) { Text( text = word.text, @@ -517,21 +604,67 @@ private fun Word( style = fittedStyle, maxLines = 1, softWrap = false, - modifier = Modifier - .background( - if (isHighlighted) colorScheme.primary.alpha(0.4f) - else Color.Transparent - ) - .then( - if (mushafWordTapEnabled) { - Modifier.clickable { onClick() } - } else { - Modifier - } - ) + modifier = modifier.then( + if (mushafWordTapEnabled) { + Modifier.clickable { onClick() } + } else { + Modifier + } + ) ) } +private fun mergedMushafHighlightRects( + words: List, + wordRects: List, + playingWordKeys: Set>, +): List { + if (words.isEmpty()) return emptyList() + + val result = mutableListOf() + var i = 0 + + while (i < words.size) { + val w = words[i] + val highlighted = (w.ayahId to w.wordIndex) in playingWordKeys + + if (!highlighted) { + i++ + continue + } + + var rect = wordRects.getOrNull(i) ?: run { + i++ + continue + } + + val ayahId = w.ayahId + + var j = i + 1 + + while (j < words.size) { + val w2 = words[j] + if ((w2.ayahId to w2.wordIndex) !in playingWordKeys || w2.ayahId != ayahId) { + break + } + val r2 = wordRects.getOrNull(j) ?: break + rect = Rect( + minOf(rect.left, r2.left), + minOf(rect.top, r2.top), + maxOf(rect.right, r2.right), + maxOf(rect.bottom, r2.bottom), + ) + j++ + } + + result.add(rect) + + i = j + } + + return result +} + private fun Modifier.mushafHorizontalRuleBelow( color: Color, strokeWidth: Dp, diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt index 12eee0eab..42fccab22 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt @@ -4,11 +4,18 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -17,6 +24,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -61,13 +69,19 @@ sealed class ReaderLayoutItem() { data class Bismillah(override val key: String) : ReaderLayoutItem() data class IsVotd(override val key: String) : ReaderLayoutItem() data class ChapterTitle(val chapterNo: Int, override val key: String) : ReaderLayoutItem() + data class VerseUI( val verse: VerseWithDetails, val parsedTranslationTexts: List> = emptyList(), val wbwByWordIndex: Map? = null, - val isLastInGroup: Boolean = false, + val showDivider: Boolean = true, override val key: String ) : ReaderLayoutItem() + + data class SectionMarker( + val text: String, + override val key: String, + ) : ReaderLayoutItem() } data class ReaderPreparedData( @@ -276,6 +290,7 @@ private fun TranslationRow( is ReaderLayoutItem.IsVotd -> IsVotd() is ReaderLayoutItem.ChapterInfo -> ChapterInfoCard(item.chapterNo) is ReaderLayoutItem.ChapterTitle -> ChapterTitle(item.chapterNo) + is ReaderLayoutItem.SectionMarker -> SectionMarkerRow(item) is ReaderLayoutItem.VerseUI -> { val isBookmarked = BookmarkKey( chapterNo = item.verse.chapterNo, @@ -286,8 +301,41 @@ private fun TranslationRow( VerseView( verseUi = item, isBookmarked = isBookmarked, - showDivider = !item.isLastInGroup, + showDivider = item.showDivider, ) } } } + +@Composable +private fun SectionMarkerRow(marker: ReaderLayoutItem.SectionMarker) { + if (marker.text.isEmpty()) return + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + HorizontalDivider( + modifier = Modifier + .weight(1f) + .widthIn(min = 100.dp), + color = MaterialTheme.colorScheme.outlineVariant, + ) + + Text( + text = marker.text, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 10.dp), + ) + + HorizontalDivider( + modifier = Modifier + .weight(1f) + .widthIn(min = 100.dp), + color = MaterialTheme.colorScheme.outlineVariant, + ) + } +} diff --git a/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt index fb743bf97..eee2ec77c 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/reference/ReferenceScreen.kt @@ -516,7 +516,7 @@ private fun ReferenceVerseViewWrapped( VerseView( verseUi = verseUi, isBookmarked = isBookmarked, - showDivider = !verseUi.isLastInGroup, + showDivider = verseUi.showDivider, ) } } @@ -622,7 +622,7 @@ private suspend fun buildReferenceRows( ReferenceRow.VerseRow( verseUi = v.copy( key = "ref-${seg.segmentIndex}-${v.key}", - isLastInGroup = i == verseUis.lastIndex, + showDivider = i != verseUis.lastIndex, ), quranTextStyle = textStyles[v.verse.pageNo], ), diff --git a/app/src/main/java/com/quranapp/android/compose/screens/settings/ScriptsScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/settings/ScriptsScreen.kt index bed307357..0f67a653b 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/settings/ScriptsScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/settings/ScriptsScreen.kt @@ -119,8 +119,7 @@ fun ScriptsScreen() { } scope.launch { - ReaderPreferences.setQuranScript(newScript) - ReaderPreferences.setQuranScriptVariant(newVariant) + ReaderPreferences.setQuranScriptWithVariant(newScript, newVariant) } } } diff --git a/app/src/main/java/com/quranapp/android/compose/screens/storageCleanup/StorageCleanupScriptsScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/storageCleanup/StorageCleanupScriptsScreen.kt index 09656ae59..877aa9a5c 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/storageCleanup/StorageCleanupScriptsScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/storageCleanup/StorageCleanupScriptsScreen.kt @@ -158,8 +158,10 @@ fun StorageCleanupScriptsScreen( toDelete?.let { row -> scope.launch(Dispatchers.IO) { if (ReaderPreferences.getQuranScript() == row.scriptKey) { - ReaderPreferences.setQuranScript(QuranScriptUtils.SCRIPT_DEFAULT) - ReaderPreferences.setQuranScriptVariant(null) + ReaderPreferences.setQuranScriptWithVariant( + QuranScriptUtils.SCRIPT_DEFAULT, + null, + ) } fileUtils.getScriptFile(row.scriptKey).delete() fileUtils.getKFQPCScriptFontDir(row.scriptKey).deleteRecursively() diff --git a/app/src/main/java/com/quranapp/android/compose/utils/preferences/DataStoreManager.kt b/app/src/main/java/com/quranapp/android/compose/utils/preferences/DataStoreManager.kt index 2f8d3acbb..d7a3aec98 100644 --- a/app/src/main/java/com/quranapp/android/compose/utils/preferences/DataStoreManager.kt +++ b/app/src/main/java/com/quranapp/android/compose/utils/preferences/DataStoreManager.kt @@ -3,6 +3,7 @@ package com.alfaazplus.sunnah.ui.utils.shared_preference import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore @@ -73,6 +74,12 @@ object DataStoreManager { } } + /** + * Single DataStore transaction — observers (e.g. [flowMultiple]) emit once with all keys updated. + */ + suspend fun edit(transform: suspend MutablePreferences.() -> Unit) { + appContext.dataStore.edit { it.transform() } + } suspend fun remove(prefKey: PrefKey) { remove(prefKey.key) diff --git a/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt b/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt index 92ba2a091..03fa2d009 100644 --- a/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt +++ b/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt @@ -265,6 +265,16 @@ object ReaderPreferences { DataStoreManager.write(KEY_SCRIPT, font ?: QuranScriptUtils.SCRIPT_DEFAULT) } + suspend fun setQuranScriptWithVariant( + font: String?, + variant: QuranScriptVariant?, + ) { + DataStoreManager.edit { + this[KEY_SCRIPT.key] = font ?: QuranScriptUtils.SCRIPT_DEFAULT + this[KEY_SCRIPT_VARIANT.key] = variant?.value ?: "" + } + } + fun quranScriptFlow(): Flow { return DataStoreManager.flow(KEY_SCRIPT) .mapLatest { diff --git a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt index 84edbb71e..00e8a174f 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt @@ -1,18 +1,19 @@ package com.quranapp.android.utils.reader import android.content.Context -import androidx.compose.ui.text.ParagraphStyle import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle import com.alfaazplus.sunnah.ui.theme.fontUrdu +import com.quranapp.android.R import com.quranapp.android.compose.components.reader.QuranPageItem import com.quranapp.android.compose.components.reader.QuranPageLineItem import com.quranapp.android.compose.components.reader.ReaderLayoutItem import com.quranapp.android.compose.components.reader.ReaderPreparedData import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.db.ChapterVerseBatch import com.quranapp.android.db.entities.quran.AyahWordEntity import com.quranapp.android.db.entities.quran.MushafLineType import com.quranapp.android.db.entities.quran.MushafMapEntity @@ -21,6 +22,13 @@ import com.quranapp.android.repository.QuranRepository import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.reader.factory.QuranTranslationFactory +private data class SectionSnapshot( + val page: Int, + val ruku: Int, + val rub: Int, + val manzil: Int, +) + object ReaderItemsBuilder { suspend fun buildVersesForTranslationMode( context: Context, @@ -47,7 +55,7 @@ object ReaderItemsBuilder { } translationFactory.use { - buildVerses( + buildReaderVerses( params, out, textStyles, @@ -121,7 +129,7 @@ object ReaderItemsBuilder { } } - buildVerses( + buildReaderVerses( params, out, textStyles, it, quranRepository, chapterNo, verseRange.first, verseRange.last, ) @@ -131,7 +139,7 @@ object ReaderItemsBuilder { return ReaderPreparedData(out, textStyles) } - private suspend fun buildVerses( + private suspend fun buildReaderVerses( params: TextBuilderParams, out: ArrayList, textStyles: MutableMap, @@ -146,7 +154,9 @@ object ReaderItemsBuilder { val wbwId = ReaderPreferences.getWbwId() val scriptCode = ReaderPreferences.getQuranScript() - val batch = quranRepository.loadChapterVerseBatch(chapterNo, fromVerse, toVerse, scriptCode) + val chapterVerseCount = quranRepository.getChapterVerseCount(chapterNo) + val batchHi = minOf(toVerse + 1, chapterVerseCount) + val batch = quranRepository.loadChapterVerseBatch(chapterNo, fromVerse, batchHi, scriptCode) ?: return val surah = batch.surah @@ -203,16 +213,27 @@ object ReaderItemsBuilder { else quranRepository.getWbwWordsForAyahs(wbwId, ids) } else emptyMap() + var prevSection: SectionSnapshot? = null + for (verseNo in fromVerse..toVerse) { val translations = translationsByVerseIndex.getOrElse(verseNo - fromVerse) { emptyList() } val ayah = batch.ayahByVerseNo[verseNo] ?: continue val words = batch.wordsByVerseNo[verseNo] ?: emptyList() + val pageNo = batch.pageByVerseNo[verseNo] ?: -1 + val cur = SectionSnapshot( + page = pageNo, + ruku = ayah.rukuNo, + rub = ayah.rubNo, + manzil = ayah.manzilNo, + ) + + out.addSectionMarker(params.context, chapterNo, verseNo, cur, prevSection) + prevSection = cur if (words.isEmpty()) continue - val pageNo = batch.pageByVerseNo[verseNo] ?: -1 ensureQuranTextStyleForPage(pageNo) val verse = VerseWithDetails( @@ -272,11 +293,21 @@ object ReaderItemsBuilder { verse = verse, parsedTranslationTexts = parsedTranslationTexts, wbwByWordIndex = wbwByAyah[verse.id]?.takeIf { it.isNotEmpty() }, - isLastInGroup = verseNo == toVerse, + showDivider = verseNo != toVerse, key = "verse-$chapterNo:${verse.verseNo}${params.toKey()}" ) ) } + + out.addSectionMarkerAtRangeEnd( + params.context, + quranRepository, + scriptCode, + chapterNo = chapterNo, + toVerse = toVerse, + verseCount = chapterVerseCount, + batch = batch, + ) } suspend fun buildQuickReferenceItems( @@ -401,7 +432,7 @@ object ReaderItemsBuilder { verse = verse, parsedTranslationTexts = parsedTranslationTexts, wbwByWordIndex = wbwByAyah[verse.id]?.takeIf { it.isNotEmpty() }, - isLastInGroup = idx == verseNos.lastIndex, + showDivider = idx != verseNos.lastIndex, key = "qref-$chapterNo:$verseNo${params.toKey()}" ) ) @@ -516,4 +547,170 @@ object ReaderItemsBuilder { } } } + + private fun ArrayList.addSectionMarker( + context: Context, + chapterNo: Int, + verseNo: Int, + cur: SectionSnapshot, + prev: SectionSnapshot?, + ) { + val pageEnded = prev != null && cur.page > 0 && cur.page != prev.page + val rukuEnded = prev != null && cur.ruku > 0 && cur.ruku != prev.ruku + val rubEnded = prev != null && cur.rub > 0 && cur.rub != prev.rub + val manzilEnded = prev != null && cur.manzil > 0 && cur.manzil != prev.manzil + + if (!pageEnded && !rukuEnded && !rubEnded && !manzilEnded) { + return + } + + val prevSnap = checkNotNull(prev) + + val pageNo = prevSnap.page.takeIf { pageEnded } + val rukuNo = prevSnap.ruku.takeIf { rukuEnded } + val rubNo = prevSnap.rub.takeIf { rubEnded } + val manzilNo = prevSnap.manzil.takeIf { manzilEnded } + + val text = context.formatSectionMarkerLabel(pageNo, rukuNo, rubNo, manzilNo) + if (text.isEmpty()) return + + add( + ReaderLayoutItem.SectionMarker( + text = text, + key = buildString { + append("section-$chapterNo:after:${verseNo - 1}") + if (pageEnded) append("-p${prevSnap.page}") + if (rukuEnded) append("-r${prevSnap.ruku}") + if (rubEnded) append("-rb${prevSnap.rub}") + if (manzilEnded) append("-mz${prevSnap.manzil}") + }, + ) + ) + + clearDividerBeforeMarker(verseNo = verseNo) + } + + private suspend fun ArrayList.addSectionMarkerAtRangeEnd( + context: Context, + quranRepository: QuranRepository, + scriptCode: String, + chapterNo: Int, + toVerse: Int, + verseCount: Int, + batch: ChapterVerseBatch, + ) { + val lastAyah = batch.ayahByVerseNo[toVerse] ?: return + val lastPage = batch.pageByVerseNo[toVerse] ?: -1 + val lastRuku = lastAyah.rukuNo + val lastRub = lastAyah.rubNo + val lastManzil = lastAyah.manzilNo + val isLastVerseOfChapter = toVerse == verseCount + + val nextAyahInChapter = batch.ayahByVerseNo[toVerse + 1] + ?: quranRepository.getAyah(chapterNo, toVerse + 1).takeIf { !isLastVerseOfChapter } + + val rukuEnded = isLastVerseOfChapter || + (nextAyahInChapter != null && nextAyahInChapter.rukuNo != lastRuku) + + val nextAyahAfterRange = when { + !isLastVerseOfChapter -> + batch.ayahByVerseNo[toVerse + 1] + ?: quranRepository.getAyah(chapterNo, toVerse + 1) + + QuranMeta.isChapterValid(chapterNo + 1) -> + quranRepository.getAyah(chapterNo + 1, 1) + + else -> null + } + + val nextPage: Int? = when { + !isLastVerseOfChapter -> { + val p = batch.pageByVerseNo[toVerse + 1] + if (p != null && p > 0) p + else quranRepository.getPageForVerse(chapterNo, toVerse + 1, scriptCode) + } + + QuranMeta.isChapterValid(chapterNo + 1) -> + quranRepository.getPageForVerse(chapterNo + 1, 1, scriptCode) + + else -> null + } + + val pageEnded = lastPage > 0 && + nextPage != null && + nextPage > 0 && + lastPage != nextPage + + val nextRub = nextAyahAfterRange?.rubNo + val nextManzil = nextAyahAfterRange?.manzilNo + val rubEnded = lastRub > 0 && nextRub != null && nextRub > 0 && lastRub != nextRub + val manzilEnded = + lastManzil > 0 && nextManzil != null && nextManzil > 0 && lastManzil != nextManzil + + val pageForMarker = lastPage.takeIf { pageEnded } + val rukuForMarker = lastRuku.takeIf { rukuEnded && lastRuku > 0 } + val rubForMarker = lastRub.takeIf { rubEnded && lastRub > 0 } + val manzilForMarker = lastManzil.takeIf { manzilEnded && lastManzil > 0 } + + if (pageForMarker == null && rukuForMarker == null && rubForMarker == null && + manzilForMarker == null + ) { + return + } + + val text = context.formatSectionMarkerLabel( + pageForMarker, + rukuForMarker, + rubForMarker, + manzilForMarker, + ) + if (text.isEmpty()) return + + add( + ReaderLayoutItem.SectionMarker( + text = text, + key = buildString { + append("section-$chapterNo:after:$toVerse-end") + pageForMarker?.let { append("-p$it") } + rukuForMarker?.let { append("-r$it") } + rubForMarker?.let { append("-rb$it") } + manzilForMarker?.let { append("-mz$it") } + }, + ) + ) + + clearDividerBeforeMarker(verseNo = toVerse + 1) + } + + private fun ArrayList.clearDividerBeforeMarker(verseNo: Int) { + val clearFrom = verseNo - 1 + if (clearFrom < 1) return + for (i in lastIndex downTo 0) { + val item = get(i) + if (item is ReaderLayoutItem.VerseUI && item.verse.verseNo == clearFrom) { + if (item.showDivider) { + set(i, item.copy(showDivider = false)) + } + break + } + } + } + + private fun Context.formatSectionMarkerLabel( + pageNo: Int?, + rukuNo: Int?, + rubNo: Int?, + manzilNo: Int?, + ): String { + val parts = buildList { + pageNo?.takeIf { it > 0 }?.let { add(getString(R.string.endOfPageNo, it)) } + rukuNo?.takeIf { it > 0 }?.let { add(getString(R.string.endOfRukuNo, it)) } + rubNo?.takeIf { it > 0 }?.let { add(getString(R.string.endOfRubNo, it)) } + manzilNo?.takeIf { it > 0 }?.let { add(getString(R.string.endOfManzilNo, it)) } + } + + return parts.chunked(2).joinToString("\n") { chunk -> + chunk.joinToString(" · ") + } + } } diff --git a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt index 7ae768c57..0461d10ae 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt @@ -19,6 +19,7 @@ import com.quranapp.android.compose.components.reader.ReaderMode import com.quranapp.android.compose.components.reader.ReaderPreparedData import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.entities.ReadHistoryEntity +import com.quranapp.android.utils.Log import com.quranapp.android.utils.others.ShortcutUtils import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.quran.QuranUtils @@ -205,11 +206,23 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic mushafPagesInFlight.clear() pageItems.clear() - mushafLayoutKey.value = newLayoutKey pageRestoreOnMushafChangeJob?.cancel() + + val previousKey = oldLayoutKey + pageRestoreOnMushafChangeJob = viewModelScope.launch { - restorePageOnMushafChange(oldLayoutKey, pageInPreviousMushaf) + try { + restorePageOnMushafChange(previousKey, pageInPreviousMushaf) + } finally { + // Apply new layout only after [currentPageNo] / navigation target match the + // new mushaf; otherwise the pager keeps the old index while [pageItems] is + // empty → visible slot never gets rebuilt. + mushafLayoutKey.value = QuranScript( + ReaderPreferences.getQuranScript(), + ReaderPreferences.getQuranScriptVariant(), + ) + } } } } @@ -277,8 +290,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic ReaderPreferences.setReaderMode(ReaderMode.Reading) if (data.mushafCode != null) { - ReaderPreferences.setQuranScript(data.mushafCode) - ReaderPreferences.setQuranScriptVariant(data.mushafVariant) + ReaderPreferences.setQuranScriptWithVariant(data.mushafCode, data.mushafVariant) // Keep in sync with DataStore so [observeChanges] does not treat this as a layout // change and run [restorePageOnMushafChange] (which can override the intended page). @@ -551,8 +563,10 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic anchorPages: Collection, totalPages: Int, params: PageBuilderParams - ) = withContext(Dispatchers.IO) { - if (totalPages <= 0) return@withContext + ) { + Log.d("BUILDING", anchorPages) + + if (totalPages <= 0) return val targets = linkedSetOf() for (anchorPage in anchorPages) { @@ -566,22 +580,26 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } } - if (targets.isEmpty()) return@withContext + if (targets.isEmpty()) return - val missing = targets.filter { page -> - !pageItems.containsKey(page) && mushafPagesInFlight.add(page) + val missing = withContext(Dispatchers.Main) { + targets.filter { page -> + !pageItems.containsKey(page) && mushafPagesInFlight.add(page) + } } - if (missing.isEmpty()) return@withContext + if (missing.isEmpty()) return try { - fontResolver.prefetch(ReaderPreferences.getQuranScript(), missing) - - val built = ReaderItemsBuilder.buildMushafPages( - repository, - fontResolver, - missing, - params - ) + val built = withContext(Dispatchers.IO) { + fontResolver.prefetch(ReaderPreferences.getQuranScript(), missing) + + ReaderItemsBuilder.buildMushafPages( + repository, + fontResolver, + missing, + params + ) + } withContext(Dispatchers.Main) { for (page in missing) { @@ -589,7 +607,9 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } } } finally { - mushafPagesInFlight.removeAll(missing.toSet()) + withContext(Dispatchers.Main) { + mushafPagesInFlight.removeAll(missing.toSet()) + } } } @@ -605,6 +625,8 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic lastKnownVerse?.takeIf { it.isValid } } + Log.d("TRYING RESTORE", verseFromMemory) + val versePair = verseFromMemory ?: run { val oldMushafId = oldLayoutKey.scriptCode.toQuranMushafId(oldLayoutKey.variant) val currentPage = pageInPreviousMushaf @@ -617,10 +639,14 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic ChapterVersePair(c, v) } + Log.d("TRYING RESTORE", versePair) + val newPage = withContext(Dispatchers.IO) { repository.getPageForVerse(versePair.chapterNo, versePair.verseNo) } ?: return + Log.d("TRYING RESTORE", newPage) + withContext(Dispatchers.Main) { lastKnownVerse = versePair _uiState.update { it.copy(currentPageNo = newPage) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0eb2eaaef..6cb4ba01a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,7 +39,12 @@ Next tafsir Surah %s Page %d + Ruku %d Juz %d + End of Page %d + End of Ruku %d + End of Rub %d + End of Manzil %d Verse %d Verses %1$d–%2$d Verse : %d From 3f27be3df18e5c181f1e3d95a8042960680568f6 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Sun, 19 Apr 2026 16:27:19 +0530 Subject: [PATCH 05/16] Translation Reading Mode --- .../compose/components/reader/ReaderLayout.kt | 11 +- .../components/reader/TranslationReader.kt | 518 ++++++++++++++++++ .../reader/navigator/ReaderAppBar.kt | 111 +++- .../reader/navigator/ReaderNavigator.kt | 8 +- .../screens/settings/SettingsMainScreen.kt | 2 +- .../utils/preferences/ReaderPreferences.kt | 13 + .../android/repository/QuranRepository.kt | 27 + .../utils/reader/ReaderItemsBuilder.kt | 344 +++++++++--- .../utils/reader/ReaderLaunchParams.kt | 15 + .../android/utils/reader/TextDecorator.kt | 13 +- .../android/utils/reader/TranslUtils.java | 4 + .../utils/reader/factory/ReaderFactory.kt | 4 +- .../android/viewModels/ReaderViewModel.kt | 175 +++++- app/src/main/res/drawable/ic_verse_end.xml | 12 + 14 files changed, 1147 insertions(+), 110 deletions(-) create mode 100644 app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt create mode 100644 app/src/main/res/drawable/ic_verse_end.xml diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt index 42fccab22..d2dc75064 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -134,7 +135,14 @@ fun ReaderLayout( } } - ReaderMode.Translation -> {} + ReaderMode.Translation -> { + ReaderLayoutTranslationPageMode( + readerVm, + nestedScrollConnection, + onSyncStateChanged, + ) + } + else -> ReaderLayoutVerseMode( readerVm, uiState, @@ -329,6 +337,7 @@ private fun SectionMarkerRow(marker: ReaderLayoutItem.SectionMarker) { style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(horizontal = 10.dp), + textAlign = TextAlign.Center ) HorizontalDivider( diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt new file mode 100644 index 000000000..6e5a009e0 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt @@ -0,0 +1,518 @@ +package com.quranapp.android.compose.components.reader + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDirection +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.quranapp.android.R +import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.utils.reader.LocalVerseActions +import com.quranapp.android.utils.reader.TranslUtils +import com.quranapp.android.utils.reader.TranslationPageBuilderParams +import com.quranapp.android.viewModels.ReaderViewModel +import kotlinx.coroutines.flow.distinctUntilChanged + +data class TranslationPageItem( + val pageNo: Int, + val juzNo: Int, + val hizbNo: Int, + val chapterNames: String, + val translationSlug: String, + val annotatedText: AnnotatedString, + val verses: List, +) + +data class TranslationPageVerse( + val chapterNo: Int, + val verseNo: Int, + /** Index within annotatedText (inclusive). */ + val rangeStart: Int, + /** Index within annotatedText (Exclusive). */ + val rangeEnd: Int, +) + + +@Composable +fun ReaderLayoutTranslationPageMode( + readerVm: ReaderViewModel, + nestedScrollConnection: NestedScrollConnection, + onSyncStateChanged: (Boolean) -> Unit = {}, +) { + val uiState by readerVm.uiState.collectAsStateWithLifecycle() + val mushafLayoutKey by readerVm.mushafLayoutKey + + val pageCount by produceState(0, mushafLayoutKey) { + value = readerVm.mushafPageCount(mushafLayoutKey.toMushafId()) + } + + val context = LocalContext.current + val colors = MaterialTheme.colorScheme + val typography = MaterialTheme.typography + val verseActions = LocalVerseActions.current + val translSizeMult = ReaderPreferences.observeTranlationTextSizeMultiplier() + val buildParams = remember(context, colors, typography, verseActions, translSizeMult) { + TranslationPageBuilderParams( + context = context, + colors = colors, + type = typography, + verseActions = verseActions, + translationSizeMultiplier = translSizeMult, + ) + } + + LaunchedEffect(buildParams) { + readerVm.clearTranslationPageCache() + } + + val initialPageIndex = + uiState.currentPageNo?.minus(1)?.coerceAtLeast(0) ?: 0 + val listState = rememberLazyListState(initialFirstVisibleItemIndex = initialPageIndex) + + var previousMushafKey by remember { mutableStateOf(mushafLayoutKey) } + + LaunchedEffect(mushafLayoutKey, pageCount, uiState.currentPageNo) { + val layoutJustChanged = previousMushafKey != mushafLayoutKey + + if (!layoutJustChanged) return@LaunchedEffect + + if (pageCount <= 0) return@LaunchedEffect + + val p = uiState.currentPageNo ?: return@LaunchedEffect + + val idx = p.coerceIn(1, pageCount) - 1 + + if (listState.firstVisibleItemIndex != idx) { + listState.scrollToItem(idx) + } + + previousMushafKey = mushafLayoutKey + } + + LaunchedEffect(listState, pageCount, mushafLayoutKey, buildParams) { + snapshotFlow { + val visible = listState.layoutInfo.visibleItemsInfo + + if (visible.isEmpty()) { + listOf(listState.firstVisibleItemIndex + 1) + } else { + visible.map { it.index + 1 } + } + } + .distinctUntilChanged() + .collect { anchorPages -> + if (pageCount > 0) { + readerVm.fetchTranslationPages( + context, anchorPages, pageCount, buildParams + ) + } + } + } + + LaunchedEffect(uiState.currentPageNo, pageCount, mushafLayoutKey, buildParams) { + val vmPage = uiState.currentPageNo ?: return@LaunchedEffect + + readerVm.fetchTranslationPages( + context, listOf(vmPage), pageCount, buildParams + ) + } + + val navigateToPage by readerVm.navigateToPage.collectAsStateWithLifecycle() + + LaunchedEffect(context, listState, pageCount, navigateToPage) { + snapshotFlow { listState.firstVisibleItemIndex } + .distinctUntilChanged() + .collect { currentIndex -> + if (navigateToPage != null) return@collect + if (pageCount <= 0) return@collect + + val currentPageNo = (currentIndex + 1).coerceIn(1, pageCount) + + readerVm.updateState { + it.copy(currentPageNo = currentPageNo) + } + + readerVm.updateLastKnownVerseFromTranslationPage(currentPageNo) + } + } + + LaunchedEffect(navigateToPage, pageCount) { + val targetPage = navigateToPage ?: return@LaunchedEffect + + if (pageCount <= 0) return@LaunchedEffect + + val clamped = targetPage.coerceIn(1, pageCount) + + try { + listState.scrollToItem(clamped - 1) + + if (clamped != targetPage) { + readerVm.updateState { it.copy(currentPageNo = clamped) } + } + } finally { + readerVm.consumePageNavigation() + } + } + + val navigateToVerse by readerVm.navigateToVerse.collectAsStateWithLifecycle() + + LaunchedEffect(navigateToVerse, pageCount) { + val targetVerse = navigateToVerse ?: return@LaunchedEffect + + val targetPage = readerVm.resolvePageNo(targetVerse.chapterNo, targetVerse.verseNo) + ?: return@LaunchedEffect + + readerVm.requestPageNavigation(targetPage) + } + + val playerState = LocalRecitation.current + val isPlayingMushaf = playerState.isAnyPlaying + val playingVerseMushaf = playerState.playingVerse + var playerVerseSync by readerVm.playerVerseSync + + LaunchedEffect(playerVerseSync, isPlayingMushaf, playingVerseMushaf, pageCount) { + if (!playerVerseSync || !isPlayingMushaf || !playingVerseMushaf.isValid || pageCount <= 0) { + return@LaunchedEffect + } + + val targetPage = + readerVm.resolvePageNo(playingVerseMushaf.chapterNo, playingVerseMushaf.verseNo) + ?: return@LaunchedEffect + if (targetPage !in 1..pageCount) return@LaunchedEffect + + snapshotFlow { listState.firstVisibleItemIndex } + .distinctUntilChanged() + .collect { settledIdx -> + val currentPage = settledIdx + 1 + if (currentPage != targetPage) { + readerVm.requestPageNavigation(targetPage) + } + } + } + + LaunchedEffect(playingVerseMushaf, pageCount) { + if (!playingVerseMushaf.isValid || pageCount <= 0) { + onSyncStateChanged(false) + return@LaunchedEffect + } + + val expectedPage = + readerVm.resolvePageNo(playingVerseMushaf.chapterNo, playingVerseMushaf.verseNo) + + if (expectedPage == null || expectedPage !in 1..pageCount) { + onSyncStateChanged(false) + return@LaunchedEffect + } + + snapshotFlow { listState.firstVisibleItemIndex + 1 } + .distinctUntilChanged() + .collect { current -> + onSyncStateChanged(current == expectedPage) + } + } + + SelectionContainer { + LazyColumn( + state = listState, + modifier = Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection), + contentPadding = PaddingValues(top = 16.dp, bottom = 240.dp), + ) { + items( + count = pageCount, + key = { index -> mushafLayoutKey to index }, + ) { pageIndex -> + key(mushafLayoutKey, pageIndex) { + if (pageIndex > 0) { + Spacer(Modifier.height(12.dp)) + } + + TranslationModePage( + readerVm = readerVm, + pageNo = pageIndex + 1, + ) + } + } + } + } +} + +@Composable +private fun TranslationModePage( + readerVm: ReaderViewModel, + pageNo: Int, +) { + val i by remember(pageNo) { + derivedStateOf { readerVm.translationPageItems[pageNo] } + } + + val item = i + if (item == null) { + TranslationPageLoadingSkeleton() + return + } + + val playerState = LocalRecitation.current + val isPlaying = playerState.isAnyPlaying + val playingVerse = playerState.playingVerse + val isRtl = TranslUtils.isRtl(item.translationSlug) + val colors = colorScheme + + val displayText = remember(item.annotatedText, item.verses, isPlaying, playingVerse, colors) { + buildAnnotatedString { + append(item.annotatedText) + + if (!isPlaying || !playingVerse.isValid) return@buildAnnotatedString + + val v = item.verses.find { + it.chapterNo == playingVerse.chapterNo && it.verseNo == playingVerse.verseNo + } ?: return@buildAnnotatedString + + addStyle( + SpanStyle(background = colors.primary.alpha(0.2f)), + v.rangeStart, + v.rangeEnd, + ) + } + } + + val textDirection = if (isRtl) TextDirection.Rtl else TextDirection.Ltr + + CompositionLocalProvider( + LocalLayoutDirection provides if (isRtl) LayoutDirection.Rtl else { + LayoutDirection.Ltr + } + ) { + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp), + shape = RoundedCornerShape(4.dp), + color = colorScheme.surfaceContainer, + border = BorderStroke( + 1.dp, + colorScheme.outlineVariant.copy(alpha = 0.45f), + ), + ) { + Column() { + TranslationBookPageHeader( + chapterNames = item.chapterNames, + pageNo = item.pageNo, + juzNo = item.juzNo, + ) + + HorizontalDivider( + color = colorScheme.outlineVariant.copy(alpha = 0.55f), + ) + + Text( + text = displayText, + modifier = Modifier + .fillMaxWidth() + .padding(14.dp), + style = TextStyle(textDirection = textDirection), + ) + } + } + } +} + +@Composable +private fun TranslationBookPageHeader( + chapterNames: String, + pageNo: Int, + juzNo: Int, +) { + val typography = MaterialTheme.typography + val scheme = colorScheme + val juzLabel = + if (juzNo > 0) stringResource(R.string.strLabelJuzNo, juzNo) else "" + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.CenterStart, + ) { + Text( + text = chapterNames.ifBlank { "—" }, + style = typography.bodyMedium, + color = scheme.onSurface.alpha(0.75f), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start, + modifier = Modifier + .basicMarquee( + initialDelayMillis = 900, + repeatDelayMillis = 1_200, + ), + ) + } + + Text( + text = stringResource(R.string.strLabelPageNo, pageNo), + style = typography.labelMedium, + color = scheme.onBackground.alpha(0.75f), + modifier = Modifier + .padding(horizontal = 12.dp) + .background(colorScheme.background, shapes.extraLarge) + .padding(horizontal = 12.dp, vertical = 6.dp), + maxLines = 1, + ) + + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.CenterEnd, + ) { + Text( + text = juzLabel.ifBlank { "—" }, + style = typography.bodyMedium, + color = scheme.onSurface.alpha(0.75f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.End, + ) + } + } +} + + +@Composable +private fun TranslationPageLoadingSkeleton() { + val scheme = MaterialTheme.colorScheme + val transition = rememberInfiniteTransition(label = "translation_page_sk") + val pulse by transition.animateFloat( + initialValue = 0.1f, + targetValue = 0.2f, + animationSpec = infiniteRepeatable( + animation = tween(950, easing = FastOutSlowInEasing), + repeatMode = RepeatMode.Reverse, + ), + label = "pulse", + ) + + val barColor = scheme.onSurface.copy(alpha = pulse) + val lineWidths = listOf(1f, 0.97f, 0.92f, 1f, 0.85f, 0.94f, 0.78f, 1f, 0.88f, 0.72f, 0.58f) + + Surface( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 380.dp) + .padding(horizontal = 12.dp), + shape = RoundedCornerShape(4.dp), + color = scheme.surfaceContainer, + border = BorderStroke( + 1.dp, + scheme.outlineVariant.copy(alpha = 0.45f), + ), + ) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .weight(1f) + .height(24.dp) + .background(barColor, RoundedCornerShape(4.dp)), + ) + + Box( + Modifier + .padding(horizontal = 24.dp) + .width(88.dp) + .height(24.dp) + .background(barColor, shapes.extraLarge), + ) + + Box( + Modifier + .weight(1f) + .height(24.dp) + .background(barColor, RoundedCornerShape(4.dp)), + ) + } + + HorizontalDivider(color = scheme.outlineVariant.copy(alpha = 0.4f)) + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(14.dp), + ) { + for (w in lineWidths) { + Box( + Modifier + .fillMaxWidth(w) + .height(32.dp) + .padding(vertical = 5.dp) + .background(barColor, shapes.large), + ) + } + } + } + } +} + diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderAppBar.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderAppBar.kt index 760d1c6d9..7d6046832 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderAppBar.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderAppBar.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -26,6 +27,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Surface @@ -66,6 +68,7 @@ import com.quranapp.android.compose.components.reader.dialogs.AutoScrollSheet import com.quranapp.android.compose.navigation.SettingRoutes import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.utils.reader.factory.QuranTranslationFactory import com.quranapp.android.utils.reader.toQuranMushafId import com.quranapp.android.utils.univ.Keys import com.quranapp.android.viewModels.ReaderUiState @@ -165,7 +168,11 @@ fun ReaderAppBar( } } - ReaderMode.Translation -> {} + ReaderMode.Translation -> { + StickyHeaderModeTranslation(readerVm, uiState) { + showNavigatorSheet = true + } + } else -> { StickyHeaderModeVbV(readerVm, uiState) { @@ -211,6 +218,7 @@ private fun ModeTabs( listOf( ReaderMode.VerseByVerse, ReaderMode.Reading, + ReaderMode.Translation, ).forEach { mode -> val isSelected = mode == readerMode val label = stringResource( @@ -477,13 +485,112 @@ private fun StickyHeaderModeMushaf( Icon( painterResource(R.drawable.dr_icon_chevron_down), - contentDescription = null + contentDescription = null, + tint = colorScheme.primary, ) } } } } +@Composable +private fun StickyHeaderModeTranslation( + readerVm: ReaderViewModel, + uiState: ReaderUiState, + onNavigatorRequest: () -> Unit +) { + val context = LocalContext.current + val currentPageNo = uiState.currentPageNo + val translationSlug = ReaderPreferences.observePrimaryTranslationSlug() + val bookName by produceState("", translationSlug) { + value = QuranTranslationFactory(context).use { + it.getTranslationBookInfo(translationSlug).displayName + } + } + + Row( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Row( + Modifier + .background(colorScheme.background, shapes.extraLarge) + .clip(shapes.extraLarge) + .clickable( + onClick = { + openReaderSetting( + context, + SettingRoutes.TRANSLATIONS + ) + } + ) + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.dr_icon_translations), + contentDescription = stringResource(R.string.strLabelSelectTranslations), + tint = colorScheme.onSurface.alpha(0.75f), + modifier = Modifier + .padding(end = 8.dp) + .size(18.dp) + ) + + Text( + bookName, + style = typography.labelLarge, + color = colorScheme.onSurface.alpha(0.75f), + maxLines = 1, + modifier = Modifier + .basicMarquee( + initialDelayMillis = 900, + repeatDelayMillis = 1_200, + ) + ) + + Icon( + painterResource(R.drawable.dr_icon_chevron_right), + contentDescription = null, + tint = colorScheme.onSurface.alpha(0.75f), + modifier = Modifier.size(18.dp) + ) + } + + Spacer( + Modifier.weight(1f) + ) + + TextButton( + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 0.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = colorScheme.onSurface + ), + onClick = onNavigatorRequest, + ) { + Column( + horizontalAlignment = Alignment.End, + ) { + if (currentPageNo != null) { + Text( + stringResource(R.string.strLabelPageNo, currentPageNo), + style = typography.titleSmall, + color = colorScheme.primary, + ) + } + } + + Icon( + painterResource(R.drawable.dr_icon_chevron_down), + contentDescription = null, + tint = colorScheme.primary, + ) + } + } +} + fun openReaderSetting(context: Context, destination: String?) { context.startActivity( Intent(context, ActivitySettings::class.java).apply { diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderNavigator.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderNavigator.kt index ba7c3bf1f..263b1cbfe 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderNavigator.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderNavigator.kt @@ -54,7 +54,7 @@ fun ReaderNavigator( fun navigateChapter(chapterNo: Int) { scope.launch { when (readerMode) { - ReaderMode.Reading -> { + ReaderMode.Reading, ReaderMode.Translation -> { val page = readerVm.resolvePageNo(chapterNo, 1) if (page != null) readerVm.requestPageNavigation(page) } @@ -71,7 +71,7 @@ fun ReaderNavigator( fun navigateVerse(chapterNo: Int, verseNo: Int) { scope.launch { when (readerMode) { - ReaderMode.Reading -> { + ReaderMode.Reading, ReaderMode.Translation -> { val page = readerVm.resolvePageNo(chapterNo, verseNo) if (page != null) readerVm.requestPageNavigation(page) } @@ -105,7 +105,7 @@ fun ReaderNavigator( fun navigateJuz(juzNo: Int) { scope.launch { when (readerMode) { - ReaderMode.Reading -> { + ReaderMode.Reading, ReaderMode.Translation -> { val page = withContext(Dispatchers.IO) { readerVm.repository.getFirstPageOfJuz(juzNo) } @@ -124,7 +124,7 @@ fun ReaderNavigator( fun navigateHizb(hizbNo: Int) { scope.launch { when (readerMode) { - ReaderMode.Reading -> { + ReaderMode.Reading, ReaderMode.Translation -> { val page = withContext(Dispatchers.IO) { readerVm.repository.getFirstPageOfHizb(hizbNo) } diff --git a/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsMainScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsMainScreen.kt index 34fa9430e..526fcfa66 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsMainScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/settings/SettingsMainScreen.kt @@ -182,7 +182,7 @@ fun SettingsMainScreen( SettingsItem( title = R.string.wordByWord, - icon = R.drawable.dr_icon_quran_script, + icon = R.drawable.ic_verse_end, ) { navController.navigate(SettingRoutes.WWB) } diff --git a/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt b/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt index 03fa2d009..420c3b9ee 100644 --- a/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt +++ b/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt @@ -244,6 +244,19 @@ object ReaderPreferences { return DataStoreManager.read(KEY_TRANSLATIONS) } + fun primaryTranslationSlug(): String { + val saved = getTranslations() + return saved.firstOrNull { !TranslUtils.isTransliteration(it) } + ?: TranslUtils.TRANSL_SLUG_DEFAULT + } + + @Composable + fun observePrimaryTranslationSlug(): String { + val saved = observeTranslations() + return saved.firstOrNull { !TranslUtils.isTransliteration(it) } + ?: TranslUtils.TRANSL_SLUG_DEFAULT + } + suspend fun setTranslations(translSlugsSet: Set) { DataStoreManager.write(KEY_TRANSLATIONS, HashSet(translSlugsSet)) } diff --git a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt index d8d2fd51b..9c921a35f 100644 --- a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt +++ b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt @@ -73,6 +73,8 @@ class QuranRepository( return ayahDao.getAyah(chapterNo, verseNo) } + suspend fun getAyahById(ayahId: Int): AyahEntity? = ayahDao.getAyahById(ayahId) + suspend fun getVerseWithDetails( chapterNo: Int, verseNo: Int, @@ -438,6 +440,31 @@ class QuranRepository( return rows.groupBy { it.pageNumber } } + /** + * Ayah ids covered by a mushaf ayah line, in canonical order (matches preload semantics). + */ + suspend fun ayahIdsForMushafAyahLine(row: MushafMapEntity): List { + if (row.lineType != MushafLineType.ayah) return emptyList() + val startAyah = row.startAyahId ?: return emptyList() + val endAyah = row.endAyahId ?: return emptyList() + if (row.startWordIndex == null || row.endWordIndex == null) return emptyList() + if (startAyah > endAyah) return emptyList() + return if (startAyah == endAyah) { + listOf(startAyah) + } else { + buildList { + add(startAyah) + if (endAyah - startAyah > 1) { + val middle = ayahDao.getAyahsStrictlyBetween(startAyah, endAyah) + for (ayah in middle) { + add(ayah.ayahId) + } + } + add(endAyah) + } + } + } + suspend fun getJuzForMushafPages( mushafId: Int, pageNumbers: List, diff --git a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt index 00e8a174f..6d51e339f 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt @@ -1,16 +1,28 @@ package com.quranapp.android.utils.reader import android.content.Context +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.Typography +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.ParagraphStyle import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withLink import androidx.compose.ui.text.withStyle import com.alfaazplus.sunnah.ui.theme.fontUrdu import com.quranapp.android.R +import com.quranapp.android.api.models.translation.TranslationBookInfoModel +import com.quranapp.android.components.quran.subcomponents.Translation import com.quranapp.android.compose.components.reader.QuranPageItem import com.quranapp.android.compose.components.reader.QuranPageLineItem import com.quranapp.android.compose.components.reader.ReaderLayoutItem import com.quranapp.android.compose.components.reader.ReaderPreparedData +import com.quranapp.android.compose.components.reader.TranslationPageItem +import com.quranapp.android.compose.components.reader.TranslationPageVerse import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.ChapterVerseBatch @@ -22,6 +34,7 @@ import com.quranapp.android.repository.QuranRepository import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.reader.factory.QuranTranslationFactory + private data class SectionSnapshot( val page: Int, val ruku: Int, @@ -29,6 +42,67 @@ private data class SectionSnapshot( val manzil: Int, ) +private data class TranslationVerseDraft( + val chapterNo: Int, + val verseNo: Int, + val ayahId: Int, + val annotatedText: AnnotatedString +) + +private fun mutedTranslatorLabelStyles( + colors: ColorScheme, + type: Typography +): Pair { + val nonUrdu = SpanStyle( + color = colors.onBackground.alpha(0.6f), + fontSize = type.labelMedium.fontSize, + ) + val urdu = SpanStyle( + color = colors.onBackground.alpha(0.6f), + fontSize = type.labelMedium.fontSize, + fontFamily = fontUrdu, + ) + return urdu to nonUrdu +} + +private fun buildAnnotatedTranslationWithTranslatorLine( + translation: Translation, + verse: VerseWithDetails, + colors: ColorScheme, + paragraphStyle: ParagraphStyle, + translationSpanStyle: SpanStyle, + labelMutedUrdu: SpanStyle, + labelMutedNonUrdu: SpanStyle, + bookInfo: TranslationBookInfoModel, + verseActions: VerseActions, +): AnnotatedString = buildAnnotatedString { + withStyle(paragraphStyle) { + withStyle(translationSpanStyle) { + append( + buildTranslationAnnotatedString( + translation, + colors, + actions = VerseActions( + verseActions.onReferenceClick, + onFootnoteClickRaw = { _, footnoteNo -> + verseActions.onFootnoteClick?.invoke( + verse, + translation.footnotes[footnoteNo] + ) + } + ) + ) + ) + } + + append("\n") + + withStyle(if (bookInfo.isUrdu) labelMutedUrdu else labelMutedNonUrdu) { + append(bookInfo.getDisplayName(false)) + } + } +} + object ReaderItemsBuilder { suspend fun buildVersesForTranslationMode( context: Context, @@ -194,15 +268,9 @@ object ReaderItemsBuilder { ts.toParagraphStyle() to ts.toSpanStyle() } - val labelMutedNonUrdu = SpanStyle( - color = params.colors.onBackground.alpha(0.6f), - fontSize = params.type.labelMedium.fontSize, - ) - - val labelMutedUrdu = SpanStyle( - color = params.colors.onBackground.alpha(0.6f), - fontSize = params.type.labelMedium.fontSize, - fontFamily = fontUrdu, + val (labelMutedUrdu, labelMutedNonUrdu) = mutedTranslatorLabelStyles( + params.colors, + params.type ) val wbwByAyah = @@ -255,37 +323,20 @@ object ReaderItemsBuilder { val (paragraphStyle, translationSpanStyle) = translationWrapStyles[translation.bookSlug] ?: return@mapNotNull null - val annotatedString = buildAnnotatedString { - withStyle(paragraphStyle) { - withStyle(translationSpanStyle) { - append( - buildTranslationAnnotatedString( - translation, - params.colors, - actions = VerseActions( - params.verseActions.onReferenceClick, - onFootnoteClickRaw = { slug, footnoteNo -> - params.verseActions.onFootnoteClick?.invoke( - verse, - translation.footnotes[footnoteNo] - ) - } - ) - ) - ) - } - - append("\n") - - withStyle( - if (bookInfo.isUrdu) labelMutedUrdu else labelMutedNonUrdu - ) { - append(bookInfo.getDisplayName(false)) - } - } - } - - Pair(translation.bookSlug, annotatedString) + Pair( + translation.bookSlug, + buildAnnotatedTranslationWithTranslatorLine( + translation = translation, + verse = verse, + colors = params.colors, + paragraphStyle = paragraphStyle, + translationSpanStyle = translationSpanStyle, + labelMutedUrdu = labelMutedUrdu, + labelMutedNonUrdu = labelMutedNonUrdu, + bookInfo = bookInfo, + verseActions = params.verseActions, + ), + ) } out.add( @@ -357,14 +408,9 @@ object ReaderItemsBuilder { ts.toParagraphStyle() to ts.toSpanStyle() } - val labelMutedNonUrdu = SpanStyle( - color = params.colors.onBackground.alpha(0.6f), - fontSize = params.type.labelMedium.fontSize, - ) - val labelMutedUrdu = SpanStyle( - color = params.colors.onBackground.alpha(0.6f), - fontSize = params.type.labelMedium.fontSize, - fontFamily = fontUrdu, + val (labelMutedUrdu, labelMutedNonUrdu) = mutedTranslatorLabelStyles( + params.colors, + params.type ) val wbwByAyah = @@ -401,30 +447,20 @@ object ReaderItemsBuilder { val (paragraphStyle, translationSpanStyle) = translationWrapStyles[translation.bookSlug] ?: return@mapNotNull null - val annotatedString = buildAnnotatedString { - withStyle(paragraphStyle) { - withStyle(translationSpanStyle) { - append( - buildTranslationAnnotatedString( - translation, params.colors, - actions = VerseActions( - params.verseActions.onReferenceClick, - onFootnoteClickRaw = { slug, footnoteNo -> - params.verseActions.onFootnoteClick?.invoke( - verse, translation.footnotes[footnoteNo] - ) - } - ) - ) - ) - } - append("\n") - withStyle( - if (bookInfo.isUrdu) labelMutedUrdu else labelMutedNonUrdu - ) { append(bookInfo.getDisplayName(false)) } - } - } - Pair(translation.bookSlug, annotatedString) + Pair( + translation.bookSlug, + buildAnnotatedTranslationWithTranslatorLine( + translation = translation, + verse = verse, + colors = params.colors, + paragraphStyle = paragraphStyle, + translationSpanStyle = translationSpanStyle, + labelMutedUrdu = labelMutedUrdu, + labelMutedNonUrdu = labelMutedNonUrdu, + bookInfo = bookInfo, + verseActions = params.verseActions, + ), + ) } out.add( @@ -509,6 +545,170 @@ object ReaderItemsBuilder { return out } + /** + * Mushaf pages with a single translation per verse. Verses are ordered by mushaf appearance + * on the page; text uses the same annotated pipeline as verse-by-verse (footnotes, refs). + */ + suspend fun buildTranslationPages( + context: Context, + quranRepository: QuranRepository, + pageNumbers: Collection, + translationSlug: String, + params: TranslationPageBuilderParams, + ): Map { + val distinct = pageNumbers.filter { it > 0 }.distinct().sorted() + if (distinct.isEmpty()) return emptyMap() + + val scriptCode = ReaderPreferences.getQuranScript() + val mushafId = scriptCode.toQuranMushafId(ReaderPreferences.getQuranScriptVariant()) + if (mushafId <= 0) return emptyMap() + + val linesByPage = quranRepository.getPageLinesGroupedForPages(mushafId, distinct) + val juzByPage = quranRepository.getJuzForMushafPages(mushafId, distinct) + val out = LinkedHashMap(distinct.size) + + QuranTranslationFactory(context).use { factory -> + val slugSet = setOf(translationSlug) + + val ts = getTranslationTextStyle( + TranslationTextStyleParams( + translationSlug, + params.translationSizeMultiplier, + ), + baseLineHeightMultiplier = 1.75f + ) + + val translationSpanStyle = ts.toSpanStyle() + val translationSpanPressedStyle = translationSpanStyle.copy( + color = params.colors.primary + ) + val paragraphStyle = ts.toParagraphStyle() + + for (pageNo in distinct) { + val rows = linesByPage[pageNo].orEmpty().sortedBy { it.lineNumber } + val drafts = ArrayList() + val seenAyahIds = mutableSetOf() + + for (row in rows) { + if (row.lineType != MushafLineType.ayah) continue + + val ayahIds = quranRepository.ayahIdsForMushafAyahLine(row) + + for (ayahId in ayahIds) { + if (!seenAyahIds.add(ayahId)) continue + + val ayah = quranRepository.getAyahById(ayahId) ?: continue + + val verseDetails = quranRepository.getVerseWithDetails( + ayah.surahNo, + ayah.ayahNo, + scriptCode, + ) ?: continue + + val transl = factory.getTranslationsSingleVerse( + slugSet, + ayah.surahNo, + ayah.ayahNo, + ).firstOrNull() ?: continue + + val annotated = buildAnnotatedString { + withLink( + LinkAnnotation.Clickable( + tag = "${ayah.surahNo}:${ayah.ayahNo}", + styles = TextLinkStyles( + style = translationSpanStyle, + pressedStyle = translationSpanPressedStyle, + hoveredStyle = translationSpanPressedStyle, + focusedStyle = translationSpanPressedStyle, + ) + ) { + params.verseActions.onReferenceClick( + slugSet, + ayah.surahNo, + ayah.ayahNo.toString(), + ) + } + ) { + withStyle( + style = SpanStyle( + color = params.colors.onSurface.alpha(0.6f), + fontWeight = FontWeight.Bold + ) + ) { + append("\u200F﴿${ayah.ayahNo}﴾\u200F ") + } + + append( + buildTranslationAnnotatedString( + transl, + params.colors, + actions = VerseActions( + params.verseActions.onReferenceClick, + onFootnoteClickRaw = { _, footnoteNo -> + params.verseActions.onFootnoteClick?.invoke( + verseDetails, + transl.footnotes[footnoteNo] + ) + } + ) + ) + ) + } + } + + drafts.add( + TranslationVerseDraft( + chapterNo = ayah.surahNo, + verseNo = ayah.ayahNo, + ayahId = ayahId, + annotatedText = annotated, + ) + ) + } + } + + val verses = ArrayList(drafts.size) + val annotatedText = buildAnnotatedString { + withStyle(paragraphStyle) { + drafts.forEachIndexed { index, d -> + if (index > 0) append(" ") + + val start = length + + append(d.annotatedText) + + val end = length + + verses.add( + TranslationPageVerse( + chapterNo = d.chapterNo, + verseNo = d.verseNo, + rangeStart = start, + rangeEnd = end, + ) + ) + } + } + } + + val hizbNo = quranRepository.getHizbForMushafPage(mushafId, pageNo) + val chapterNames = quranRepository.getChapterNamesOnMushafPage(mushafId, pageNo) + + out[pageNo] = TranslationPageItem( + pageNo = pageNo, + juzNo = juzByPage[pageNo] ?: -1, + hizbNo = hizbNo, + chapterNames = chapterNames, + translationSlug = translationSlug, + annotatedText = annotatedText, + verses = verses, + ) + } + } + + return out + } + private suspend fun mapMushafRowToLineItem( row: MushafMapEntity, quranRepository: QuranRepository, diff --git a/app/src/main/java/com/quranapp/android/utils/reader/ReaderLaunchParams.kt b/app/src/main/java/com/quranapp/android/utils/reader/ReaderLaunchParams.kt index 8d34cb959..02c000c8a 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/ReaderLaunchParams.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/ReaderLaunchParams.kt @@ -46,6 +46,7 @@ sealed class ReaderIntentData { val pageNo: Int, val fallbackChapterNo: Int = 0, val fallbackVerseNo: Int = 0, + override val initialVerse: ChapterVersePair? = null, ) : ReaderIntentData() } @@ -92,6 +93,10 @@ data class ReaderLaunchParams( putExtra(KEY_RESTORE_PAGE, d.pageNo) putExtra(KEY_CHAPTER_NO, d.fallbackChapterNo) putExtra(KEY_FALLBACK_VERSE, d.fallbackVerseNo) + d.initialVerse?.let { + putExtra(KEY_INITIAL_VERSE_CHAPTER, it.chapterNo) + putExtra(KEY_INITIAL_VERSE_NO, it.verseNo) + } } } @@ -127,6 +132,15 @@ data class ReaderLaunchParams( val restorePage = intent.getIntExtra(KEY_RESTORE_PAGE, -1) if (mushafCode != null && restorePage > 0) { + val mushafInitialChapter = intent.getIntExtra(KEY_INITIAL_VERSE_CHAPTER, -1) + val mushafInitialVerse = intent.getIntExtra(KEY_INITIAL_VERSE_NO, -1) + val mushafInitialVersePair = + if (mushafInitialChapter > 0 && mushafInitialVerse > 0) { + ChapterVersePair(mushafInitialChapter, mushafInitialVerse) + } else { + null + } + return ReaderLaunchParams( data = ReaderIntentData.MushafPage( mushafCode = mushafCode, @@ -134,6 +148,7 @@ data class ReaderLaunchParams( pageNo = restorePage, fallbackChapterNo = intent.getIntExtra(KEY_CHAPTER_NO, 1), fallbackVerseNo = intent.getIntExtra(KEY_FALLBACK_VERSE, 1), + initialVerse = mushafInitialVersePair, ), readerMode = intent.getStringExtra(KEY_READER_MODE) ?.takeIf { it.isNotEmpty() } diff --git a/app/src/main/java/com/quranapp/android/utils/reader/TextDecorator.kt b/app/src/main/java/com/quranapp/android/utils/reader/TextDecorator.kt index 9a615c595..d2dbecd90 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/TextDecorator.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/TextDecorator.kt @@ -48,6 +48,14 @@ data class PageBuilderParams( val contentWidthPx: Int, ) +data class TranslationPageBuilderParams( + val context: Context, + val colors: ColorScheme, + val type: Typography, + val verseActions: VerseActions, + val translationSizeMultiplier: Float, +) + data class TranslationTextStyleParams( val slug: String, val sizeMultiplier: Float @@ -65,7 +73,8 @@ data class QuranTextStyleParams( ) fun getTranslationTextStyle( - params: TranslationTextStyleParams + params: TranslationTextStyleParams, + baseLineHeightMultiplier: Float = 1.5f ): TextStyle { val isUrdu = TranslUtils.isUrdu(params.slug) val resolvedFontSize = 16.sp * params.sizeMultiplier @@ -77,7 +86,7 @@ fun getTranslationTextStyle( includeFontPadding = true ), fontSize = resolvedFontSize, - lineHeight = if (isUrdu) resolvedFontSize * 2.5f else resolvedFontSize * 1.5, + lineHeight = if (isUrdu) resolvedFontSize * 2.5f else resolvedFontSize * baseLineHeightMultiplier, ) } diff --git a/app/src/main/java/com/quranapp/android/utils/reader/TranslUtils.java b/app/src/main/java/com/quranapp/android/utils/reader/TranslUtils.java index 3380c22d0..568eb180f 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/TranslUtils.java +++ b/app/src/main/java/com/quranapp/android/utils/reader/TranslUtils.java @@ -189,6 +189,10 @@ public static boolean isUrdu(String slug) { return Objects.equals(slug.split("_")[0], "ur"); } + public static boolean isRtl(String slug) { + return isUrdu(slug); + } + public static boolean isTransliteration(String slug) { return slug.contains(TRANSL_TRANSLITERATION_SLUG_PART); } diff --git a/app/src/main/java/com/quranapp/android/utils/reader/factory/ReaderFactory.kt b/app/src/main/java/com/quranapp/android/utils/reader/factory/ReaderFactory.kt index 9ba285726..a0b0530c4 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/factory/ReaderFactory.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/factory/ReaderFactory.kt @@ -186,7 +186,9 @@ object ReaderFactory { val readerMode = ReaderMode.fromValue(entity.readerMode) val pageNo = entity.pageNo - if (readerMode == ReaderMode.Reading && pageNo != null && pageNo > 0 && entity.mushafCode != null) { + if ((readerMode == ReaderMode.Reading || readerMode == ReaderMode.Translation) && + pageNo != null && pageNo > 0 && entity.mushafCode != null + ) { return ReaderLaunchParams( data = ReaderIntentData.MushafPage( mushafCode = entity.mushafCode, diff --git a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt index 0461d10ae..af65b225b 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt @@ -17,6 +17,7 @@ import com.quranapp.android.compose.components.reader.QuranPageLineItem import com.quranapp.android.compose.components.reader.ReaderLayoutItem import com.quranapp.android.compose.components.reader.ReaderMode import com.quranapp.android.compose.components.reader.ReaderPreparedData +import com.quranapp.android.compose.components.reader.TranslationPageItem import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.entities.ReadHistoryEntity import com.quranapp.android.utils.Log @@ -31,6 +32,7 @@ import com.quranapp.android.utils.reader.ReaderIntentData import com.quranapp.android.utils.reader.ReaderItemsBuilder import com.quranapp.android.utils.reader.ReaderLaunchParams import com.quranapp.android.utils.reader.TextBuilderParams +import com.quranapp.android.utils.reader.TranslationPageBuilderParams import com.quranapp.android.utils.reader.VerseActions import com.quranapp.android.utils.reader.toQuranMushafId import kotlinx.coroutines.Dispatchers @@ -122,8 +124,9 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic initialValue = emptyList(), ) - val pageItems = mutableStateMapOf() val pageCounts = mutableStateMapOf() + + val pageItems = mutableStateMapOf() private val mushafPagesInFlight = mutableSetOf() var mushafLayoutKey = mutableStateOf( @@ -135,6 +138,10 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic private var pageRestoreOnMushafChangeJob: Job? = null + val translationPageItems = mutableStateMapOf() + private val translationPagesInFlight = mutableSetOf() + private var lastTranslationReaderContentKey: String? = null + private val context get() = application init { @@ -227,7 +234,50 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } } - else -> {} + ReaderMode.Translation -> { + val newLayoutKey = QuranScript.fromRawValues(script, scriptVariant) + val oldLayoutKey = mushafLayoutKey.value + + if (oldLayoutKey != newLayoutKey) { + val pageInPreviousMushaf = _uiState.value.currentPageNo + + mushafPagesInFlight.clear() + pageItems.clear() + translationPagesInFlight.clear() + translationPageItems.clear() + lastTranslationReaderContentKey = null + + pageRestoreOnMushafChangeJob?.cancel() + + val previousKey = oldLayoutKey + + pageRestoreOnMushafChangeJob = viewModelScope.launch { + try { + restorePageOnMushafChange(previousKey, pageInPreviousMushaf) + } finally { + mushafLayoutKey.value = QuranScript( + ReaderPreferences.getQuranScript(), + ReaderPreferences.getQuranScriptVariant(), + ) + } + } + } + + val contentKey = + "$script-$scriptVariant-${ReaderPreferences.primaryTranslationSlug()}-${ + prefs.get(ReaderPreferences.KEY_TEXT_SIZE_MULT_TRANSL) + }" + + if (lastTranslationReaderContentKey != contentKey) { + translationPageItems.clear() + translationPagesInFlight.clear() + lastTranslationReaderContentKey = contentKey + } + } + + else -> { + lastTranslationReaderContentKey = null + } } } } @@ -249,7 +299,13 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic // Check if explicitly requested mushaf mode if (data is ReaderIntentData.MushafPage) { - initMushafPage(data) + initMushafPage(data, params.readerMode) + if (data.initialVerse != null) { + requestVerseNavigation( + data.initialVerse.chapterNo, + data.initialVerse.verseNo, + ) + } return } @@ -286,8 +342,11 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } } - private suspend fun initMushafPage(data: ReaderIntentData.MushafPage) { - ReaderPreferences.setReaderMode(ReaderMode.Reading) + private suspend fun initMushafPage( + data: ReaderIntentData.MushafPage, + readerMode: ReaderMode?, + ) { + ReaderPreferences.setReaderMode(readerMode ?: ReaderMode.Reading) if (data.mushafCode != null) { ReaderPreferences.setQuranScriptWithVariant(data.mushafCode, data.mushafVariant) @@ -382,6 +441,27 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } } + fun updateLastKnownVerseFromTranslationPage(pageNo: Int) { + val page = translationPageItems[pageNo] + + if (page != null) { + val firstVerse = page.verses.firstOrNull() + + if (firstVerse != null) { + lastKnownVerse = ChapterVersePair(firstVerse.chapterNo, firstVerse.verseNo) + return + } + } + + viewModelScope.launch(Dispatchers.IO) { + val ayahId = repository.getFirstAyahIdOnPage(pageNo) ?: return@launch + + lastKnownVerse = QuranUtils.getVerseNoFromAyahId(ayahId).let { + ChapterVersePair(it.first, it.second) + } + } + } + fun saveReadHistory() { val state = _uiState.value val viewType = state.viewType ?: return @@ -443,7 +523,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic consumeVerseNavigation() when (to) { - ReaderMode.Reading -> { + ReaderMode.Reading, ReaderMode.Translation -> { val page = resolvePageNo(chapterNo, verseNo) if (page != null) { @@ -468,8 +548,6 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic requestVerseNavigation(chapterNo, verseNo) } - - else -> {} } } @@ -497,7 +575,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic val (chapterNo, verseNo) = verse when (mode) { - ReaderMode.Reading -> { + ReaderMode.Reading, ReaderMode.Translation -> { val page = resolvePageNo(chapterNo, verseNo) if (page != null) requestPageNavigation(page) } @@ -518,8 +596,6 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } requestVerseNavigation(chapterNo, verseNo) } - - else -> {} } } @@ -564,22 +640,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic totalPages: Int, params: PageBuilderParams ) { - Log.d("BUILDING", anchorPages) - - if (totalPages <= 0) return - - val targets = linkedSetOf() - for (anchorPage in anchorPages) { - if (anchorPage !in 1..totalPages) continue - - for (d in -MUSHAF_PREFETCH_RADIUS..MUSHAF_PREFETCH_RADIUS) { - val page = anchorPage + d - if (page in 1..totalPages) { - targets += page - } - } - } - + val targets = mushafPrefetchTargets(anchorPages, totalPages) if (targets.isEmpty()) return val missing = withContext(Dispatchers.Main) { @@ -613,6 +674,53 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } } + fun clearTranslationPageCache() { + translationPageItems.clear() + translationPagesInFlight.clear() + } + + suspend fun fetchTranslationPages( + context: Context, + anchorPages: Collection, + totalPages: Int, + buildParams: TranslationPageBuilderParams, + ) { + val targets = mushafPrefetchTargets(anchorPages, totalPages) + if (targets.isEmpty()) return + + val missing = withContext(Dispatchers.Main) { + targets.filter { page -> + !translationPageItems.containsKey(page) && translationPagesInFlight.add(page) + } + } + + if (missing.isEmpty()) return + + val slug = ReaderPreferences.primaryTranslationSlug() + + try { + val built = withContext(Dispatchers.IO) { + ReaderItemsBuilder.buildTranslationPages( + context, + repository, + missing, + slug, + buildParams, + ) + } + + withContext(Dispatchers.Main) { + for (page in missing) { + built[page]?.let { translationPageItems[page] = it } + } + } + } finally { + withContext(Dispatchers.Main) { + translationPagesInFlight.removeAll(missing.toSet()) + } + } + } + /** * Resolves reading position after script/mushaf change: [lastKnownVerse] if valid, else first ayah * on the current page using the **previous** mushaf layout, then maps that verse to a page in the new mushaf. @@ -658,6 +766,19 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic const val MUSHAF_PREFETCH_RADIUS = 4 +private fun mushafPrefetchTargets(anchorPages: Collection, totalPages: Int): Set { + if (totalPages <= 0) return emptySet() + val targets = linkedSetOf() + for (anchorPage in anchorPages) { + if (anchorPage !in 1..totalPages) continue + for (d in -MUSHAF_PREFETCH_RADIUS..MUSHAF_PREFETCH_RADIUS) { + val page = anchorPage + d + if (page in 1..totalPages) targets += page + } + } + return targets +} + private fun ReaderUiState.rebuildEquals(other: ReaderUiState): Boolean = viewType == other.viewType && error == other.error diff --git a/app/src/main/res/drawable/ic_verse_end.xml b/app/src/main/res/drawable/ic_verse_end.xml new file mode 100644 index 000000000..dc6652952 --- /dev/null +++ b/app/src/main/res/drawable/ic_verse_end.xml @@ -0,0 +1,12 @@ + + + + From 46e17236945e756b3a281e65a64ee3aa2c9ded41 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Sun, 19 Apr 2026 17:30:50 +0530 Subject: [PATCH 06/16] Advanced chapter filtering --- .../homepage/HomeSectionReadHistory.kt | 1 + .../compose/components/reader/Mushaf.kt | 7 +- .../components/reader/TranslationReader.kt | 7 +- .../compose/screens/ReadHistoryScreen.kt | 5 +- .../screens/reader/ReaderIndexScreen.kt | 317 +++++++++++++++--- .../com/quranapp/android/db/dao/AyahDao.kt | 9 + .../android/repository/QuranRepository.kt | 4 + .../utils/reader/ReaderChapterIndexFilters.kt | 82 +++++ .../viewModels/ReaderIndexViewModel.kt | 54 ++- .../android/viewModels/ReaderViewModel.kt | 15 +- app/src/main/res/values/strings.xml | 10 + 11 files changed, 448 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/com/quranapp/android/utils/reader/ReaderChapterIndexFilters.kt diff --git a/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionReadHistory.kt b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionReadHistory.kt index 7be023f49..263b1e7e0 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionReadHistory.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionReadHistory.kt @@ -143,6 +143,7 @@ private fun ItemCard( painter = painterResource( when (ReaderMode.fromValue(history.readerMode)) { ReaderMode.Reading -> R.drawable.ic_mode_mushaf + ReaderMode.Translation -> R.drawable.ic_mode_translation else -> R.drawable.ic_mode_verse } ), diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt index 9108cd003..7eb97f5b6 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/Mushaf.kt @@ -240,10 +240,15 @@ fun ReaderLayoutPageMode( LaunchedEffect(navigateToVerse, pageCount) { val targetVerse = navigateToVerse ?: return@LaunchedEffect + if (pageCount <= 0) return@LaunchedEffect val targetPage = readerVm.resolvePageNo(targetVerse.chapterNo, targetVerse.verseNo) - ?: return@LaunchedEffect + ?: run { + readerVm.consumeVerseNavigation() + return@LaunchedEffect + } + readerVm.consumeVerseNavigation() readerVm.requestPageNavigation(targetPage) } diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt index 6e5a009e0..c7506a71d 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt @@ -212,10 +212,15 @@ fun ReaderLayoutTranslationPageMode( LaunchedEffect(navigateToVerse, pageCount) { val targetVerse = navigateToVerse ?: return@LaunchedEffect + if (pageCount <= 0) return@LaunchedEffect val targetPage = readerVm.resolvePageNo(targetVerse.chapterNo, targetVerse.verseNo) - ?: return@LaunchedEffect + ?: run { + readerVm.consumeVerseNavigation() + return@LaunchedEffect + } + readerVm.consumeVerseNavigation() readerVm.requestPageNavigation(targetPage) } diff --git a/app/src/main/java/com/quranapp/android/compose/screens/ReadHistoryScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/ReadHistoryScreen.kt index bfc6f385f..b1e6af582 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/ReadHistoryScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/ReadHistoryScreen.kt @@ -197,6 +197,7 @@ private fun ReadHistoryCard( painter = painterResource( when (ReaderMode.fromValue(history.readerMode)) { ReaderMode.Reading -> R.drawable.ic_mode_mushaf + ReaderMode.Translation -> R.drawable.ic_mode_translation else -> R.drawable.ic_mode_verse } ), @@ -306,7 +307,7 @@ private fun HistoryDeleteDialog( @Composable fun ReadHistoryEntity.titleLabel(chapterName: String): String { - if (readerMode == ReaderMode.Reading.value) { + if (readerMode == ReaderMode.Reading.value || readerMode == ReaderMode.Translation.value) { return pageNo?.let { stringResource(R.string.strLabelPageNo, pageNo) } ?: "-" } else { return when (ReadType.fromValue(readType)) { @@ -329,7 +330,7 @@ fun ReadHistoryEntity.subtitleLabel(chapterName: String): String? { stringResource(R.string.strLabelVerses, fromVerseNo, toVerseNo) } - if (readerMode == ReaderMode.Reading.value) { + if (readerMode == ReaderMode.Reading.value || readerMode == ReaderMode.Translation.value) { return mushafCode?.getQuranScriptName() ?: "-" } else { return when (ReadType.fromValue(readType)) { diff --git a/app/src/main/java/com/quranapp/android/compose/screens/reader/ReaderIndexScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/reader/ReaderIndexScreen.kt index bb0a2c507..6bbbb56e3 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/reader/ReaderIndexScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/reader/ReaderIndexScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -21,6 +22,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan @@ -30,7 +32,11 @@ import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButtonDefaults @@ -38,13 +44,12 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.PrimaryScrollableTabRow -import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Scaffold import androidx.compose.material3.SecondaryTabRow import androidx.compose.material3.Surface import androidx.compose.material3.Tab import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.rememberTopAppBarState @@ -69,6 +74,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -82,14 +88,21 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.peacedesign.android.utils.ColorUtils import com.quranapp.android.R import com.quranapp.android.activities.ActivitySearch +import com.quranapp.android.compose.components.common.Chip import com.quranapp.android.compose.components.common.Loader +import com.quranapp.android.compose.components.dialogs.BottomSheet import com.quranapp.android.compose.components.reader.navigator.ChapterCard import com.quranapp.android.compose.components.reader.navigator.FilterField import com.quranapp.android.compose.components.reader.navigator.HizbCard import com.quranapp.android.compose.components.reader.navigator.JuzCard import com.quranapp.android.db.relations.NavigationUnit import com.quranapp.android.db.relations.SurahWithLocalizations +import com.quranapp.android.utils.reader.ReaderChapterIndexFilters +import com.quranapp.android.utils.reader.ReaderChapterLengthFilter +import com.quranapp.android.utils.reader.ReaderChapterRevelationFilter +import com.quranapp.android.utils.reader.ReaderChapterSajdaFilter import com.quranapp.android.utils.reader.factory.ReaderFactory +import com.quranapp.android.utils.reader.filteredByChapterIndex import com.quranapp.android.utils.univ.MessageUtils import com.quranapp.android.viewModels.ReaderIndexViewModel import kotlinx.coroutines.launch @@ -391,8 +404,9 @@ private fun ReaderIndexTabs( }, shadowElevation = 2.dp, ) { - SecondaryTabRow ( + SecondaryTabRow( selectedTabIndex = selectedTabIndex, + containerColor = colorScheme.surfaceContainer ) { tabs.forEachIndexed { index, titleRes -> val isSelected = selectedTabIndex == index @@ -417,6 +431,7 @@ private fun ReaderIndexTabs( } } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun ReaderIndexChaptersList( viewModel: ReaderIndexViewModel, @@ -430,74 +445,265 @@ private fun ReaderIndexChaptersList( val scope = rememberCoroutineScope() val favChapters = viewModel.getFavouriteChapters() + val chapterFilters by viewModel.chapterIndexFilters.collectAsState() + val surahNosWithSajdah by viewModel.surahNosWithSajdah.collectAsState() + var searchQuery by rememberSaveable { mutableStateOf("") } var filteredSurahs by remember { mutableStateOf(surahs) } - - LaunchedEffect(searchQuery, surahs, reversed) { + var filterSheetOpen by remember { mutableStateOf(false) } + + LaunchedEffect( + searchQuery, + surahs, + reversed, + chapterFilters, + surahNosWithSajdah, + ) { val query = searchQuery.lowercase().trim() - val base = if (query.isEmpty()) { + val searched = if (query.isEmpty()) { surahs } else { val surahNos = viewModel.repository.searchSurahNos(query) surahs.filter { it.surah.surahNo in surahNos } } - filteredSurahs = if (reversed) base.reversed() else base + val filtered = searched.filteredByChapterIndex(chapterFilters, surahNosWithSajdah) + filteredSurahs = if (reversed) filtered.reversed() else filtered } if (surahs.isEmpty()) return Loader(true) + val isFilterApplied = !chapterFilters.isDefault() + BoxWithConstraints { - LazyVerticalGrid( - columns = GridCells.Fixed(if (maxWidth < 600.dp) 1 else 2), - state = listState, - modifier = modifier - .fillMaxSize() - .nestedScroll(nestedScrollConnection), - contentPadding = PaddingValues( - start = 16.dp, - end = 16.dp, - top = ReaderIndexTabHeight + 16.dp, - bottom = 128.dp - ), - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + val cellCount = if (maxWidth < 600.dp) 1 else 2 + + Box(modifier = Modifier.fillMaxSize()) { + LazyVerticalGrid( + columns = GridCells.Fixed(cellCount), + state = listState, + modifier = modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection), + contentPadding = PaddingValues( + start = 16.dp, + end = 16.dp, + top = ReaderIndexTabHeight + 16.dp, + bottom = 128.dp + ), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + item(span = { GridItemSpan(maxLineSpan) }) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 6.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + FilterField( + modifier = Modifier.weight(1f), + value = searchQuery, + onValueChange = { searchQuery = it }, + hint = stringResource(R.string.strHintSearchChapter), + keyboardType = KeyboardType.Text, + ) + + BadgedBox( + badge = { + if (isFilterApplied) { + Badge() + } + } + ) { + IconButton( + onClick = { filterSheetOpen = true }, + modifier = Modifier.size(48.dp), + ) { + Icon( + painter = painterResource(R.drawable.dr_icon_filter), + contentDescription = stringResource(R.string.chapterFilters), + tint = if (isFilterApplied) colorScheme.primary else colorScheme.onBackground + ) + } + } + } + } + + items(filteredSurahs, key = { it.surah.surahNo }) { surah -> + val isFav = favChapters.contains(surah.surah.surahNo) + + ChapterCard( + surah = surah, + isFavourite = isFav, + onClick = { + ReaderFactory.startChapter(context, surah.surah.surahNo) + }, + onToggleFavourite = { + scope.launch { + if (isFav) { + viewModel.removeFromFavourites( + context, + surah.surah.surahNo, + favChapters + ) + } else { + viewModel.addToFavourites( + context, + surah.surah.surahNo, + favChapters + ) + } + } + } + ) + } + } + + ReaderIndexChapterFiltersSheet( + isOpen = filterSheetOpen, + onDismiss = { filterSheetOpen = false }, + filters = chapterFilters, + onSetFilters = { viewModel.setChapterIndexFilters(it) }, + ) + } + } +} + +@Composable +private fun ReaderIndexChapterFiltersSheet( + isOpen: Boolean, + onDismiss: () -> Unit, + filters: ReaderChapterIndexFilters, + onSetFilters: (ReaderChapterIndexFilters) -> Unit, +) { + BottomSheet( + isOpen = isOpen, + onDismiss = onDismiss, + icon = R.drawable.dr_icon_filter, + title = stringResource(R.string.strTitleFilters), + ) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) + .padding(bottom = 32.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { - item(span = { GridItemSpan(maxLineSpan) }) { - FilterField( - value = searchQuery, - onValueChange = { searchQuery = it }, - hint = stringResource(R.string.strHintSearchChapter), - keyboardType = KeyboardType.Text, + Text( + text = stringResource(R.string.strTitleChapInfoRevType), + style = MaterialTheme.typography.titleSmall, + color = colorScheme.onSurface + ) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Chip( + selected = filters.revelation == ReaderChapterRevelationFilter.any, + onClick = { + onSetFilters(filters.copy(revelation = ReaderChapterRevelationFilter.any)) + }, + label = { Text(stringResource(R.string.any)) }, + ) + Chip( + selected = filters.revelation == ReaderChapterRevelationFilter.meccan, + onClick = { + onSetFilters(filters.copy(revelation = ReaderChapterRevelationFilter.meccan)) + }, + label = { Text(stringResource(R.string.strTitleMakki)) }, + ) + Chip( + selected = filters.revelation == ReaderChapterRevelationFilter.medinan, + onClick = { + onSetFilters(filters.copy(revelation = ReaderChapterRevelationFilter.medinan)) + }, + label = { Text(stringResource(R.string.strTitleMadani)) }, ) } - items(filteredSurahs, key = { it.surah.surahNo }) { surah -> - val isFav = favChapters.contains(surah.surah.surahNo) + Text( + text = stringResource(R.string.sajda), + style = MaterialTheme.typography.titleSmall, + color = colorScheme.onSurface + ) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Chip( + selected = filters.sajda == ReaderChapterSajdaFilter.any, + onClick = { + onSetFilters(filters.copy(sajda = ReaderChapterSajdaFilter.any)) + }, + label = { Text(stringResource(R.string.any)) }, + ) + Chip( + selected = filters.sajda == ReaderChapterSajdaFilter.withSajda, + onClick = { + onSetFilters(filters.copy(sajda = ReaderChapterSajdaFilter.withSajda)) + }, + label = { Text(stringResource(R.string.withSajda)) }, + ) + Chip( + selected = filters.sajda == ReaderChapterSajdaFilter.withoutSajda, + onClick = { + onSetFilters(filters.copy(sajda = ReaderChapterSajdaFilter.withoutSajda)) + }, + label = { Text(stringResource(R.string.withoutSajda)) }, + ) + } - ChapterCard( - surah = surah, - isFavourite = isFav, + Text( + text = stringResource(R.string.filterSectionLength), + style = MaterialTheme.typography.titleSmall, + color = colorScheme.onSurface + ) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Chip( + selected = filters.length == ReaderChapterLengthFilter.any, onClick = { - ReaderFactory.startChapter(context, surah.surah.surahNo) + onSetFilters(filters.copy(length = ReaderChapterLengthFilter.any)) }, - onToggleFavourite = { - scope.launch { - if (isFav) { - viewModel.removeFromFavourites( - context, - surah.surah.surahNo, - favChapters - ) - } else { - viewModel.addToFavourites( - context, - surah.surah.surahNo, - favChapters - ) - } - } - } + label = { Text(stringResource(R.string.any)) }, + ) + Chip( + selected = filters.length == ReaderChapterLengthFilter.short, + onClick = { + onSetFilters(filters.copy(length = ReaderChapterLengthFilter.short)) + }, + label = { Text(stringResource(R.string.chapterFilterLengthShort)) }, ) + Chip( + selected = filters.length == ReaderChapterLengthFilter.medium, + onClick = { + onSetFilters(filters.copy(length = ReaderChapterLengthFilter.medium)) + }, + label = { Text(stringResource(R.string.chapterFilterLengthMedium)) }, + ) + Chip( + selected = filters.length == ReaderChapterLengthFilter.long, + onClick = { + onSetFilters(filters.copy(length = ReaderChapterLengthFilter.long)) + }, + label = { Text(stringResource(R.string.chapterFilterLengthLong)) }, + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + TextButton( + onClick = { onSetFilters(ReaderChapterIndexFilters.Default) } + ) { + Text(stringResource(R.string.clearFilters)) + } + Spacer(modifier = Modifier.width(8.dp)) + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.strLabelDone)) + } } } } @@ -551,6 +757,9 @@ private fun ReaderIndexJuzList( ) { item(span = { GridItemSpan(maxLineSpan) }) { FilterField( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 6.dp), value = searchQuery, onValueChange = { searchQuery = it }, hint = stringResource(R.string.strHintSearchBy), @@ -618,6 +827,9 @@ private fun ReaderIndexHizbList( ) { item(span = { GridItemSpan(maxLineSpan) }) { FilterField( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 6.dp), value = searchQuery, onValueChange = { searchQuery = it }, hint = stringResource(R.string.strHintSearchHizb), @@ -646,6 +858,7 @@ private fun ReaderIndexFavChaptersList( modifier: Modifier = Modifier ) { val context = LocalContext.current + val resources = LocalResources.current val scope = rememberCoroutineScope() val favChapters = viewModel.getFavouriteChapters() @@ -709,9 +922,9 @@ private fun ReaderIndexFavChaptersList( onToggleFavourite = { MessageUtils.showConfirmationDialog( context, - title = context.getString(R.string.titleRemoveFromFavourites), + title = resources.getString(R.string.titleRemoveFromFavourites), msg = surah.getCurrentName(), - btn = context.getString(R.string.strLabelRemove), + btn = resources.getString(R.string.strLabelRemove), btnColor = ColorUtils.DANGER, action = Runnable { scope.launch { diff --git a/app/src/main/java/com/quranapp/android/db/dao/AyahDao.kt b/app/src/main/java/com/quranapp/android/db/dao/AyahDao.kt index db0e9d797..5fe3d9eae 100644 --- a/app/src/main/java/com/quranapp/android/db/dao/AyahDao.kt +++ b/app/src/main/java/com/quranapp/android/db/dao/AyahDao.kt @@ -90,4 +90,13 @@ interface AyahDao { """ ) suspend fun getAyahsByIds(ayahIds: List): List + + @Query( + """ + SELECT DISTINCT surah_no FROM ayahs + WHERE IFNULL(sajdah_type, 0) != 0 + ORDER BY surah_no + """ + ) + suspend fun getDistinctSurahNosWithSajdah(): List } \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt index 9c921a35f..a885832c7 100644 --- a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt +++ b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt @@ -508,6 +508,10 @@ class QuranRepository( return surahDao.getAllSurahsWithLocalizations() } + suspend fun getSurahNosWithSajdah(): Set { + return ayahDao.getDistinctSurahNosWithSajdah().toSet() + } + fun getJuzs() = getRangesResolved(NavigationType.juz) fun getHizbs() = getRangesResolved(NavigationType.hizb) diff --git a/app/src/main/java/com/quranapp/android/utils/reader/ReaderChapterIndexFilters.kt b/app/src/main/java/com/quranapp/android/utils/reader/ReaderChapterIndexFilters.kt new file mode 100644 index 000000000..4632b952f --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/reader/ReaderChapterIndexFilters.kt @@ -0,0 +1,82 @@ +package com.quranapp.android.utils.reader + +import com.quranapp.android.db.entities.quran.RevelationType +import com.quranapp.android.db.relations.SurahWithLocalizations +import kotlinx.serialization.Serializable + +@Serializable +enum class ReaderChapterRevelationFilter { + any, + meccan, + medinan, +} + +@Serializable +enum class ReaderChapterSajdaFilter { + any, + withSajda, + withoutSajda, +} + +@Serializable +enum class ReaderChapterLengthFilter { + any, + short, + medium, + long, +} + +@Serializable +data class ReaderChapterIndexFilters( + val revelation: ReaderChapterRevelationFilter = ReaderChapterRevelationFilter.any, + val sajda: ReaderChapterSajdaFilter = ReaderChapterSajdaFilter.any, + val length: ReaderChapterLengthFilter = ReaderChapterLengthFilter.any, +) { + fun isDefault(): Boolean = this == Default + + companion object { + val Default = ReaderChapterIndexFilters() + } +} + +fun List.filteredByChapterIndex( + filters: ReaderChapterIndexFilters, + surahNosWithSajdah: Set, +): List { + var result = this + + when (filters.revelation) { + ReaderChapterRevelationFilter.meccan -> + result = result.filter { it.surah.revelationType == RevelationType.meccan } + + ReaderChapterRevelationFilter.medinan -> + result = result.filter { it.surah.revelationType == RevelationType.medinan } + + ReaderChapterRevelationFilter.any -> Unit + } + + when (filters.sajda) { + ReaderChapterSajdaFilter.withSajda -> + result = result.filter { it.surah.surahNo in surahNosWithSajdah } + + ReaderChapterSajdaFilter.withoutSajda -> + result = result.filter { it.surah.surahNo !in surahNosWithSajdah } + + ReaderChapterSajdaFilter.any -> Unit + } + + when (filters.length) { + ReaderChapterLengthFilter.short -> + result = result.filter { it.surah.ayahCount <= 100 } + + ReaderChapterLengthFilter.medium -> + result = result.filter { it.surah.ayahCount in 101..200 } + + ReaderChapterLengthFilter.long -> + result = result.filter { it.surah.ayahCount >= 201 } + + ReaderChapterLengthFilter.any -> Unit + } + + return result +} diff --git a/app/src/main/java/com/quranapp/android/viewModels/ReaderIndexViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ReaderIndexViewModel.kt index 9f606e6b0..4724bb459 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/ReaderIndexViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/ReaderIndexViewModel.kt @@ -4,7 +4,6 @@ import android.app.Application import android.content.Context import android.widget.Toast import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableIntStateOf import androidx.datastore.preferences.core.stringPreferencesKey import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope @@ -12,20 +11,73 @@ import com.alfaazplus.sunnah.ui.utils.shared_preference.DataStoreManager import com.quranapp.android.R import com.quranapp.android.db.DatabaseProvider import com.quranapp.android.utils.Log +import com.quranapp.android.utils.reader.ReaderChapterIndexFilters import com.quranapp.android.utils.sharedPrefs.SPFavouriteChapters import com.quranapp.android.utils.univ.Keys import com.quranapp.android.utils.univ.MessageUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json class ReaderIndexViewModel(application: Application) : AndroidViewModel(application) { val repository = DatabaseProvider.getQuranRepository(application) + private val filtersJson = Json { + ignoreUnknownKeys = true + encodeDefaults = true + } + + private val chapterFiltersKey = stringPreferencesKey("reader_index_chapter_filters") + private val chapterFiltersDefaultJson = + filtersJson.encodeToString(ReaderChapterIndexFilters.Default) + + val chapterIndexFilters: StateFlow = DataStoreManager + .flow(chapterFiltersKey, chapterFiltersDefaultJson) + .map { raw -> + try { + filtersJson.decodeFromString(raw) + } catch (e: Exception) { + Log.saveError(e, "chapterIndexFilters") + ReaderChapterIndexFilters.Default + } + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = ReaderChapterIndexFilters.Default + ) + + private val _surahNosWithSajdah = MutableStateFlow>(emptySet()) + val surahNosWithSajdah: StateFlow> = _surahNosWithSajdah.asStateFlow() + + init { + viewModelScope.launch(Dispatchers.IO) { + try { + _surahNosWithSajdah.value = repository.getSurahNosWithSajdah() + } catch (e: Exception) { + Log.saveError(e, "surahNosWithSajdah") + } + } + } + + fun setChapterIndexFilters(filters: ReaderChapterIndexFilters) { + viewModelScope.launch(Dispatchers.IO) { + DataStoreManager.write( + chapterFiltersKey, + filtersJson.encodeToString(filters) + ) + } + } + val surahs = repository.getAllSurahs() .stateIn( scope = viewModelScope, diff --git a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt index af65b225b..96ecb638e 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt @@ -300,12 +300,6 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic // Check if explicitly requested mushaf mode if (data is ReaderIntentData.MushafPage) { initMushafPage(data, params.readerMode) - if (data.initialVerse != null) { - requestVerseNavigation( - data.initialVerse.chapterNo, - data.initialVerse.verseNo, - ) - } return } @@ -367,6 +361,10 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic ) } requestPageNavigation(data.pageNo) + // explicit mushaf page wins over [initialVerse] for positioning; verse is only for + // history/sync — do not call [requestVerseNavigation] or translation/mushaf UI will + // resolve the verse to a (possibly different) page after scroll. + data.initialVerse?.takeIf { it.isValid }?.let { lastKnownVerse = it } } else if (QuranMeta.isChapterValid(data.fallbackChapterNo) && data.fallbackVerseNo > 0) { val page = resolvePageNo(data.fallbackChapterNo, data.fallbackVerseNo, data.mushafCode) @@ -377,10 +375,15 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic ) } if (page != null) requestPageNavigation(page) + lastKnownVerse = data.initialVerse?.takeIf { it.isValid } + ?: ChapterVersePair(data.fallbackChapterNo, data.fallbackVerseNo) } else { _uiState.update { ReaderUiState(viewType = ReaderViewType.Chapter(1)) } + data.initialVerse?.takeIf { it.isValid }?.let { + requestVerseNavigation(it.chapterNo, it.verseNo) + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6cb4ba01a..0c26fe684 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -87,6 +87,16 @@ Note Rate App Filters + Sajda + Chapter length + Any + With sajda + Without sajda + Short (≤100 verses) + Medium (101–200 verses) + Long (201+ verses) + Clear filters + Chapter filters Menu Tafsir Select Tafsir From 9d4311b6f7e19bd60e5c6a983251ed6a910ee5c1 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Sun, 19 Apr 2026 19:34:10 +0530 Subject: [PATCH 07/16] Audio streaming support / other improvements --- app/build.gradle.kts | 2 + .../components/player/PlayerMessages.kt | 18 ++ .../player/dialogs/ReciterSelectionSheet.kt | 49 +++- .../screens/tafsir/TafsirReaderScreen.kt | 30 ++- .../mediaplayer/RecitationAudioRepository.kt | 227 ++++-------------- .../utils/mediaplayer/RecitationService.kt | 71 +++++- .../android/utils/tafsir/TafsirUtils.java | 64 ----- .../android/utils/tafsir/TafsirUtils.kt | 49 ++++ .../android/viewModels/ReaderViewModel.kt | 10 +- .../viewModels/TafsirReaderViewModel.kt | 103 +++++++- app/src/main/res/values/strings.xml | 2 + gradle/libs.versions.toml | 2 + 12 files changed, 341 insertions(+), 286 deletions(-) delete mode 100644 app/src/main/java/com/quranapp/android/utils/tafsir/TafsirUtils.java create mode 100644 app/src/main/java/com/quranapp/android/utils/tafsir/TafsirUtils.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 00aa03362..c3d3620d1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -131,6 +131,8 @@ dependencies { implementation(libs.androidx.fragmentKtx) implementation(libs.media3ExoPlayer) + implementation(libs.media3Datasource) + implementation(libs.media3Database) implementation(libs.media3Session) implementation(libs.media3UI) diff --git a/app/src/main/java/com/quranapp/android/compose/components/player/PlayerMessages.kt b/app/src/main/java/com/quranapp/android/compose/components/player/PlayerMessages.kt index c8d9f5119..f5809f8e9 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/player/PlayerMessages.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/player/PlayerMessages.kt @@ -27,6 +27,24 @@ fun PlayerMessages(state: RecitationServiceState, isPlaying: Boolean) { verticalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.padding(horizontal = 16.dp), ) { + if (state.resolvingChapterNo != null && state.downloadProgress == null) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + CircularProgressIndicator( + modifier = Modifier.size(18.dp), + strokeWidth = 2.dp, + color = PlayerContentColor.alpha(0.9f), + ) + Text( + text = stringResource(R.string.textPreparingAudio), + style = MaterialTheme.typography.bodyMedium, + color = PlayerContentColor.alpha(0.9f), + ) + } + } + state.downloadProgress?.let { progress -> Row( verticalAlignment = Alignment.CenterVertically, diff --git a/app/src/main/java/com/quranapp/android/compose/components/player/dialogs/ReciterSelectionSheet.kt b/app/src/main/java/com/quranapp/android/compose/components/player/dialogs/ReciterSelectionSheet.kt index a281460c0..4fe495629 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/player/dialogs/ReciterSelectionSheet.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/player/dialogs/ReciterSelectionSheet.kt @@ -1,6 +1,8 @@ package com.quranapp.android.compose.components.player.dialogs +import android.content.Context import android.content.Intent +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -17,6 +19,7 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Tab import androidx.compose.material3.Text @@ -30,12 +33,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.quranapp.android.R import com.quranapp.android.activities.ActivitySettings import com.quranapp.android.api.models.mediaplayer.RecitationAudioKind +import com.quranapp.android.compose.components.common.AlertCard import com.quranapp.android.compose.components.common.IconButton import com.quranapp.android.compose.components.common.Loader import com.quranapp.android.compose.components.common.RadioItem @@ -78,10 +83,8 @@ fun ReciterSelectorSheet( modifier = Modifier .fillMaxWidth() .padding( - top = 16.dp, - bottom = 16.dp, - start = 16.dp, - end = 16.dp + horizontal = 16.dp, + vertical = 8.dp, ), verticalAlignment = Alignment.CenterVertically, ) { @@ -101,11 +104,7 @@ fun ReciterSelectorSheet( IconButton( painter = painterResource(R.drawable.dr_icon_download) ) { - context.startActivity( - Intent(context, ActivitySettings::class.java).apply { - putExtra(Keys.NAV_DESTINATION, SettingRoutes.RECITATION_DOWNLOAD) - }, - ) + openDownloadsScreen(context) } IconButton( @@ -133,6 +132,30 @@ fun ReciterSelectorSheet( } } + AlertCard( + modifier = Modifier.padding(16.dp), + ) { + Column( + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text( + text = stringResource(R.string.strMsgReciterDownloadHint), + style = typography.bodyMedium + ) + + Text( + stringResource(R.string.downloadRecitations), + modifier = Modifier.clickable { + openDownloadsScreen(context) + }, + style = typography.bodyMedium.copy( + fontWeight = FontWeight.Medium + ), + color = colorScheme.primary + ) + } + } + HorizontalPager( state = pagerState, modifier = Modifier @@ -267,4 +290,12 @@ private fun TranslationReciters( ) } } +} + +private fun openDownloadsScreen(context: Context) { + context.startActivity( + Intent(context, ActivitySettings::class.java).apply { + putExtra(Keys.NAV_DESTINATION, SettingRoutes.RECITATION_DOWNLOAD) + }, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/screens/tafsir/TafsirReaderScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/tafsir/TafsirReaderScreen.kt index 21448cb91..f31d8fe51 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/tafsir/TafsirReaderScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/tafsir/TafsirReaderScreen.kt @@ -62,6 +62,8 @@ import com.quranapp.android.R import com.quranapp.android.activities.ActivitySettings import com.quranapp.android.compose.components.common.AppBar import com.quranapp.android.compose.navigation.SettingRoutes +import com.quranapp.android.utils.quran.QuranMeta +import com.quranapp.android.utils.tafsir.TafsirUtils import com.quranapp.android.utils.tafsir.TafsirWebViewClient import com.quranapp.android.utils.univ.Keys import com.quranapp.android.utils.univ.ResUtils @@ -286,7 +288,7 @@ private fun TafsirWebViewContent( onPageFinished = { isLoading = false } ) val signature = htmlContent to tafsirKey - + if (lastLoad != signature) { lastLoad = signature isLoading = true @@ -336,9 +338,15 @@ private fun buildTafsirHtml( val multiVerseAlert = if (verses.size > 1) { val alertMsg = context.getString(R.string.readingTafsirMultiVerses) + val versesSorted = verses.sortedWith( + compareBy( + { it.substringBefore(':').toIntOrNull() ?: 0 }, + { it.substringAfter(':').toIntOrNull() ?: 0 } + ) + ) """
- $alertMsg ${verses.joinToString(", ")} + $alertMsg ${versesSorted.joinToString(", ")}
""".trimIndent() } else "" @@ -458,10 +466,22 @@ private fun TafsirBottomNavigation( uiState: TafsirReaderUiState, onEvent: (TafsirReaderEvent) -> Unit, ) { - val hasPrevious = uiState.verseNo > 1 val totalVerses = uiState.chapterMeta?.surah?.ayahCount ?: 0 - val verseNo = uiState.verseNo - val hasNext = verseNo < totalVerses + val lastChapter = QuranMeta.chapterRange.last + val range = when (val c = uiState.contentState) { + is TafsirContentState.Success -> + TafsirUtils.tafsirVerseRangeInChapter(c.tafsir, uiState.chapterNo) + + else -> null + } + + val groupFirst = range?.first ?: uiState.verseNo + val groupLast = range?.last ?: uiState.verseNo + + val hasPrevious = uiState.chapterNo > 1 || groupFirst > 1 + val hasNext = totalVerses > 0 && + (groupLast < totalVerses || + (uiState.chapterNo < lastChapter && groupLast >= totalVerses)) Surface( color = colorScheme.surfaceContainer, diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt index ec1197b6f..126eda701 100644 --- a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt @@ -1,10 +1,8 @@ package com.quranapp.android.utils.mediaplayer import android.content.Context +import android.net.Uri import androidx.core.net.toUri -import androidx.lifecycle.asFlow -import androidx.work.ExistingWorkPolicy -import androidx.work.WorkInfo import androidx.work.WorkManager import com.quranapp.android.api.JsonHelper import com.quranapp.android.api.RetrofitInstance @@ -27,10 +25,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.withContext import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.intOrNull @@ -83,11 +79,18 @@ class RecitationAudioRepository(private val context: Context) { } /** - * Resolves chapter audio and timing. Each resource is handled on its own: - * - Audio: uses local file if present; otherwise downloads (requires network). - * - Timing: uses cache if valid; otherwise downloads when online (timingMetadata may be null). + * Resolves chapter audio URIs and timing for playback. * - * Missing audio still fails the flow when that track is required; missing timing never fails. + * Audio URIs + * - If the chapter file under the reciter directory exists and is non-empty, `RecitationAudioTrack.audioUri` + * is a `file://` URI (explicit / bulk download). This always wins. + * - Otherwise, when the device is online, `audioUri` is the prepared HTTPS URL for that chapter. + * Playback buffers via ExoPlayer; bytes are not written to the reciter folder by this call. + * Repeat plays use ExoPlayer `SimpleCache` (see `RecitationService`), not `RecitationAudioDownloadWorker`. + * - Offline with an empty placeholder file fails with `NoInternetException`. + * + * Timing: cache if valid; otherwise fetch when online (ChapterTimingMetadata may be null). + * Missing audio for a required track fails; missing timing does not fail the flow. */ fun resolveAudioUris( chapterNo: Int, @@ -117,8 +120,9 @@ class RecitationAudioRepository(private val context: Context) { modelManager.getRecitationAudioFile(it.id, chapterNo) } - val quranNeedsDownload = shouldPlayArabic && quranFile != null && quranFile.length() == 0L - val translationNeedsDownload = + val quranNeedsStream = + shouldPlayArabic && quranFile != null && quranFile.length() == 0L + val translationNeedsStream = shouldPlayTranslation && translationFile != null && translationFile.length() == 0L try { @@ -131,73 +135,10 @@ class RecitationAudioRepository(private val context: Context) { translationModel?.let { resolveChapterTimingMetadata(it, chapterNo) } } - if (quranNeedsDownload || translationNeedsDownload) { + if (quranNeedsStream || translationNeedsStream) { if (!NetworkStateReceiver.isNetworkConnected(context)) { throw NoInternetException() } - - val progressByReciterChapter = mutableMapOf() - - suspend fun emitCombinedDownloadProgress() { - emit( - ResolvedAudioResult.Downloading( - combineChapterDownloadProgress( - progressByReciterChapter, - chapterNo, - quranNeedsDownload, - translationNeedsDownload, - quranModel?.id, - translationModel?.id, - ), - ), - ) - } - - emitCombinedDownloadProgress() - - if (quranNeedsDownload) { - val url = prepareAudioUrl(quranModel.urlTemplate, chapterNo) - ?: throw IllegalStateException("Failed to prepare audio URL") - - downloadAudioWithWorker( - reciterId = quranModel.id, - chapterNo = chapterNo, - url = url, - destinationFile = quranFile, - title = "Downloading recitation", - subtitle = quranModel.reciter, - audioKind = RecitationAudioKind.QURAN, - onProgress = { p -> - progressByReciterChapter[ - downloadProgressKey(quranModel.id, chapterNo), - ] = p - emitCombinedDownloadProgress() - }, - ) - } - - if (translationNeedsDownload) { - val url = prepareAudioUrl(translationModel.urlTemplate, chapterNo) - ?: throw IllegalStateException("Failed to prepare translation audio URL") - - downloadAudioWithWorker( - reciterId = translationModel.id, - chapterNo = chapterNo, - url = url, - destinationFile = translationFile, - title = "Downloading recitation translation", - subtitle = translationModel.reciter, - audioKind = RecitationAudioKind.TRANSLATION, - onProgress = { p -> - progressByReciterChapter[ - downloadProgressKey(translationModel.id, chapterNo), - ] = p - emitCombinedDownloadProgress() - }, - ) - } - - emit(ResolvedAudioResult.Downloading(-1)) } val quranTiming = quranTimingDeferred.await() @@ -208,26 +149,57 @@ class RecitationAudioRepository(private val context: Context) { "Resolved timings for chapter $chapterNo - quran: $quranTiming, translation: $translationTiming" ) + fun resolveTrackUri( + needsStream: Boolean, + file: File?, + urlTemplate: String, + ): Uri { + if (file == null) { + throw IllegalStateException("Missing audio file path") + } + + if (!needsStream) { + return file.toUri() + } + + val url = prepareAudioUrl(urlTemplate, chapterNo) + ?: throw IllegalStateException("Failed to prepare audio URL") + + return url.toUri() + } + emit( ResolvedAudioResult.Resoved( chapter = chapterNo, - quran = quranFile?.let { file -> + quran = if (shouldPlayArabic && quranModel != null && quranFile != null) { RecitationAudioTrack( kind = RecitationAudioKind.QURAN, chapterNo = chapterNo, reciterId = quranModel.id, - audioUri = file.toUri(), + audioUri = resolveTrackUri( + needsStream = quranNeedsStream, + file = quranFile, + urlTemplate = quranModel.urlTemplate, + ), timingMetadata = quranTiming, ) + } else { + null }, - translation = translationFile?.let { file -> + translation = if (shouldPlayTranslation && translationModel != null && translationFile != null) { RecitationAudioTrack( kind = RecitationAudioKind.TRANSLATION, chapterNo = chapterNo, reciterId = translationModel.id, - audioUri = file.toUri(), + audioUri = resolveTrackUri( + needsStream = translationNeedsStream, + file = translationFile, + urlTemplate = translationModel.urlTemplate, + ), timingMetadata = translationTiming, ) + } else { + null }, ), ) @@ -238,103 +210,6 @@ class RecitationAudioRepository(private val context: Context) { } }.flowOn(Dispatchers.IO) - private suspend fun downloadAudioWithWorker( - reciterId: String, - chapterNo: Int, - url: String, - destinationFile: File, - title: String, - subtitle: String?, - audioKind: RecitationAudioKind, - onProgress: suspend (Int) -> Unit, - ) { - val workName = RecitationAudioDownloadWorker.uniqueWorkName(reciterId, chapterNo) - - workManager.enqueueUniqueWork( - workName, - ExistingWorkPolicy.KEEP, - RecitationAudioDownloadWorker.oneTimeRequest( - url = url, - outputPath = destinationFile.absolutePath, - title = title, - subtitle = subtitle, - isBulkChild = false, - reciterId = reciterId, - audioKind = audioKind, - ) - ) - - awaitWorkCompletion(workName, onProgress) - } - - private suspend fun awaitWorkCompletion( - workName: String, - onProgress: suspend (Int) -> Unit, - ) { - workManager.getWorkInfosForUniqueWorkLiveData(workName).asFlow() - .mapNotNull { infos -> infos.firstOrNull() } - .first { info -> - val p = info.progress.getInt(RecitationAudioDownloadWorker.KEY_PROGRESS, -1) - if (p in 0..100) { - onProgress(p) - } - - when (info.state) { - WorkInfo.State.SUCCEEDED -> true - WorkInfo.State.FAILED -> { - val message = - info.outputData.getString(RecitationAudioDownloadWorker.KEY_ERROR) - ?: "Download failed" - throw IllegalStateException(message) - } - - WorkInfo.State.CANCELLED -> { - throw IllegalStateException("Download cancelled") - } - - else -> false - } - } - } - - - private fun downloadProgressKey(reciterId: String, chapterNo: Int) = "$reciterId:$chapterNo" - - /** - * Merges per-reciter progress into one 0–100 value. When both Quran and translation download, - * each file fills half the range so the bar does not reset between workers. - */ - private fun combineChapterDownloadProgress( - progressByKey: Map, - chapterNo: Int, - quranNeedsDownload: Boolean, - translationNeedsDownload: Boolean, - quranReciterId: String?, - translationReciterId: String?, - ): Int { - return when { - quranNeedsDownload && translationNeedsDownload -> { - val qid = quranReciterId ?: return 0 - val tid = translationReciterId ?: return 0 - val q = progressByKey[downloadProgressKey(qid, chapterNo)] ?: 0 - val t = progressByKey[downloadProgressKey(tid, chapterNo)] ?: 0 - (q + t) / 2 - } - - quranNeedsDownload -> { - val qid = quranReciterId ?: return 0 - progressByKey[downloadProgressKey(qid, chapterNo)] ?: 0 - } - - translationNeedsDownload -> { - val tid = translationReciterId ?: return 0 - progressByKey[downloadProgressKey(tid, chapterNo)] ?: 0 - } - - else -> 0 - } - } - suspend fun resolveChapterTimingMetadata( model: RecitationModelBase, chapterNo: Int, diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationService.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationService.kt index 6275043d4..ffebdc3a0 100644 --- a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationService.kt +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationService.kt @@ -17,8 +17,15 @@ import androidx.media3.common.MediaMetadata import androidx.media3.common.PlaybackException import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi +import androidx.media3.database.StandaloneDatabaseProvider +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.datasource.cache.CacheDataSource +import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor +import androidx.media3.datasource.cache.SimpleCache import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.session.CommandButton import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService @@ -60,6 +67,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import java.io.File @OptIn(UnstableApi::class) class RecitationService : MediaSessionService() { @@ -84,6 +92,37 @@ class RecitationService : MediaSessionService() { private const val SEEK_STEP_MS = 5000L + /** Max bytes for HTTP chapter audio in [SimpleCache] (LRU evicted when full). */ + private const val RECITATION_CACHE_MAX_BYTES = 512L * 1024 * 1024 + + @Volatile + private var recitationSimpleCache: SimpleCache? = null + + private val recitationCacheLock = Any() + + /** + * Process-wide cache for streamed `https` chapter audio. Persists across [RecitationService] + * instances; not cleared on service destroy. + */ + private fun recitationCache(context: android.content.Context): SimpleCache { + synchronized(recitationCacheLock) { + recitationSimpleCache?.let { return it } + + val appCtx = context.applicationContext + val dir = File(appCtx.cacheDir, "exo_recitation_cache") + + if (!dir.exists()) { + dir.mkdirs() + } + + val evictor = LeastRecentlyUsedCacheEvictor(RECITATION_CACHE_MAX_BYTES) + + val dbProvider = StandaloneDatabaseProvider(appCtx) + + return SimpleCache(dir, evictor, dbProvider).also { recitationSimpleCache = it } + } + } + val sharedState = MutableStateFlow(RecitationServiceState.EMPTY) } @@ -185,13 +224,23 @@ class RecitationService : MediaSessionService() { private fun initializePlayer() { val loadControl = DefaultLoadControl.Builder().setBufferDurationsMs( - /* minBufferMs */ 15_000, - /* maxBufferMs */ 30_000, - /* bufferForPlaybackMs */ 1_500, - /* bufferForPlaybackAfterRebufferMs */ 3_000, + 15_000, + 30_000, + 1_500, + 3_000, ).build() + val cache = recitationCache(this) + val cacheDataSourceFactory = CacheDataSource.Factory() + .setCache(cache) + .setUpstreamDataSourceFactory(DefaultHttpDataSource.Factory()) + .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR) + + val dataSourceFactory = DefaultDataSource.Factory(this, cacheDataSourceFactory) + val mediaSourceFactory = DefaultMediaSourceFactory(dataSourceFactory) + player = ExoPlayer.Builder(this) + .setMediaSourceFactory(mediaSourceFactory) .setLoadControl(loadControl) .build().apply { setAudioAttributes( @@ -391,6 +440,10 @@ class RecitationService : MediaSessionService() { } } + /** + * Cancels WorkManager chapter downloads (e.g. bulk or explicit saves). Does not clear ExoPlayer’s + * HTTP cache (SimpleCache) used for streamed playback. + */ fun cancelLoading() { audioRepository.cancelAll() setResolving(null) @@ -480,15 +533,7 @@ class RecitationService : MediaSessionService() { audioRepository.resolveAudioUris(chapterNo).collect { result -> when (result) { is ResolvedAudioResult.Downloading -> { - if (chapterNo == state.value.resolvingChapterNo) { - if (result.progress in 0..100) { - updateState { - copy(downloadProgress = result.progress) - } - } else { - updateState { copy(downloadProgress = null) } - } - } + // Playback resolution no longer emits this; bulk/explicit downloads use WorkManager UI. } is ResolvedAudioResult.Error -> terminal = result diff --git a/app/src/main/java/com/quranapp/android/utils/tafsir/TafsirUtils.java b/app/src/main/java/com/quranapp/android/utils/tafsir/TafsirUtils.java deleted file mode 100644 index 0232804e6..000000000 --- a/app/src/main/java/com/quranapp/android/utils/tafsir/TafsirUtils.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.quranapp.android.utils.tafsir; - -import androidx.annotation.Nullable; - -import com.quranapp.android.api.models.tafsir.TafsirInfoModel; -import com.quranapp.android.utils.app.AppUtils; -import com.quranapp.android.utils.reader.tafsir.TafsirManager; -import com.quranapp.android.utils.univ.FileUtils; - -import java.util.List; -import java.util.Map; - -public class TafsirUtils { - public static final String DIR_NAME = FileUtils.createPath(AppUtils.BASE_APP_DOWNLOADED_SAVED_DATA_DIR, "tafsirs"); - public static final String AVAILABLE_TAFSIRS_FILENAME = "available_tafsirs_v2.json"; - public static final String KEY_TAFSIR = "key.tafsir"; - - @Nullable - public static String getTafsirName(String key) { - if (key == null) { - return null; - } - - TafsirInfoModel model = TafsirManager.getModel(key); - if (model == null) { - return null; - } - - return model.getName(); - } - - public static String getDefaultTafsirKey() { - Map> models = TafsirManager.getModels(); - if (models == null) { - return null; - } - - List tafsirs = models.get("en"); - - if (tafsirs == null || tafsirs.isEmpty()) { - return null; - } - - return tafsirs.get(0).getKey(); - } - - public static boolean isUrdu(String key) { - TafsirInfoModel model = TafsirManager.getModel(key); - if (model == null) { - return false; - } - - return model.getLangCode().equals("ur"); - } - - public static boolean isArabic(String key) { - TafsirInfoModel model = TafsirManager.getModel(key); - if (model == null) { - return false; - } - - return model.getLangCode().equals("ar"); - } -} diff --git a/app/src/main/java/com/quranapp/android/utils/tafsir/TafsirUtils.kt b/app/src/main/java/com/quranapp/android/utils/tafsir/TafsirUtils.kt new file mode 100644 index 000000000..c8ccd3ddb --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/tafsir/TafsirUtils.kt @@ -0,0 +1,49 @@ +package com.quranapp.android.utils.tafsir + +import com.quranapp.android.api.models.tafsir.TafsirModel +import com.quranapp.android.compose.utils.appFallbackLanguageCodes +import com.quranapp.android.utils.app.AppUtils +import com.quranapp.android.utils.reader.tafsir.TafsirManager +import com.quranapp.android.utils.univ.FileUtils + +object TafsirUtils { + @JvmField + val DIR_NAME: String = + FileUtils.createPath(AppUtils.BASE_APP_DOWNLOADED_SAVED_DATA_DIR, "tafsirs") + + const val AVAILABLE_TAFSIRS_FILENAME = "available_tafsirs_v2.json" + const val KEY_TAFSIR = "key.tafsir" + + fun getDefaultTafsirKey(): String? { + val models = TafsirManager.getModels()?.takeIf { it.isNotEmpty() } ?: return null + + val idealModels = appFallbackLanguageCodes().firstNotNullOfOrNull { + models[it] + } ?: models["en"] + + return idealModels?.firstOrNull()?.key + } + + fun isUrdu(key: String): Boolean { + val model = TafsirManager.getModel(key) ?: return false + return model.langCode == "ur" + } + + fun isArabic(key: String): Boolean { + val model = TafsirManager.getModel(key) ?: return false + return model.langCode == "ar" + } + + fun tafsirVerseRangeInChapter(tafsir: TafsirModel, chapterNo: Int): IntRange? { + val nums = tafsir.verses.mapNotNull { key -> + val parts = key.split(":") + if (parts.size < 2) return@mapNotNull null + val surah = parts[0].toIntOrNull() ?: return@mapNotNull null + val ayah = parts[1].toIntOrNull() ?: return@mapNotNull null + if (surah == chapterNo) ayah else null + } + if (nums.isEmpty()) return null + val sorted = nums.sorted() + return sorted.first()..sorted.last() + } +} diff --git a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt index 96ecb638e..bf90e17e2 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt @@ -412,7 +412,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic fun updateLastKnownVerseFromItems(firstVisibleIndex: Int) { - val items = verseByVerseItems.value + val items = _verseByVersePrepared.value.items for (i in firstVisibleIndex until items.size) { val item = items[i] @@ -584,7 +584,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic } ReaderMode.VerseByVerse -> { - val isInView = verseByVerseItems.value.any { item -> + val isInView = _verseByVersePrepared.value.items.any { item -> item is ReaderLayoutItem.VerseUI && item.verse.chapterNo == chapterNo && item.verse.verseNo == verseNo @@ -736,8 +736,6 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic lastKnownVerse?.takeIf { it.isValid } } - Log.d("TRYING RESTORE", verseFromMemory) - val versePair = verseFromMemory ?: run { val oldMushafId = oldLayoutKey.scriptCode.toQuranMushafId(oldLayoutKey.variant) val currentPage = pageInPreviousMushaf @@ -750,14 +748,10 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic ChapterVersePair(c, v) } - Log.d("TRYING RESTORE", versePair) - val newPage = withContext(Dispatchers.IO) { repository.getPageForVerse(versePair.chapterNo, versePair.verseNo) } ?: return - Log.d("TRYING RESTORE", newPage) - withContext(Dispatchers.Main) { lastKnownVerse = versePair _uiState.update { it.copy(currentPageNo = newPage) } diff --git a/app/src/main/java/com/quranapp/android/viewModels/TafsirReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/TafsirReaderViewModel.kt index a4fc5c9c4..14d26caf1 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/TafsirReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/TafsirReaderViewModel.kt @@ -6,10 +6,12 @@ import androidx.lifecycle.viewModelScope import com.quranapp.android.api.RetrofitInstance import com.quranapp.android.api.models.tafsir.TafsirInfoModel import com.quranapp.android.api.models.tafsir.TafsirModel +import com.quranapp.android.components.reader.ChapterVersePair import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.DatabaseProvider import com.quranapp.android.db.relations.SurahWithLocalizations import com.quranapp.android.db.tafsir.QuranTafsirDBHelper +import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.reader.tafsir.TafsirManager import com.quranapp.android.utils.receivers.NetworkStateReceiver import com.quranapp.android.utils.tafsir.TafsirUtils @@ -82,8 +84,8 @@ class TafsirReaderViewModel(application: Application) : AndroidViewModel(applica } is TafsirReaderEvent.ChangeTafsir -> changeTafsir(event.tafsirKey) - is TafsirReaderEvent.NextVerse -> navigateVerse(1) - is TafsirReaderEvent.PreviousVerse -> navigateVerse(-1) + is TafsirReaderEvent.NextVerse -> navigateByTafsirGroup(1) + is TafsirReaderEvent.PreviousVerse -> navigateByTafsirGroup(-1) is TafsirReaderEvent.Retry -> loadTafsirContent() is TafsirReaderEvent.UpdateTextSize -> updateTextSize(event.multiplier) } @@ -151,20 +153,99 @@ class TafsirReaderViewModel(application: Application) : AndroidViewModel(applica loadTafsirContent() } - private fun navigateVerse(delta: Int) { + /** + * Moves by whole tafsir blocks: next goes to the first ayah after the current block; + * previous goes to the first ayah of the block before the current one. + */ + private fun navigateByTafsirGroup(delta: Int) { val state = _uiState.value - val newVerseNo = state.verseNo + delta + if (state.chapterNo < 1 || state.verseNo < 1) return - if (newVerseNo < 1 || newVerseNo > (state.chapterMeta?.surah?.ayahCount ?: 0)) return + viewModelScope.launch { + val coord = withContext(Dispatchers.IO) { + resolveGroupNavigationTarget(state, delta) + } ?: return@launch - _uiState.update { - it.copy( - verseNo = newVerseNo, - contentState = TafsirContentState.Loading - ) + val newMeta = if (coord.chapterNo != state.chapterNo) { + withContext(Dispatchers.IO) { + repository.getSurahWithLocalizations(coord.chapterNo) + } + } else { + state.chapterMeta + } + + _uiState.update { + it.copy( + chapterNo = coord.chapterNo, + verseNo = coord.verseNo, + chapterMeta = newMeta ?: it.chapterMeta, + contentState = TafsirContentState.Loading + ) + } + + loadTafsirContent() } + } - loadTafsirContent() + private suspend fun resolveGroupNavigationTarget( + state: TafsirReaderUiState, + delta: Int, + ): ChapterVersePair? { + val tafsirKey = state.tafsirKey + val chapterNo = state.chapterNo + val verseNo = state.verseNo + + val totalVerses = repository.getSurahWithLocalizations(chapterNo)?.surah?.ayahCount + ?: return null + if (totalVerses < 1) return null + + val lastChapter = QuranMeta.chapterRange.last + + val dbHelper = QuranTafsirDBHelper(context) + try { + val currentModel: TafsirModel? = when (val c = state.contentState) { + is TafsirContentState.Success -> c.tafsir + else -> dbHelper.getTafsirByVerse(tafsirKey, chapterNo, verseNo) + } + + val range = currentModel?.let { TafsirUtils.tafsirVerseRangeInChapter(it, chapterNo) } + ?: (verseNo..verseNo) + + return when (delta) { + 1 -> { + val next = range.last + 1 + when { + next <= totalVerses -> ChapterVersePair(chapterNo, next) + chapterNo < lastChapter -> ChapterVersePair(chapterNo + 1, 1) + else -> null + } + } + + -1 -> { + val first = range.first + + if (first > 1) { + val beforeCurrentGroup = first - 1 + ChapterVersePair(chapterNo, beforeCurrentGroup) + } else if (chapterNo > 1) { + val prevChapter = chapterNo - 1 + val prevTotal = repository.getSurahWithLocalizations(prevChapter) + ?.surah?.ayahCount + ?: return null + + if (prevTotal < 1) return null + + ChapterVersePair(prevChapter, prevTotal) + } else { + null + } + } + + else -> null + } + } finally { + dbHelper.close() + } } private fun updateTextSize(multiplier: Float) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c26fe684..f67718da5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,6 +105,7 @@ Download Recitations Select Reciter + Download recitation audio for offline listening and smoother playback. Download Recitations Chapter %d %1$d / %2$d chapters @@ -257,6 +258,7 @@ Downloading fonts Extracting fonts Downloading… + Preparing audio… Last crash log App logs Crash logs diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b5d5226ca..a4ff1f1ac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -70,6 +70,8 @@ extensionMediasession = { module = "com.google.android.exoplayer:extension-media # AndroidX Media3 media3ExoPlayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" } +media3Datasource = { module = "androidx.media3:media3-datasource", version.ref = "media3" } +media3Database = { module = "androidx.media3:media3-database", version.ref = "media3" } media3Session = { module = "androidx.media3:media3-session", version.ref = "media3" } media3UI = { module = "androidx.media3:media3-ui", version.ref = "media3" } From 32005632316d5a4bbb3f13cec5e125de72cdffed Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Sun, 19 Apr 2026 22:04:16 +0530 Subject: [PATCH 08/16] Tafsir share / Verse navigator on tafsir screen and player --- app/src/main/assets/tafsir/tafsir_page.css | 6 - .../compose/components/VerseOfTheDay.kt | 13 +- .../compose/components/player/Thumbnail.kt | 75 ++-- .../reader/navigator/ChapterVerseNavigator.kt | 329 ++++++++++++++++++ .../screens/tafsir/TafsirReaderScreen.kt | 112 +++++- .../viewModels/ChapterNavigatorViewModel.kt | 19 + .../viewModels/RecitationPlayerViewModel.kt | 2 + .../viewModels/TafsirReaderViewModel.kt | 2 +- 8 files changed, 510 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ChapterVerseNavigator.kt create mode 100644 app/src/main/java/com/quranapp/android/viewModels/ChapterNavigatorViewModel.kt diff --git a/app/src/main/assets/tafsir/tafsir_page.css b/app/src/main/assets/tafsir/tafsir_page.css index 7f4d81dda..444149b65 100644 --- a/app/src/main/assets/tafsir/tafsir_page.css +++ b/app/src/main/assets/tafsir/tafsir_page.css @@ -9,12 +9,6 @@ src: url('https://assets-font/content') format("truetype"); } -* { - user-select: none; - -webkit-user-select: none; - -webkit-tap-highlight-color: transparent; -} - body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; diff --git a/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt b/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt index a74ac40f2..cf37a39ac 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt @@ -363,7 +363,7 @@ private fun VotdContent() { R.drawable.ic_bookmark } ), - contentDescription = null, + contentDescription = stringResource(R.string.strLabelBookmark), tint = if (isBookmarked) colorScheme.primary else iconTint, small = true, ) { @@ -377,13 +377,22 @@ private fun VotdContent() { painter = painterResource( if (votdEnabled) R.drawable.ic_bell_ring else R.drawable.ic_bell ), - contentDescription = null, + contentDescription = stringResource(R.string.dailyReminderMsg), tint = if (votdEnabled) colorScheme.primary else iconTint, small = true, ) { showDailyReminderSheet = true } + IconButton( + painter = painterResource(R.drawable.dr_icon_tafsir), + contentDescription = stringResource(R.string.strTitleTafsir), + tint = null, + small = true, + ) { + ReaderFactory.startTafsir(context, verse.chapterNo, verse.verseNo) + } + Spacer(Modifier.weight(1f)) FilledTonalButton( diff --git a/app/src/main/java/com/quranapp/android/compose/components/player/Thumbnail.kt b/app/src/main/java/com/quranapp/android/compose/components/player/Thumbnail.kt index 1a46d3da9..8baeffd62 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/player/Thumbnail.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/player/Thumbnail.kt @@ -1,23 +1,23 @@ package com.quranapp.android.compose.components.player -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -31,7 +31,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -40,10 +39,12 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel import com.quranapp.android.R import com.quranapp.android.components.reader.ChapterVersePair import com.quranapp.android.compose.components.ChapterIcon -import com.quranapp.android.db.DatabaseProvider +import com.quranapp.android.compose.components.reader.navigator.ChapterVerseNavigator +import com.quranapp.android.viewModels.RecitationPlayerViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -52,15 +53,15 @@ fun ExtendedThumbnail( verse: ChapterVersePair, modifier: Modifier = Modifier, ) { - val context = LocalContext.current val headerShape = RoundedCornerShape(32.dp) + val viewModel = viewModel() - val repository = remember(context) { DatabaseProvider.getQuranRepository(context) } + var showChapterVerseNavigator by remember { mutableStateOf(false) } var chapterName by remember { mutableStateOf("") } LaunchedEffect(verse.chapterNo) { chapterName = withContext(Dispatchers.IO) { - repository.getChapterName(verse.chapterNo) + viewModel.repository.getChapterName(verse.chapterNo) } } @@ -162,8 +163,6 @@ fun ExtendedThumbnail( medium -> 14.dp else -> 22.dp } - val badgeHorizontalPadding: Dp = if (compact) 10.dp else 14.dp - val badgeVerticalPadding: Dp = if (compact) 4.dp else 6.dp Column( modifier = Modifier @@ -206,26 +205,50 @@ fun ExtendedThumbnail( Spacer(Modifier.height(titleToBadgeGap)) - Surface( - shape = RoundedCornerShape(999.dp), - color = Color.White.copy(alpha = 0.1f), - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = BorderStroke(1.dp, Color.White.copy(alpha = 0.08f)) + Box( + modifier = Modifier + .clip(RoundedCornerShape(999.dp)) + .background(Color.White.copy(alpha = 0.1f)) + .border(1.dp, Color.White.copy(alpha = 0.08f), RoundedCornerShape(999.dp)) + .clickable { + showChapterVerseNavigator = true + } + .padding( + horizontal = 10.dp, + vertical = 6.dp, + ) ) { - Text( - text = stringResource(R.string.strLabelVerseNo, verse.verseNo), - modifier = Modifier.padding( - horizontal = badgeHorizontalPadding, - vertical = badgeVerticalPadding, - ), - color = Color.White.copy(alpha = 0.88f), - style = MaterialTheme.typography.labelMedium, - fontWeight = FontWeight.Bold - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = stringResource(R.string.strLabelVerseNo, verse.verseNo), + color = Color.White.copy(alpha = 0.88f), + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold + ) + + Icon( + painterResource(R.drawable.dr_icon_chevron_down), + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(14.dp) + ) + } } } } + + ChapterVerseNavigator( + isOpen = showChapterVerseNavigator, + onDismiss = { showChapterVerseNavigator = false }, + selectedChapterNo = verse.chapterNo, + selectedVerseNos = setOf(verse.verseNo), + onVerseSelected = { chapterNo, verseNo -> + viewModel.controller.start(ChapterVersePair(chapterNo, verseNo)) + }, + ) } diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ChapterVerseNavigator.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ChapterVerseNavigator.kt new file mode 100644 index 000000000..18b99b5f1 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ChapterVerseNavigator.kt @@ -0,0 +1,329 @@ +package com.quranapp.android.compose.components.reader.navigator + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.quranapp.android.R +import com.quranapp.android.compose.components.common.Loader +import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.db.entities.quran.SurahEntity +import com.quranapp.android.db.relations.SurahWithLocalizations +import com.quranapp.android.repository.QuranRepository +import com.quranapp.android.viewModels.ChapterNavigatorViewModel +import verticalFadingEdge + +private val VerseSelectionSaver: Saver, List> = Saver( + save = { it.sorted() }, + restore = { it.toSet() }, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChapterVerseNavigator( + isOpen: Boolean, + onDismiss: () -> Unit, + selectedChapterNo: Int? = null, + selectedVerseNos: Set = emptySet(), + /** + * If provided, verse selector will not be visible + */ + onChapterSelected: ((Int) -> Unit)? = null, + onVerseSelected: ((chapterNo: Int, verseNo: Int) -> Unit)? = null, +) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val viewModel = viewModel() + + if (!isOpen) { + return + } + + ModalBottomSheet( + onDismissRequest = onDismiss, + sheetState = sheetState, + ) { + + Content( + viewModel, + onDismiss, + selectedChapterNo, + selectedVerseNos, + onChapterSelected, + onVerseSelected + ) + } +} + +@Composable +private fun Content( + vm: ChapterNavigatorViewModel, + onDismiss: () -> Unit, + initialChapterNo: Int?, + initialVerseNos: Set, + onChapterSelected: ((Int) -> Unit)?, + onVerseSelected: ((chapterNo: Int, verseNo: Int) -> Unit)?, +) { + val surahs by vm.surahs.collectAsState() + + var selectedChapterNo by rememberSaveable { + mutableStateOf(initialChapterNo) + } + + var selectedVerseNos by rememberSaveable(stateSaver = VerseSelectionSaver) { + mutableStateOf(initialVerseNos) + } + + LaunchedEffect(initialChapterNo, initialVerseNos) { + selectedChapterNo = initialChapterNo + selectedVerseNos = initialVerseNos + } + + if (surahs.isEmpty()) { + Loader(true) + return + } + + val showVerseSelector = onChapterSelected == null + + Column( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.92f) + .navigationBarsPadding() + ) { + Row { + ChapterOnlyList( + repository = vm.repository, + surahs = surahs, + selectedChapterNo = selectedChapterNo, + onChapterSelected = { chapterNo -> + if (onChapterSelected != null) { + onChapterSelected(chapterNo) + onDismiss() + } else { + selectedChapterNo = chapterNo + selectedVerseNos = if (chapterNo == initialChapterNo) initialVerseNos + else emptySet() + } + }, + ) + + if (showVerseSelector) { + val chapterNo = selectedChapterNo + if (chapterNo != null) { + ChapterVerseMultiSelectList( + currentChapter = chapterNo.let { no -> surahs.getOrNull(no - 1)?.surah }, + selectedVerseNos = selectedVerseNos, + onToggleVerse = { verseNo -> + onVerseSelected?.invoke(chapterNo, verseNo) + onDismiss() + }, + ) + } + } + } + } +} + +@Composable +private fun RowScope.ChapterOnlyList( + repository: QuranRepository, + surahs: List, + selectedChapterNo: Int?, + onChapterSelected: (Int) -> Unit, +) { + val gridState = rememberLazyGridState( + initialFirstVisibleItemIndex = selectedChapterNo?.let { it - 1 } ?: 0, + initialFirstVisibleItemScrollOffset = -100, + ) + + var filteredSurahs by remember { mutableStateOf(surahs) } + var searchQuery by rememberSaveable { mutableStateOf("") } + + LaunchedEffect(searchQuery, surahs) { + val query = searchQuery.lowercase().trim() + + if (query.isEmpty()) { + filteredSurahs = surahs + } else { + val surahNos = repository.searchSurahNos(query) + + filteredSurahs = surahs.filter { surah -> + surah.surah.surahNo in surahNos + } + + gridState.requestScrollToItem(0) + } + } + + Column(Modifier.weight(1f)) { + Box( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp), + ) { + FilterField( + value = searchQuery, + onValueChange = { searchQuery = it }, + hint = stringResource(R.string.strHintSearchChapter), + keyboardType = KeyboardType.Text, + ) + } + + BoxWithConstraints(Modifier.verticalFadingEdge(gridState)) { + LazyVerticalGrid( + columns = GridCells.Fixed(if (maxWidth < 800.dp) 1 else 2), + modifier = Modifier.fillMaxWidth(), + state = gridState, + contentPadding = PaddingValues( + start = 16.dp, + end = 16.dp, + top = 16.dp, + bottom = 64.dp, + ), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + items(filteredSurahs, key = { it.surah.surahNo }) { surah -> + ChapterCard( + surah, + isCurrent = selectedChapterNo == surah.surah.surahNo, + iconWithPrefix = false, + onClick = { onChapterSelected(surah.surah.surahNo) }, + ) + } + } + } + } +} + +@Composable +private fun ChapterVerseMultiSelectList( + currentChapter: SurahEntity?, + selectedVerseNos: Set, + onToggleVerse: (Int) -> Unit, +) { + if (currentChapter == null) return + + val state = rememberLazyListState( + initialFirstVisibleItemIndex = selectedVerseNos.firstOrNull() ?: 0, + initialFirstVisibleItemScrollOffset = -200, + ) + + val ayahs = (1..currentChapter.ayahCount).toList() + var searchQuery by rememberSaveable { mutableStateOf("") } + var filteredAyahs by remember { mutableStateOf(ayahs) } + + LaunchedEffect(currentChapter) { + state.requestScrollToItem(selectedVerseNos.firstOrNull() ?: 0, -200) + } + + LaunchedEffect(ayahs, searchQuery) { + val query = searchQuery.lowercase().trim() + + if (query.isEmpty()) { + filteredAyahs = ayahs + } else { + filteredAyahs = ayahs.filter { ayahNo -> + ayahNo.toString().contains(query) + } + state.requestScrollToItem(0) + } + } + + Column(modifier = Modifier.width(100.dp)) { + Box( + modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp), + ) { + FilterField( + value = searchQuery, + onValueChange = { searchQuery = it }, + hint = stringResource(R.string.strHintSearch), + keyboardType = KeyboardType.Number, + showLeading = false, + ) + } + + Box(Modifier.verticalFadingEdge(state)) { + LazyColumn( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(6.dp), + contentPadding = PaddingValues( + start = 8.dp, + end = 8.dp, + top = 8.dp, + bottom = 64.dp, + ), + state = state, + ) { + items(filteredAyahs) { verseNo -> + val selected = verseNo in selectedVerseNos + + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = colorScheme.surface, + ), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp), + border = BorderStroke( + width = 1.dp, + color = if (selected) colorScheme.primary + else colorScheme.outlineVariant.alpha(0.5f), + ), + ) { + Text( + stringResource(R.string.strLabelVerseNo, verseNo), + modifier = Modifier + .clickable { onToggleVerse(verseNo) } + .padding(10.dp), + style = typography.bodyMedium.copy( + fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, + ), + color = if (selected) colorScheme.primary else colorScheme.onSurface, + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/quranapp/android/compose/screens/tafsir/TafsirReaderScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/tafsir/TafsirReaderScreen.kt index f31d8fe51..a5ad3d097 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/tafsir/TafsirReaderScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/tafsir/TafsirReaderScreen.kt @@ -1,6 +1,7 @@ package com.quranapp.android.compose.screens.tafsir import ThemeUtils +import android.content.Context import android.content.Intent import android.view.View import android.webkit.WebChromeClient @@ -26,7 +27,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon @@ -56,11 +56,15 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.text.HtmlCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import com.peacedesign.android.utils.AppBridge import com.quranapp.android.R import com.quranapp.android.activities.ActivitySettings +import com.quranapp.android.api.models.tafsir.TafsirModel import com.quranapp.android.compose.components.common.AppBar +import com.quranapp.android.compose.components.reader.navigator.ChapterVerseNavigator import com.quranapp.android.compose.navigation.SettingRoutes import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.tafsir.TafsirUtils @@ -82,14 +86,24 @@ fun TafsirReaderScreen( val uiState by viewModel.uiState.collectAsStateWithLifecycle() var webViewScrollY by remember { mutableIntStateOf(0) } var webViewRef by remember { mutableStateOf(null) } + var showChapterVerseNavigator by remember { mutableStateOf(false) } val onEvent = viewModel::onEvent + val context = LocalContext.current Scaffold( modifier = modifier, topBar = { TafsirTopBar( uiState = uiState, + onOpenChapterVerseNavigator = { showChapterVerseNavigator = true }, + onShare = when (val s = uiState.contentState) { + is TafsirContentState.Success -> { + { shareTafsir(context, uiState, s.tafsir) } + } + + else -> null + }, ) }, bottomBar = { @@ -176,12 +190,41 @@ fun TafsirReaderScreen( } } } + + val initialVerseNos = remember(uiState.chapterNo, uiState.verseNo, uiState.contentState) { + when (val c = uiState.contentState) { + is TafsirContentState.Success -> { + TafsirUtils.tafsirVerseRangeInChapter(c.tafsir, uiState.chapterNo) + ?.let { range -> (range.first..range.last).toSet() } + ?: setOf(uiState.verseNo) + } + + else -> setOf(uiState.verseNo) + } + } + + ChapterVerseNavigator( + isOpen = showChapterVerseNavigator, + onDismiss = { showChapterVerseNavigator = false }, + selectedChapterNo = uiState.chapterNo.takeIf { it >= 1 }, + selectedVerseNos = initialVerseNos, + onVerseSelected = { chapterNo, verseNo -> + viewModel.onEvent( + TafsirReaderEvent.Init( + uiState.tafsirKey.ifBlank { null }, + chapterNo, + verseNo, + ) + ) + }, + ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable private fun TafsirTopBar( uiState: TafsirReaderUiState, + onOpenChapterVerseNavigator: () -> Unit, + onShare: (() -> Unit)? = null, ) { val context = LocalContext.current val chapterName = uiState.chapterMeta?.getCurrentName() ?: "" @@ -193,8 +236,8 @@ private fun TafsirTopBar( Column( modifier = Modifier .fillMaxWidth() + .clickable(onClick = onOpenChapterVerseNavigator) .padding(horizontal = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = uiState.tafsirInfo?.name ?: "", @@ -206,17 +249,33 @@ private fun TafsirTopBar( textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(2.dp)) - Text( - text = "$chapterName $chapterNo:$verseNo", - style = MaterialTheme.typography.bodyMedium, - color = colorScheme.primary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center - ) + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "$chapterName $chapterNo:$verseNo", + style = MaterialTheme.typography.bodyMedium, + color = colorScheme.primary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center + ) + Icon( + painterResource(R.drawable.dr_icon_chevron_down), + contentDescription = null, + modifier = Modifier.size(14.dp) + ) + } } }, actions = { + if (onShare != null) { + IconButton(onClick = onShare) { + Icon( + painter = painterResource(R.drawable.dr_icon_share), + contentDescription = stringResource(R.string.strLabelShare), + tint = colorScheme.onSurface, + ) + } + } IconButton( onClick = { val intent = Intent(context, ActivitySettings::class.java).apply { @@ -480,8 +539,8 @@ private fun TafsirBottomNavigation( val hasPrevious = uiState.chapterNo > 1 || groupFirst > 1 val hasNext = totalVerses > 0 && - (groupLast < totalVerses || - (uiState.chapterNo < lastChapter && groupLast >= totalVerses)) + (groupLast < totalVerses || + (uiState.chapterNo < lastChapter && groupLast >= totalVerses)) Surface( color = colorScheme.surfaceContainer, @@ -584,3 +643,30 @@ private fun NavigationButton( } } } + + +private fun shareTafsir( + context: Context, + uiState: TafsirReaderUiState, + tafsir: TafsirModel, +) { + val plain = HtmlCompat.fromHtml(tafsir.text, HtmlCompat.FROM_HTML_MODE_LEGACY) + .toString() + .replace('\u00A0', ' ') + .trim() + if (plain.isEmpty()) return + + val chapterName = uiState.chapterMeta?.getCurrentName() ?: "" + val ref = "${uiState.chapterNo}:${uiState.verseNo}" + val text = buildString { + uiState.tafsirInfo?.name?.let { append(it).append('\n') } + append(chapterName).append(' ').append(ref).append("\n\n") + append(plain) + } + + AppBridge.newSharer(context) + .setData(text) + .setChooserTitle(context.getString(R.string.strLabelShare)) + .setPlatform(AppBridge.Platform.SYSTEM_SHARE) + .share() +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/viewModels/ChapterNavigatorViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ChapterNavigatorViewModel.kt new file mode 100644 index 000000000..6f8d2681e --- /dev/null +++ b/app/src/main/java/com/quranapp/android/viewModels/ChapterNavigatorViewModel.kt @@ -0,0 +1,19 @@ +package com.quranapp.android.viewModels + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.quranapp.android.db.DatabaseProvider +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn + +class ChapterNavigatorViewModel(application: Application) : AndroidViewModel(application) { + val repository = DatabaseProvider.getQuranRepository(application) + + val surahs = repository.getAllSurahs() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = emptyList() + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/viewModels/RecitationPlayerViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/RecitationPlayerViewModel.kt index 32c28ca5b..7d0db8fb3 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/RecitationPlayerViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/RecitationPlayerViewModel.kt @@ -3,6 +3,7 @@ package com.quranapp.android.viewModels import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import com.quranapp.android.db.DatabaseProvider import com.quranapp.android.utils.mediaplayer.RecitationController import com.quranapp.android.utils.mediaplayer.RecitationServiceState import kotlinx.coroutines.flow.SharingStarted @@ -12,6 +13,7 @@ import kotlinx.coroutines.flow.stateIn class RecitationPlayerViewModel(application: Application) : AndroidViewModel(application) { val controller = RecitationController.getInstance(application) + val repository = DatabaseProvider.getQuranRepository(application) init { controller.connect() diff --git a/app/src/main/java/com/quranapp/android/viewModels/TafsirReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/TafsirReaderViewModel.kt index 14d26caf1..fb92e268d 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/TafsirReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/TafsirReaderViewModel.kt @@ -61,7 +61,7 @@ class TafsirReaderViewModel(application: Application) : AndroidViewModel(applica val uiState: StateFlow = _uiState.asStateFlow() private val context get() = getApplication() - private val repository = DatabaseProvider.getQuranRepository(context) + val repository = DatabaseProvider.getQuranRepository(context) private var contentLoadJob: Job? = null From 60f8ec2324f578877786997d143e6c8fa39ea874 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Mon, 20 Apr 2026 00:20:27 +0530 Subject: [PATCH 09/16] Reader footer navigator --- .../compose/components/reader/ReaderLayout.kt | 15 + .../reader/navigator/PullNavigation.kt | 168 ++++++++++ .../reader/navigator/ReaderFooterNavigator.kt | 286 ++++++++++++++++++ .../reader/navigator/ReaderNavigator.kt | 3 +- .../android/viewModels/ReaderViewModel.kt | 9 - app/src/main/res/drawable/ic_arrow_up.xml | 10 + app/src/main/res/values/strings.xml | 7 + 7 files changed, 488 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/quranapp/android/compose/components/reader/navigator/PullNavigation.kt create mode 100644 app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderFooterNavigator.kt create mode 100644 app/src/main/res/drawable/ic_arrow_up.xml diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt index d2dc75064..c9ed0437a 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/ReaderLayout.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign @@ -36,6 +37,7 @@ import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.quranapp.android.compose.components.common.Loader +import com.quranapp.android.compose.components.reader.navigator.ReaderFooterNavigator import com.quranapp.android.db.entities.BookmarkKey import com.quranapp.android.db.entities.wbw.WbwWordEntity import com.quranapp.android.db.relations.VerseWithDetails @@ -91,6 +93,7 @@ data class ReaderPreparedData( val textStyles: Map = emptyMap(), ) + @OptIn(ExperimentalFoundationApi::class) @Composable fun ReaderLayout( @@ -160,6 +163,7 @@ private fun ReaderLayoutVerseMode( nestedScrollConnection: NestedScrollConnection, onSyncStateChanged: (Boolean) -> Unit, ) { + val density = LocalDensity.current val listState = rememberLazyListState() val prepared by readerVm.verseByVersePrepared.collectAsStateWithLifecycle() val items = prepared.items @@ -283,10 +287,21 @@ private fun ReaderLayoutVerseMode( ) { item -> TranslationRow(readerVm, item, bookmarkedVerseKeys) } + + if (uiState.viewType != null && items.isNotEmpty()) { + item(key = "verse_reader_nav_footer") { + ReaderFooterNavigator( + readerVm = readerVm, + viewType = uiState.viewType, + listState = listState, + ) + } + } } } } + @Composable private fun TranslationRow( readerVm: ReaderViewModel, diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/PullNavigation.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/PullNavigation.kt new file mode 100644 index 000000000..ac150ae33 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/PullNavigation.kt @@ -0,0 +1,168 @@ +package com.quranapp.android.compose.components.reader.navigator + +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastCoerceIn +import com.quranapp.android.R +import com.quranapp.android.utils.quran.QuranMeta +import com.quranapp.android.viewModels.ReaderViewModel +import com.quranapp.android.viewModels.ReaderViewType +import kotlinx.coroutines.launch + +@Composable +fun PullNavigation( + readerVm: ReaderViewModel, + viewType: ReaderViewType?, + content: @Composable () -> Unit +) { + val pullRefreshState = rememberPullToRefreshState() + val scope = rememberCoroutineScope() + + val density = LocalDensity.current + val shiftPx = with(density) { + (pullRefreshState.distanceFraction.fastCoerceIn( + 0f, + 1f + ) * (PULL_INDICATOR_SHIFT_DISTANCE * 2.5).dp.toPx()).toInt() + } + + + PullToRefreshBox( + state = pullRefreshState, + isRefreshing = false, + onRefresh = { + viewType?.let { vt -> + scope.launch { + adjacentDivisionLaunchParams(vt, -1)?.let { readerVm.initReader(it) } + } + } + }, + indicator = { Indicator(pullRefreshState, viewType, readerVm) } + ) { + Box( + Modifier + .fillMaxSize() + .offset { + IntOffset( + x = 0, + y = shiftPx + ) + } + ) { + content() + } + } +} + +@Composable +private fun BoxScope.Indicator( + state: PullToRefreshState, + viewType: ReaderViewType?, + readerVm: ReaderViewModel, +) { + val vt = viewType ?: return + + val labelRes = when (vt) { + is ReaderViewType.Chapter -> R.string.previousChapter + is ReaderViewType.Juz -> R.string.previousJuz + is ReaderViewType.Hizb -> R.string.previousHizb + } + + val prevEnabled = remember(vt) { + adjacentDivisionLaunchParams(vt, -1) != null + } + + val previousChapterSubtitle by produceState( + initialValue = null, + vt, + prevEnabled, + readerVm, + ) { + if (!prevEnabled || vt !is ReaderViewType.Chapter) { + value = null + return@produceState + } + val n = vt.chapterNo - 1 + value = if (n in QuranMeta.chapterRange) { + readerVm.repository.getChapterName(n) + } else { + null + } + } + + val previousSubtitle: String? = when { + !prevEnabled -> null + vt is ReaderViewType.Juz -> stringResource(R.string.strLabelJuzNo, vt.juzNo - 1) + vt is ReaderViewType.Hizb -> stringResource(R.string.labelHizbNo, vt.hizbNo - 1) + vt is ReaderViewType.Chapter -> previousChapterSubtitle + else -> null + } + + val fraction = state.distanceFraction.fastCoerceIn(0f, 1f) + if (fraction <= 0f) return + + val density = LocalDensity.current + val shiftPx = with(density) { + (fraction * PULL_INDICATOR_SHIFT_DISTANCE.dp.toPx()).toInt() + } + + Surface( + modifier = Modifier + .align(Alignment.TopCenter) + .offset { IntOffset(0, shiftPx) } + .alpha(fraction), + shape = shapes.medium, + color = colorScheme.surfaceContainer, + ) { + Column( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResource(labelRes), + style = typography.labelMedium, + color = colorScheme.onSurface.copy(alpha = 0.8f), + ) + + Text( + text = previousSubtitle ?: stringResource(R.string.noItems), + style = typography.bodySmall, + color = colorScheme.onSurface.copy(alpha = 0.5f), + maxLines = 1, + modifier = Modifier.basicMarquee( + initialDelayMillis = 900, + repeatDelayMillis = 1_200, + ), + ) + } + } +} + +const val PULL_INDICATOR_SHIFT_DISTANCE = 36 \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderFooterNavigator.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderFooterNavigator.kt new file mode 100644 index 000000000..5f1a2758a --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderFooterNavigator.kt @@ -0,0 +1,286 @@ +package com.quranapp.android.compose.components.reader.navigator + +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.quranapp.android.R +import com.quranapp.android.utils.quran.QuranMeta +import com.quranapp.android.utils.reader.ReaderIntentData +import com.quranapp.android.utils.reader.ReaderLaunchParams +import com.quranapp.android.viewModels.ReaderViewModel +import com.quranapp.android.viewModels.ReaderViewType +import kotlinx.coroutines.launch + + +@Composable +fun ReaderFooterNavigator( + readerVm: ReaderViewModel, + viewType: ReaderViewType, + listState: LazyListState, +) { + val scope = rememberCoroutineScope() + val prevEnabled = remember(viewType) { + adjacentDivisionLaunchParams(viewType, -1) != null + } + val nextEnabled = remember(viewType) { + adjacentDivisionLaunchParams(viewType, 1) != null + } + + val previousChapterSubtitle by produceState( + initialValue = null, + viewType, + prevEnabled, + readerVm, + ) { + if (!prevEnabled || viewType !is ReaderViewType.Chapter) { + value = null + return@produceState + } + + val n = viewType.chapterNo - 1 + + value = if (n in QuranMeta.chapterRange) { + readerVm.repository.getChapterName(n) + } else { + null + } + } + + val nextChapterSubtitle by produceState( + initialValue = null, + viewType, + nextEnabled, + readerVm, + ) { + if (!nextEnabled || viewType !is ReaderViewType.Chapter) { + value = null + return@produceState + } + val n = viewType.chapterNo + 1 + value = if (n in QuranMeta.chapterRange) { + readerVm.repository.getChapterName(n) + } else { + null + } + } + + val previousSubtitle: String? = when { + !prevEnabled -> null + viewType is ReaderViewType.Juz -> stringResource(R.string.strLabelJuzNo, viewType.juzNo - 1) + + viewType is ReaderViewType.Hizb -> stringResource(R.string.labelHizbNo, viewType.hizbNo - 1) + + viewType is ReaderViewType.Chapter -> previousChapterSubtitle + else -> null + } + + val nextSubtitle: String? = when { + !nextEnabled -> null + viewType is ReaderViewType.Juz -> stringResource(R.string.strLabelJuzNo, viewType.juzNo + 1) + + viewType is ReaderViewType.Hizb -> stringResource(R.string.labelHizbNo, viewType.hizbNo + 1) + + viewType is ReaderViewType.Chapter -> nextChapterSubtitle + else -> null + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 20.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + NavigationButton( + enabled = prevEnabled, + label = when (viewType) { + is ReaderViewType.Chapter -> R.string.previousChapter + is ReaderViewType.Juz -> R.string.previousJuz + is ReaderViewType.Hizb -> R.string.previousHizb + }, + subtitle = previousSubtitle, + isNext = false, + ) { + listState.requestScrollToItem(0) + + scope.launch { + adjacentDivisionLaunchParams(viewType, -1)?.let { readerVm.initReader(it) } + } + } + + Box( + modifier = Modifier + .height(56.dp) + .width(48.dp) + .clip(shapes.medium) + .background(colorScheme.surfaceContainer) + .clickable { + listState.requestScrollToItem(0) + }, + contentAlignment = Alignment.Center, + ) { + Icon( + painterResource(R.drawable.ic_arrow_up), + contentDescription = stringResource(R.string.strLabelTop), + tint = colorScheme.onSurface.copy(alpha = 0.8f) + ) + } + + NavigationButton( + enabled = nextEnabled, + label = when (viewType) { + is ReaderViewType.Chapter -> R.string.nextChapter + is ReaderViewType.Juz -> R.string.nextJuz + is ReaderViewType.Hizb -> R.string.nextHizb + }, + subtitle = nextSubtitle, + isNext = true, + ) { + scope.launch { + adjacentDivisionLaunchParams(viewType, 1)?.let { readerVm.initReader(it) } + } + } + } +} + +@Composable +private fun RowScope.NavigationButton( + enabled: Boolean, + label: Int, + subtitle: String?, + isNext: Boolean, + onClick: () -> Unit, +) { + val backgroundColor = if (enabled) { + colorScheme.surfaceContainer + } else { + colorScheme.surfaceContainer.copy(0.5f) + } + + val contentColor = if (enabled) { + colorScheme.onSurface.copy(alpha = 0.8f) + } else { + colorScheme.onSurface.copy(alpha = 0.5f) + } + + Row( + modifier = Modifier + .weight(1f) + .height(56.dp) + .clip( + shapes.medium + ) + .background(backgroundColor) + .clickable(enabled = enabled, onClick = onClick) + .padding(horizontal = 8.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp, if (isNext) Alignment.End else Alignment.Start), + verticalAlignment = Alignment.CenterVertically + ) { + if (!isNext) { + Icon( + painter = painterResource(R.drawable.dr_icon_chevron_left), + contentDescription = null, + tint = contentColor, + modifier = Modifier.size(18.dp) + ) + } + + Column( + horizontalAlignment = if (isNext) Alignment.End else Alignment.Start, + verticalArrangement = Arrangement.Center, + ) { + Text( + text = stringResource(label), + style = MaterialTheme.typography.labelMedium, + color = contentColor + ) + + if (!subtitle.isNullOrBlank()) { + Text( + subtitle, + style = typography.bodySmall, + color = colorScheme.onSurface.copy(0.5f), + maxLines = 1, + modifier = Modifier + .basicMarquee( + initialDelayMillis = 900, + repeatDelayMillis = 1_200, + ), + ) + } + } + + if (isNext) { + Icon( + painter = painterResource(R.drawable.dr_icon_chevron_right), + contentDescription = null, + tint = contentColor, + modifier = Modifier.size(18.dp) + ) + } + } +} + +internal fun adjacentDivisionLaunchParams( + viewType: ReaderViewType, + direction: Int, +): ReaderLaunchParams? { + require(direction == -1 || direction == 1) + + return when (viewType) { + is ReaderViewType.Chapter -> { + val n = viewType.chapterNo + direction + if (n in QuranMeta.chapterRange) { + ReaderLaunchParams(ReaderIntentData.FullChapter(n)) + } else { + null + } + } + + is ReaderViewType.Juz -> { + val n = viewType.juzNo + direction + if (n in QuranMeta.juzRange) { + ReaderLaunchParams(ReaderIntentData.FullJuz(n)) + } else { + null + } + } + + is ReaderViewType.Hizb -> { + val n = viewType.hizbNo + direction + if (n in QuranMeta.hizbRange) { + ReaderLaunchParams(ReaderIntentData.FullHizb(n)) + } else { + null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderNavigator.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderNavigator.kt index 263b1cbfe..ec160ebd0 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderNavigator.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderNavigator.kt @@ -21,6 +21,7 @@ import com.quranapp.android.R import com.quranapp.android.components.reader.ChapterVersePair import com.quranapp.android.compose.components.reader.ReaderLayoutItem import com.quranapp.android.compose.components.reader.ReaderMode +import com.quranapp.android.utils.Log import com.quranapp.android.utils.reader.ReaderIntentData import com.quranapp.android.utils.reader.ReaderLaunchParams import com.quranapp.android.viewModels.ReaderViewModel @@ -77,7 +78,7 @@ fun ReaderNavigator( } else -> { - val isInCurrentView = readerVm.verseByVerseItems.value.any { item -> + val isInCurrentView = readerVm.verseByVersePrepared.value.items.any { item -> item is ReaderLayoutItem.VerseUI && item.verse.chapterNo == chapterNo && item.verse.verseNo == verseNo diff --git a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt index bf90e17e2..e0b6d69a6 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt @@ -115,15 +115,6 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic val verseByVersePrepared: StateFlow = _verseByVersePrepared.asStateFlow() - val verseByVerseItems: StateFlow> = - _verseByVersePrepared - .map { it.items } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5000), - initialValue = emptyList(), - ) - val pageCounts = mutableStateMapOf() val pageItems = mutableStateMapOf() diff --git a/app/src/main/res/drawable/ic_arrow_up.xml b/app/src/main/res/drawable/ic_arrow_up.xml new file mode 100644 index 000000000..5c444025b --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f67718da5..82366fce4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,6 +25,7 @@ View all Skip Next + Previous Start Download Update @@ -37,6 +38,12 @@ Next verse Previous tafsir Next tafsir + Next chapter + Previous chapter + Next juz + Previous chapter + Next hizb + Previous hizb Surah %s Page %d Ruku %d From bd51c760bb56e1f2c687628787bfa73ac7223358 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Mon, 20 Apr 2026 00:25:27 +0530 Subject: [PATCH 10/16] fix ci --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b09520de3..d50bde6e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,8 @@ on: - ".github/**" - "kotlin_code_style.xml" push: + branches: + - master paths-ignore: - "**.md" - "fastlane/**" From b2f068a82331f22d743f4e7da2d564670305f1de Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Mon, 20 Apr 2026 02:44:41 +0530 Subject: [PATCH 11/16] recommendation system --- .../assets/verses/recommended/lang_en.json | 14 + .../main/assets/verses/recommended/rules.json | 47 +++ .../compose/components/VerseOfTheDay.kt | 377 +++++++++--------- .../homepage/HomeSectionRecommended.kt | 180 +++++++++ .../components/homepage/HomeTabbedSection.kt | 79 ++++ .../android/compose/screens/HomeScreen.kt | 7 +- .../android/utils/recommended/Recommended.kt | 180 +++++++++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 699 insertions(+), 186 deletions(-) create mode 100644 app/src/main/assets/verses/recommended/lang_en.json create mode 100644 app/src/main/assets/verses/recommended/rules.json create mode 100644 app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionRecommended.kt create mode 100644 app/src/main/java/com/quranapp/android/compose/components/homepage/HomeTabbedSection.kt create mode 100644 app/src/main/java/com/quranapp/android/utils/recommended/Recommended.kt diff --git a/app/src/main/assets/verses/recommended/lang_en.json b/app/src/main/assets/verses/recommended/lang_en.json new file mode 100644 index 000000000..c95877563 --- /dev/null +++ b/app/src/main/assets/verses/recommended/lang_en.json @@ -0,0 +1,14 @@ +{ + "friday_kahf": { + "title": "Friday Light", + "description": "Recite Surah Al-Kahf for the blessed day of Jumu'ah." + }, + "night_three_quls": { + "title": "Night Protection & Peace", + "description": "End your day with verses of protection and calm." + }, + "night_mulk": { + "title": "Before Sleep Recitation", + "description": "Recite Surah Al-Mulk before resting tonight." + } +} \ No newline at end of file diff --git a/app/src/main/assets/verses/recommended/rules.json b/app/src/main/assets/verses/recommended/rules.json new file mode 100644 index 000000000..a1a041607 --- /dev/null +++ b/app/src/main/assets/verses/recommended/rules.json @@ -0,0 +1,47 @@ +{ + "schema": 2, + "defaults": { + "timeZone": "local" + }, + "rules": [ + { + "id": "friday_kahf", + "priority": 100, + "when": { + "clauses": [ + { + "weekdays": [5], + "hourRanges": [[4, 18]] + } + ] + }, + "ref": { + "segments": ["18"] + } + }, + { + "id": "night", + "priority": 90, + "when": { + "clauses": [ + { + "weekdays": [1, 2, 3, 4, 5, 6, 7], + "hourRanges": [[20, 1]] + } + ] + }, + "ref": { + "segments": [ + { + "verseRef": "2:285-286,112:1-4,113:1-5,114:1-6", + "langKey": "night_three_quls" + }, + { + "verseRef": "67", + "langKey": "night_mulk" + } + ] + } + } + ] +} diff --git a/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt b/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt index cf37a39ac..357f5e972 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/VerseOfTheDay.kt @@ -14,8 +14,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -82,12 +80,21 @@ private data class VerseOfTheDayState( @Composable fun VerseOfTheDay() { ReaderProvider { - VotdContent() + HomePremiumBannerContainer { + VotdContent() + } } } @Composable -private fun VotdContent() { +internal fun VotdContent( + header: @Composable () -> Unit = { + HomePremiumHeaderPill( + icon = R.drawable.dr_icon_heart_filled, + title = stringResource(R.string.strTitleVOTD) + ) + } +) { val context = LocalContext.current val colors = colorScheme val type = typography @@ -216,22 +223,153 @@ private fun VotdContent() { ) } + + val iconTint = Color.White.alpha(0.7f) + + val recState = LocalRecitation.current + val isVersePlaying = recState.isAnyPlaying && recState.playingVerse.doesEqual(verse) + + Column { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + contentAlignment = Alignment.Center + ) { + header() + } + + if (arabicEnabled && arabicText.isNotBlank()) { + Text( + text = arabicText, + style = quranTextStyle.copy(color = Color.White), + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + ) + } + + if (translationText.isNotBlank()) { + SelectionContainer { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = translationText, + style = translationTextStyle, + color = Color.White, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + ) + + Text( + text = stringResource( + R.string.strLabelVerseSerialWithChapter, + votdState.verse.chapter.getCurrentName(), + verse.chapterNo, + verse.verseNo + ), + style = translationTextStyle.copy( + fontStyle = FontStyle.Italic, + fontSize = typography.labelMedium.fontSize + ), + color = Color.White.alpha(0.6f), + textAlign = TextAlign.Center, + ) + } + } + } + + HorizontalDivider( + modifier = Modifier.padding(top = 16.dp), + color = Color.White.alpha(0.2f) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 6.dp, end = 12.dp, top = 4.dp, bottom = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + IconButton( + painter = if (isVersePlaying) painterResource(R.drawable.ic_pause) + else painterResource(R.drawable.ic_play), + contentDescription = if (isVersePlaying) stringResource(R.string.strLabelPause) + else stringResource(R.string.strLabelPlay), + tint = if (isVersePlaying) colorScheme.primary else iconTint, + small = true, + onClick = { recState.controller.playControl(ChapterVersePair(verse)) } + ) + + IconButton( + painter = painterResource(if (isBookmarked) R.drawable.ic_bookmark_added else R.drawable.ic_bookmark), + contentDescription = stringResource(R.string.strLabelBookmark), + tint = if (isBookmarked) colorScheme.primary else iconTint, + small = true, + onClick = { + verseActions.onBookmarkRequest?.invoke( + votdState.verse.chapterNo, + votdState.verse.verseNo..votdState.verse.verseNo + ) + } + ) + + IconButton( + painter = painterResource(if (votdEnabled) R.drawable.ic_bell_ring else R.drawable.ic_bell), + contentDescription = stringResource(R.string.dailyReminderMsg), + tint = if (votdEnabled) colorScheme.primary else iconTint, + small = true, + onClick = { showDailyReminderSheet = true } + ) + + IconButton( + painter = painterResource(R.drawable.dr_icon_tafsir), + contentDescription = stringResource(R.string.strTitleTafsir), + tint = null, + small = true, + onClick = { ReaderFactory.startTafsir(context, verse.chapterNo, verse.verseNo) } + ) + + Spacer(Modifier.weight(1f)) + + FilledTonalButton( + modifier = Modifier.height(28.dp), + onClick = { ReaderFactory.startVerse(context, verse.chapterNo, verse.verseNo) }, + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 0.dp) + ) { + Text( + stringResource(R.string.strLabelRead), + style = typography.labelMedium + ) + } + } + } +} + +@Composable +internal fun HomePremiumBannerContainer( + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + val colors = colorScheme val gradient = remember(colors) { Brush.linearGradient( colors = listOf( + Color.Black, colors.primary, Color.Black, ) ) } - val iconTint = Color.White.alpha(0.7f) - - val recState = LocalRecitation.current - val isVersePlaying = recState.isAnyPlaying && recState.playingVerse.doesEqual(verse) - Box( - modifier = Modifier + modifier = modifier .fillMaxWidth() .padding(12.dp), ) { @@ -248,193 +386,64 @@ private fun VotdContent() { Box( modifier = Modifier .matchParentSize() - .background(Color.Black.alpha(0.5f)) + .background(Color.Black.alpha(0.8f)) ) - Column() { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - Surface( - shape = RoundedCornerShape(999.dp), - color = Color.White.copy(alpha = 0.12f), - ) { - Row( - modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource(R.drawable.dr_icon_heart_filled), - contentDescription = null, - tint = Color.White, - modifier = Modifier.size(16.dp), - ) - Text( - text = stringResource(R.string.strTitleVOTD), - style = typography.labelMedium, - fontWeight = FontWeight.SemiBold, - color = Color.White, - ) - } - } - } - - if (arabicEnabled && arabicText.isNotBlank()) { - Text( - text = arabicText, - style = quranTextStyle.copy( - color = Color.White - ), - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - ) - } - - if (translationText.isNotBlank()) { - SelectionContainer { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text( - text = translationText, - style = translationTextStyle, - color = Color.White, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - ) - - Text( - text = stringResource( - R.string.strLabelVerseSerialWithChapter, - votdState.verse.chapter.getCurrentName(), - verse.chapterNo, - verse.verseNo - ), - style = translationTextStyle.copy( - fontStyle = FontStyle.Italic, - fontSize = type.labelMedium.fontSize - ), - color = Color.White.alpha(0.6f), - textAlign = TextAlign.Center, - ) - } - } - } + content() + } + } +} - HorizontalDivider( - modifier = Modifier.padding(top = 16.dp), - color = Color.White.alpha(0.2f) +@Composable +internal fun HomePremiumHeaderPill( + modifier: Modifier = Modifier, + icon: Int? = null, + title: String, + isSelected: Boolean = true, + onClick: (() -> Unit)? = null +) { + val alpha = if (isSelected) 0.12f else 0f + val contentAlpha = if (isSelected) 1f else 0.6f + + Surface( + modifier = modifier.clip(RoundedCornerShape(999.dp)), + shape = RoundedCornerShape(999.dp), + color = Color.White.copy(alpha = alpha), + onClick = onClick ?: {}, + enabled = onClick != null + ) { + Row( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (icon != null) { + Icon( + painter = painterResource(icon), + contentDescription = null, + tint = Color.White.copy(alpha = contentAlpha), + modifier = Modifier.size(16.dp), ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 6.dp, end = 12.dp, top = 4.dp, bottom = 4.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp), - ) { - IconButton( - painter = if (isVersePlaying) painterResource(R.drawable.ic_pause) - else painterResource(R.drawable.ic_play), - contentDescription = if (isVersePlaying) stringResource(R.string.strLabelPause) - else stringResource(R.string.strLabelPlay), - tint = if (isVersePlaying) colorScheme.primary else iconTint, - small = true, - ) { - recState.controller.playControl(ChapterVersePair(verse)) - } - - IconButton( - painter = painterResource( - if (isBookmarked) { - R.drawable.ic_bookmark_added - } else { - R.drawable.ic_bookmark - } - ), - contentDescription = stringResource(R.string.strLabelBookmark), - tint = if (isBookmarked) colorScheme.primary else iconTint, - small = true, - ) { - verseActions.onBookmarkRequest?.invoke( - votdState.verse.chapterNo, - votdState.verse.verseNo..votdState.verse.verseNo - ) - } - - IconButton( - painter = painterResource( - if (votdEnabled) R.drawable.ic_bell_ring else R.drawable.ic_bell - ), - contentDescription = stringResource(R.string.dailyReminderMsg), - tint = if (votdEnabled) colorScheme.primary else iconTint, - small = true, - ) { - showDailyReminderSheet = true - } - - IconButton( - painter = painterResource(R.drawable.dr_icon_tafsir), - contentDescription = stringResource(R.string.strTitleTafsir), - tint = null, - small = true, - ) { - ReaderFactory.startTafsir(context, verse.chapterNo, verse.verseNo) - } - - Spacer(Modifier.weight(1f)) - - FilledTonalButton( - modifier = Modifier.height(28.dp), - onClick = { - ReaderFactory.startVerse( - context, - verse.chapterNo, - verse.verseNo - ) - }, - contentPadding = PaddingValues(horizontal = 16.dp, vertical = 0.dp) - ) { - Text( - stringResource(R.string.strLabelRead), - style = type.labelMedium - ) - } - } } + Text( + text = title, + style = typography.labelMedium, + fontWeight = FontWeight.SemiBold, + color = Color.White.copy(alpha = contentAlpha), + ) } } } + @Composable private fun VerseOfTheDayLoading() { - Card( + Box( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 10.dp, vertical = 6.dp), - shape = RoundedCornerShape(18.dp), - colors = CardDefaults.cardColors( - containerColor = colorScheme.surfaceContainer, - ), + .padding(vertical = 28.dp), + contentAlignment = Alignment.Center, ) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 28.dp), - contentAlignment = Alignment.Center, - ) { - Loader() - } + Loader() } } diff --git a/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionRecommended.kt b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionRecommended.kt new file mode 100644 index 000000000..1847209ad --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionRecommended.kt @@ -0,0 +1,180 @@ +package com.quranapp.android.compose.components.homepage + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.quranapp.android.R +import com.quranapp.android.db.DatabaseProvider +import com.quranapp.android.utils.reader.factory.ReaderFactory +import com.quranapp.android.utils.recommended.Recommendation +import com.quranapp.android.utils.recommended.RecommendationRef + +@Composable +fun HomeSectionRecommended( + recommendations: List, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + recommendations.forEach { recommendation -> + RecommendationCard(recommendation) + } + } +} + +@Composable +private fun RecommendationCard( + recommendation: Recommendation +) { + val context = LocalContext.current + + val resolvedChapterName by produceState(initialValue = null) { + val quranRepository = DatabaseProvider.getQuranRepository(context) + + val ref = recommendation.reference + if (ref is RecommendationRef.Chapter) { + value = quranRepository.getChapterName(ref.number) + } + } + + Box( + modifier = Modifier + .fillMaxWidth() + .clip(shapes.medium) + .background(Color.White.copy(alpha = 0.08f)) + .clickable { + when (val ref = recommendation.reference) { + is RecommendationRef.Chapter -> { + ReaderFactory.startChapter(context, ref.number) + } + + is RecommendationRef.Verses -> { + // Simple parsing for "chapter:verse-verse" or "chapter:verse" or multi-spec + if (ref.spec.contains(',')) { + val ranges = ref.spec.split(',') + val chapters = mutableListOf() + val verseSpecs = mutableListOf() + + ranges.forEach { rangeSpec -> + val trimmed = rangeSpec.trim() + val chapterNo = trimmed.split(':')[0].toIntOrNull() ?: 0 + chapters.add(chapterNo) + verseSpecs.add(trimmed) + } + + ReaderFactory.startReferenceVerse( + context = context, + title = recommendation.title, + desc = recommendation.description, + translSlug = emptyArray(), + chapters = chapters, + verses = verseSpecs + ) + return@clickable + } + + val parts = ref.spec.split(':') + + if (parts.size == 2) { + val chapterNo = parts[0].toIntOrNull() ?: return@clickable + val versePart = parts[1] + val rangeParts = versePart.split('-', '–') + + if (rangeParts.size == 2) { + val from = rangeParts[0].toIntOrNull() ?: return@clickable + val to = rangeParts[1].toIntOrNull() ?: return@clickable + ReaderFactory.startVerseRange(context, chapterNo, from, to) + } else { + val verseNo = versePart.toIntOrNull() ?: return@clickable + ReaderFactory.startVerse(context, chapterNo, verseNo) + } + } + } + } + } + .padding(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(Color.White.copy(alpha = 0.1f)), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(R.drawable.dr_icon_feature), + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(20.dp) + ) + } + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + resolvedChapterName?.let { name -> + Text( + text = stringResource(R.string.strLabelSurah, name), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Black, + color = Color.White + ) + } + + Text( + text = recommendation.title, + style = if (resolvedChapterName != null) MaterialTheme.typography.labelMedium + else MaterialTheme.typography.titleMedium, + fontWeight = if (resolvedChapterName != null) FontWeight.Medium else FontWeight.Bold, + color = if (resolvedChapterName != null) Color.White.copy(alpha = 0.8f) else Color.White + ) + + if (recommendation.description.isNotBlank()) { + Text( + text = recommendation.description, + style = MaterialTheme.typography.bodySmall, + color = Color.White.copy(alpha = 0.6f) + ) + } + } + + Icon( + painter = painterResource(R.drawable.dr_icon_chevron_right), + contentDescription = null, + tint = Color.White.copy(alpha = 0.4f), + modifier = Modifier.size(16.dp) + ) + } + } +} diff --git a/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeTabbedSection.kt b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeTabbedSection.kt new file mode 100644 index 000000000..46e4c5d50 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeTabbedSection.kt @@ -0,0 +1,79 @@ +package com.quranapp.android.compose.components.homepage + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.SecondaryTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import com.quranapp.android.R +import com.quranapp.android.compose.components.HomePremiumBannerContainer +import com.quranapp.android.compose.components.VerseOfTheDay +import com.quranapp.android.compose.components.VotdContent +import com.quranapp.android.compose.components.reader.ReaderProvider +import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.utils.recommended.Recommendation +import com.quranapp.android.utils.recommended.Recommended + +@Composable +fun HomeTabbedSection() { + val context = LocalContext.current + var recommendations by remember { mutableStateOf(emptyList()) } + var selectedTabIndex by remember { mutableIntStateOf(0) } + + LaunchedEffect(Unit) { + recommendations = Recommended.getRecommendations(context) + } + + if (recommendations.isEmpty()) { + VerseOfTheDay() + return + } + + HomePremiumBannerContainer { + Column { + SecondaryTabRow( + selectedTabIndex = selectedTabIndex, + containerColor = Color.Transparent, + contentColor = Color.White, + divider = {} + ) { + Tab( + selected = selectedTabIndex == 0, + onClick = { selectedTabIndex = 0 }, + text = { Text(stringResource(R.string.labelRecommended)) }, + selectedContentColor = Color.White, + unselectedContentColor = Color.White.copy(alpha = 0.6f) + ) + Tab( + selected = selectedTabIndex == 1, + onClick = { selectedTabIndex = 1 }, + text = { Text(stringResource(R.string.strTitleVOTD)) }, + selectedContentColor = Color.White, + unselectedContentColor = Color.White.copy(alpha = 0.6f) + ) + } + + HorizontalDivider( + color = Color.White.alpha(0.15f) + ) + + if (selectedTabIndex == 0) { + HomeSectionRecommended(recommendations) + } else { + ReaderProvider { + VotdContent(header = {}) + } + } + } + } +} diff --git a/app/src/main/java/com/quranapp/android/compose/screens/HomeScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/HomeScreen.kt index 76de6cf0a..bb1daf25c 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/HomeScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/HomeScreen.kt @@ -11,16 +11,17 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import com.quranapp.android.compose.components.VerseOfTheDay import com.quranapp.android.compose.components.homepage.AppUpdateBanner import com.quranapp.android.compose.components.homepage.HomeSectionDuas import com.quranapp.android.compose.components.homepage.HomeSectionEtiquettes import com.quranapp.android.compose.components.homepage.HomeSectionFeaturedReading +import com.quranapp.android.compose.components.homepage.HomeSectionHeader import com.quranapp.android.compose.components.homepage.HomeSectionMajorSins import com.quranapp.android.compose.components.homepage.HomeSectionProphets import com.quranapp.android.compose.components.homepage.HomeSectionQuranScience import com.quranapp.android.compose.components.homepage.HomeSectionReadHistory import com.quranapp.android.compose.components.homepage.HomeSectionSolutions +import com.quranapp.android.compose.components.homepage.HomeTabbedSection import com.quranapp.android.compose.components.player.MINI_PLAYER_HEIGHT import com.quranapp.android.viewModels.HomeViewModel @@ -40,7 +41,9 @@ fun HomeScreen( .padding(bottom = MINI_PLAYER_HEIGHT), ) { AppUpdateBanner() - VerseOfTheDay() + + HomeTabbedSection() + HomeSectionReadHistory() HomeSectionFeaturedReading() HomeSectionDuas() diff --git a/app/src/main/java/com/quranapp/android/utils/recommended/Recommended.kt b/app/src/main/java/com/quranapp/android/utils/recommended/Recommended.kt new file mode 100644 index 000000000..af302ad06 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/recommended/Recommended.kt @@ -0,0 +1,180 @@ +package com.quranapp.android.utils.recommended + +import android.content.Context +import com.quranapp.android.compose.utils.appFallbackLanguageCodes +import com.quranapp.android.utils.univ.ResUtils +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime + +data class Recommendation( + val title: String, + val description: String, + val reference: RecommendationRef +) + +sealed class RecommendationRef { + data class Chapter(val number: Int) : RecommendationRef() + data class Verses(val spec: String) : RecommendationRef() +} + +object Recommended { + private val json = Json { ignoreUnknownKeys = true; isLenient = true; explicitNulls = false } + + fun getRecommendations( + context: Context, + ): List { + val doc = loadDocument(context) ?: return emptyList() + val strings = loadStrings(context) + + return doc.rules + .filter { it.enabled && it.matches(Instant.now(), doc.defaults?.timeZone) } + .sortedByDescending { it.priority } + .flatMap { rule -> + rule.toRecommendations(strings) + } + } + + private fun loadDocument(context: Context): RecommendedRulesDoc? = try { + val text = ResUtils.readAssetsTextFile(context, "verses/recommended/rules.json") + json.decodeFromString(text) + } catch (e: Exception) { + null + } + + private fun loadStrings(context: Context): Map { + val text = appFallbackLanguageCodes().map { lang -> + ResUtils.readAssetsTextFile(context, "verses/recommended/lang_$lang.json") + }.firstOrNull { it.isNotBlank() } ?: "" + + if (text.isBlank()) return emptyMap() + + return try { + val root = json.parseToJsonElement(text).jsonObject + root.mapValues { json.decodeFromJsonElement(it.value) } + } catch (e: Exception) { + emptyMap() + } + } +} + +@Serializable +private data class RecommendedRulesDoc(val defaults: Defaults? = null, val rules: List) + +@Serializable +private data class Defaults(val timeZone: String? = null) + +@Serializable +private data class Rule( + val id: String, + val enabled: Boolean = true, + val priority: Int, + @SerialName("when") val schedule: Schedule, + val ref: JsonElement, +) + +@Serializable +private data class Schedule(val timeZone: String? = null, val clauses: List) + +@Serializable +private data class Clause(val weekdays: JsonElement? = null, val hourRanges: JsonElement? = null) + +@Serializable +private data class RuleCopy(val title: String, val description: String?) + +private fun Rule.matches(instant: Instant, defaultTz: String?): Boolean { + val tz = schedule.timeZone ?: defaultTz ?: "local" + val zoneId = if (tz.lowercase() == "local") ZoneId.systemDefault() else ZoneId.of(tz) + val zdt = instant.atZone(zoneId) + return schedule.clauses.any { it.matches(zdt) } +} + +private fun Clause.matches(zdt: ZonedDateTime): Boolean { + val dayOk = weekdays?.let { el -> + when (el) { + is JsonPrimitive -> el.content == "*" || el.intOrNull == zdt.dayOfWeek.value + is JsonArray -> el.any { it.jsonPrimitive.intOrNull == zdt.dayOfWeek.value } + else -> false + } + } ?: true + + val hourOk = hourRanges?.let { el -> + when (el) { + is JsonPrimitive -> el.content == "*" + is JsonArray -> el.any { range -> + val r = range.jsonArray + + if (r.size < 2) return@any false + + val start = r[0].jsonPrimitive.intOrNull ?: 0 + val end = r[1].jsonPrimitive.intOrNull ?: 0 + + if (start <= end) { + zdt.hour in start until end + } else { + zdt.hour >= start || zdt.hour < end + } + } + + else -> false + } + } ?: true + + return dayOk && hourOk +} + +private fun Rule.toRecommendations(strings: Map): List { + val segments = when (val el = ref) { + is JsonPrimitive -> el.content.split(',').map { it.trim() } + .filter { it.isNotBlank() } + .map { RawSegment(it) } + + is JsonObject -> el["segments"]?.jsonArray?.map { seg -> + if (seg is JsonPrimitive) RawSegment(seg.content.trim(), null) + else RawSegment( + seg.jsonObject["verseRef"]?.jsonPrimitive?.content ?: "", + seg.jsonObject["langKey"]?.jsonPrimitive?.content + ) + } ?: emptyList() + + else -> emptyList() + } + + if (segments.isEmpty()) return emptyList() + + return segments.map { segment -> + val key = segment.langKey ?: id + buildRec(strings, key, segment.ref.toRecRef()) + } +} + +private data class RawSegment(val ref: String, val langKey: String? = null) + +private fun String.toRecRef() = this.toIntOrNull()?.let { RecommendationRef.Chapter(it) } + ?: RecommendationRef.Verses(this) + +private fun buildRec( + strings: Map, + key: String, + ref: RecommendationRef +): Recommendation { + val copy = strings[key] + + return Recommendation( + title = copy?.title ?: key, + description = copy?.description.orEmpty(), + reference = ref + ) +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 82366fce4..4a2045eba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -142,6 +142,7 @@ Share Verse Featured Reading Verse of The Day + Recommended Prophets Duas in Quran Solutions from Quran From 192df6f4a7c4defb3c1ac8edfe9976e728b3df52 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Mon, 20 Apr 2026 21:56:26 +0530 Subject: [PATCH 12/16] update resources_versions.json --- inventory/versions/resources_versions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inventory/versions/resources_versions.json b/inventory/versions/resources_versions.json index b7e10604e..adf01f35f 100644 --- a/inventory/versions/resources_versions.json +++ b/inventory/versions/resources_versions.json @@ -3,5 +3,6 @@ "translations": 16, "recitations": 4, "recitationTranslations": 7, - "tafsirs": 1 + "tafsirs": 1, + "wbw": 1 } From 426994234cc9b95aa721be73e0a0d86bd44bf5c8 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Mon, 20 Apr 2026 22:22:01 +0530 Subject: [PATCH 13/16] update manager improvements --- .../android/activities/MainActivity.kt | 17 +- .../android/api/models/ResourcesVersions.kt | 4 +- .../android/components/AppUpdateInfo.kt | 35 ---- .../components/homepage/AppUpdateBanner.kt | 16 +- .../homepage/HomeSectionRecommended.kt | 19 +-- .../android/compose/screens/HomeScreen.kt | 8 +- .../quranapp/android/utils/app/AppActions.kt | 54 ------ .../utils/app/ResourceUpdateManager.kt | 158 ++++++++++++++++++ .../android/utils/app/UpdateManager.kt | 119 ++++++++++--- .../quranapp/android/utils/app/UrlsManager.kt | 45 +++-- .../mediaplayer/RecitationModelManager.kt | 4 + .../android/utils/reader/wbw/WbwManager.kt | 27 --- .../utils/reader/wbw/WbwVersionStore.kt | 7 - .../android/utils/recommended/Recommended.kt | 6 +- .../android/utils/sharedPrefs/SPAppConfigs.kt | 44 ----- .../android/utils/univ/FileUtils.java | 4 + .../android/viewModels/HomeViewModel.kt | 33 ---- 17 files changed, 302 insertions(+), 298 deletions(-) delete mode 100644 app/src/main/java/com/quranapp/android/components/AppUpdateInfo.kt create mode 100644 app/src/main/java/com/quranapp/android/utils/app/ResourceUpdateManager.kt delete mode 100644 app/src/main/java/com/quranapp/android/viewModels/HomeViewModel.kt diff --git a/app/src/main/java/com/quranapp/android/activities/MainActivity.kt b/app/src/main/java/com/quranapp/android/activities/MainActivity.kt index b73761e4a..5ba04cea3 100644 --- a/app/src/main/java/com/quranapp/android/activities/MainActivity.kt +++ b/app/src/main/java/com/quranapp/android/activities/MainActivity.kt @@ -8,7 +8,6 @@ import com.quranapp.android.activities.base.BaseActivity import com.quranapp.android.compose.screens.MainScreen import com.quranapp.android.compose.theme.QuranAppTheme import com.quranapp.android.utils.app.AppActions.checkForCrashLogs -import com.quranapp.android.utils.app.AppActions.checkForResourcesVersions import com.quranapp.android.utils.app.AppActions.scheduleActions import com.quranapp.android.utils.app.UpdateManager import com.quranapp.android.utils.sharedPrefs.SPAppActions @@ -19,21 +18,8 @@ class MainActivity : BaseActivity() { override fun getLayoutResource() = 0 - override fun onPause() { - mUpdateManager?.onPause() - super.onPause() - } - - override fun onResume() { - super.onResume() - mUpdateManager?.onResume() - } - override fun initCreate(savedInstanceState: Bundle?) { - mUpdateManager = UpdateManager(this) - mUpdateManager!!.refreshAppUpdatesJson() - - if (mUpdateManager!!.check4CriticalUpdate()) { + if (UpdateManager.getInstance(this).check4CriticalUpdate()) { return } @@ -64,7 +50,6 @@ class MainActivity : BaseActivity() { private fun initActions() { - checkForResourcesVersions(this) scheduleActions(this) checkForCrashLogs(this) } diff --git a/app/src/main/java/com/quranapp/android/api/models/ResourcesVersions.kt b/app/src/main/java/com/quranapp/android/api/models/ResourcesVersions.kt index 958f63338..7bbbeb657 100644 --- a/app/src/main/java/com/quranapp/android/api/models/ResourcesVersions.kt +++ b/app/src/main/java/com/quranapp/android/api/models/ResourcesVersions.kt @@ -6,8 +6,10 @@ import kotlinx.serialization.Serializable @Serializable data class ResourcesVersions( @SerialName("urls") val urlsVersion: Long, + @Deprecated("Translation manifest are always loaded on demand on the download screen.") @SerialName("translations") val translationsVersion: Long, @SerialName("recitations") val recitationsVersion: Long, @SerialName("recitationTranslations") val recitationTranslationsVersion: Long, - @SerialName("tafsirs") val tafsirsVersion: Long + @SerialName("tafsirs") val tafsirsVersion: Long, + @SerialName("wbw") val wbwVersion: Long ) diff --git a/app/src/main/java/com/quranapp/android/components/AppUpdateInfo.kt b/app/src/main/java/com/quranapp/android/components/AppUpdateInfo.kt deleted file mode 100644 index bd1fbf07a..000000000 --- a/app/src/main/java/com/quranapp/android/components/AppUpdateInfo.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.quranapp.android.components - -import android.content.Context -import com.quranapp.android.BuildConfig -import com.quranapp.android.api.JsonHelper -import com.quranapp.android.api.models.AppUpdate -import com.quranapp.android.utils.univ.FileUtils -import kotlinx.serialization.decodeFromString - -class AppUpdateInfo(private val ctx: Context) { - companion object { - const val CRITICAL = 5 - const val MAJOR = 4 - const val MODERATE = 3 - const val MINOR = 2 - const val COSMETIC = 1 - const val NONE = 0 - } - - private val updates: List = try { - val fileUtils = FileUtils.newInstance(ctx) - JsonHelper.json.decodeFromString(fileUtils.appUpdatesFile.readText()) - } catch (e: Exception) { - ArrayList() - } - - fun getMostImportantUpdate(): AppUpdate { - val currentAppVersion = BuildConfig.VERSION_CODE - - val mostImportantUpdate = updates - .filter { it.version > currentAppVersion } - .sortedBy { it.priority } - return mostImportantUpdate.takeIf { it.isNotEmpty() }?.get(0) ?: AppUpdate(0, NONE) - } -} diff --git a/app/src/main/java/com/quranapp/android/compose/components/homepage/AppUpdateBanner.kt b/app/src/main/java/com/quranapp/android/compose/components/homepage/AppUpdateBanner.kt index 079547416..38714657e 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/homepage/AppUpdateBanner.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/homepage/AppUpdateBanner.kt @@ -22,11 +22,9 @@ import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer @@ -43,16 +41,10 @@ import kotlin.math.pow @Composable fun AppUpdateBanner() { val context = LocalContext.current - val updateManager = remember { UpdateManager(context) } - var showBanner by remember { mutableStateOf(false) } + val updateManager = remember { UpdateManager.getInstance(context) } + val bannerDecision by updateManager.bannerDecision.collectAsState() - LaunchedEffect(Unit) { - showBanner = updateManager.getBannerDecision().showInlineBanner - updateManager.fetchAndSaveUpdates() - showBanner = updateManager.getBannerDecision().showInlineBanner - } - - if (!showBanner) return + if (!bannerDecision.showInlineBanner) return Card( modifier = Modifier diff --git a/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionRecommended.kt b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionRecommended.kt index 1847209ad..cf51d4283 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionRecommended.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/homepage/HomeSectionRecommended.kt @@ -124,21 +124,6 @@ private fun RecommendationCard( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - Box( - modifier = Modifier - .size(40.dp) - .clip(CircleShape) - .background(Color.White.copy(alpha = 0.1f)), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(R.drawable.dr_icon_feature), - contentDescription = null, - tint = Color.White, - modifier = Modifier.size(20.dp) - ) - } - Column( modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp) @@ -163,7 +148,7 @@ private fun RecommendationCard( if (recommendation.description.isNotBlank()) { Text( text = recommendation.description, - style = MaterialTheme.typography.bodySmall, + style = MaterialTheme.typography.bodyMedium, color = Color.White.copy(alpha = 0.6f) ) } @@ -173,7 +158,7 @@ private fun RecommendationCard( painter = painterResource(R.drawable.dr_icon_chevron_right), contentDescription = null, tint = Color.White.copy(alpha = 0.4f), - modifier = Modifier.size(16.dp) + modifier = Modifier.size(24.dp) ) } } diff --git a/app/src/main/java/com/quranapp/android/compose/screens/HomeScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/HomeScreen.kt index bb1daf25c..3734bc8ba 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/HomeScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/HomeScreen.kt @@ -10,12 +10,10 @@ import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import com.quranapp.android.compose.components.homepage.AppUpdateBanner import com.quranapp.android.compose.components.homepage.HomeSectionDuas import com.quranapp.android.compose.components.homepage.HomeSectionEtiquettes import com.quranapp.android.compose.components.homepage.HomeSectionFeaturedReading -import com.quranapp.android.compose.components.homepage.HomeSectionHeader import com.quranapp.android.compose.components.homepage.HomeSectionMajorSins import com.quranapp.android.compose.components.homepage.HomeSectionProphets import com.quranapp.android.compose.components.homepage.HomeSectionQuranScience @@ -23,15 +21,11 @@ import com.quranapp.android.compose.components.homepage.HomeSectionReadHistory import com.quranapp.android.compose.components.homepage.HomeSectionSolutions import com.quranapp.android.compose.components.homepage.HomeTabbedSection import com.quranapp.android.compose.components.player.MINI_PLAYER_HEIGHT -import com.quranapp.android.viewModels.HomeViewModel private const val HOME_HISTORY_LIMIT = 10 @Composable -fun HomeScreen( - modifier: Modifier, - homeVm: HomeViewModel = viewModel(), -) { +fun HomeScreen(modifier: Modifier) { Column( modifier = modifier .fillMaxSize() diff --git a/app/src/main/java/com/quranapp/android/utils/app/AppActions.kt b/app/src/main/java/com/quranapp/android/utils/app/AppActions.kt index e09a964d4..3038161cc 100644 --- a/app/src/main/java/com/quranapp/android/utils/app/AppActions.kt +++ b/app/src/main/java/com/quranapp/android/utils/app/AppActions.kt @@ -10,19 +10,13 @@ import com.peacedesign.android.utils.AppBridge import com.peacedesign.android.widget.dialog.base.PeaceDialog import com.quranapp.android.R import com.quranapp.android.api.ApiConfig -import com.quranapp.android.api.RetrofitInstance import com.quranapp.android.compose.utils.VerseOfTheDayScheduler import com.quranapp.android.compose.utils.preferences.VersePreferences import com.quranapp.android.utils.Log import com.quranapp.android.utils.extensions.copyToClipboard import com.quranapp.android.utils.managers.TranslationDownloadManager import com.quranapp.android.utils.reader.factory.QuranTranslationFactory -import com.quranapp.android.utils.sharedPrefs.SPAppActions -import com.quranapp.android.utils.sharedPrefs.SPAppConfigs import com.quranapp.android.utils.sharedPrefs.SPLog -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch object AppActions { const val APP_ACTION_KEY = "app.action.key" @@ -51,54 +45,6 @@ object AppActions { } } - /** - * Checks if there has been changes in the app resources on the server. - * If there has been changes, then the upcoming versions from the remote config will be greater than that of locally saved. - * */ - @JvmStatic - fun checkForResourcesVersions(ctx: Context) { - CoroutineScope(Dispatchers.IO).launch { - try { - val (urlsVersion, translationsVersion, recitationsVersion, recitationsTranslationVersion, tafsirsVersion) - = RetrofitInstance.github.getResourcesVersions() - - val localUrlsVersion = SPAppConfigs.getUrlsVersion(ctx) - val localTranslationsVersion = SPAppConfigs.getTranslationsVersion(ctx) - val localRecitationsVersion = SPAppConfigs.getRecitationsVersion(ctx) - val localRecitationTranslationsVersion = - SPAppConfigs.getRecitationTranslationsVersion(ctx) - val localTafsirsVersion = SPAppConfigs.getTafsirsVersion(ctx) - - if (urlsVersion > localUrlsVersion) { - SPAppActions.setFetchUrlsForce(ctx, true) - SPAppConfigs.setUrlsVersion(ctx, urlsVersion) - } - - if (translationsVersion > localTranslationsVersion) { - SPAppActions.setFetchTranslationsForce(ctx, true) - SPAppConfigs.setTranslationsVersion(ctx, translationsVersion) - } - - if (recitationsVersion > localRecitationsVersion) { - SPAppActions.setFetchRecitationsForce(ctx, true) - SPAppConfigs.setRecitationsVersion(ctx, recitationsVersion) - } - - if (recitationsTranslationVersion > localRecitationTranslationsVersion) { - SPAppActions.setFetchRecitationTranslationsForce(ctx, true) - SPAppConfigs.setRecitationTranslationsVersion(ctx, recitationsVersion) - } - - if (tafsirsVersion > localTafsirsVersion) { - SPAppActions.setFetchTafsirsForce(ctx, true) - SPAppConfigs.setTafsirsVersion(ctx, tafsirsVersion) - } - } catch (e: Exception) { - e.printStackTrace() - } - } - } - @JvmStatic fun checkForCrashLogs(ctx: Context) { var lastCrashLog = Log.getLastCrashLog(ctx) diff --git a/app/src/main/java/com/quranapp/android/utils/app/ResourceUpdateManager.kt b/app/src/main/java/com/quranapp/android/utils/app/ResourceUpdateManager.kt new file mode 100644 index 000000000..52925e918 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/app/ResourceUpdateManager.kt @@ -0,0 +1,158 @@ +package com.quranapp.android.utils.app + +import android.content.Context +import com.quranapp.android.api.JsonHelper +import com.quranapp.android.api.RetrofitInstance +import com.quranapp.android.api.models.ResourcesVersions +import com.quranapp.android.utils.Log +import com.quranapp.android.utils.Logger +import com.quranapp.android.utils.mediaplayer.RecitationModelManager +import com.quranapp.android.utils.reader.tafsir.TafsirManager +import com.quranapp.android.utils.reader.wbw.WbwManager +import com.quranapp.android.utils.univ.FileUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.withContext +import kotlinx.serialization.encodeToString + +enum class ResourceUpdateState { + IDLE, CHECKING, UPDATING, COMPLETED, FAILED +} + +class ResourceUpdateManager private constructor(private val ctx: Context) { + companion object { + private var INSTANCE: ResourceUpdateManager? = null + + fun getInstance(context: Context): ResourceUpdateManager { + if (INSTANCE == null) { + INSTANCE = ResourceUpdateManager(context.applicationContext) + } + return INSTANCE!! + } + } + + private val _updateState = MutableStateFlow(ResourceUpdateState.IDLE) + val updateState: StateFlow = _updateState.asStateFlow() + + private val fileUtils = FileUtils.newInstance(ctx) + + private fun getLocalVersions(): ResourcesVersions? { + val file = fileUtils.resourcesVersionsFile + if (!file.exists() || file.length() == 0L) return null + + return try { + JsonHelper.json.decodeFromString(file.readText()) + } catch (e: Exception) { + null + } + } + + suspend fun checkAndPerformUpdates(force: Boolean = false) = withContext(Dispatchers.IO) { + if (_updateState.value == ResourceUpdateState.CHECKING || _updateState.value == ResourceUpdateState.UPDATING) return@withContext + + _updateState.value = ResourceUpdateState.CHECKING + + try { + val remoteVersions = RetrofitInstance.github.getResourcesVersions() + val localVersions = getLocalVersions() + + if (force || localVersions == null || isAnyUpdateAvailable( + localVersions, + remoteVersions + ) + ) { + _updateState.value = ResourceUpdateState.UPDATING + + Logger.print("Resources update available: ", remoteVersions) + performUpdates(localVersions, remoteVersions, force) + + saveLocalVersions(remoteVersions) + + _updateState.value = ResourceUpdateState.COMPLETED + } else { + _updateState.value = ResourceUpdateState.IDLE + } + } catch (e: Exception) { + Log.saveError(e, "ResourceUpdateManager.checkAndPerformUpdates") + _updateState.value = ResourceUpdateState.FAILED + } + } + + private fun isAnyUpdateAvailable(local: ResourcesVersions, remote: ResourcesVersions): Boolean { + return remote.urlsVersion > local.urlsVersion || + remote.translationsVersion > local.translationsVersion || + remote.recitationsVersion > local.recitationsVersion || + remote.recitationTranslationsVersion > local.recitationTranslationsVersion || + remote.tafsirsVersion > local.tafsirsVersion || + remote.wbwVersion > local.wbwVersion + } + + private suspend fun performUpdates( + local: ResourcesVersions?, + remote: ResourcesVersions, + force: Boolean + ) = withContext(Dispatchers.IO) { + supervisorScope { + // URLs + launch { + if (force || local == null || remote.urlsVersion > local.urlsVersion) { + try { + UrlsManager(ctx).refresh() + } catch (e: Exception) { + Log.saveError(e, "ResourceUpdateManager.updateUrls") + } + } + } + + // Recitations + launch { + if (force || local == null || remote.recitationsVersion > local.recitationsVersion || + remote.recitationTranslationsVersion > local.recitationTranslationsVersion + ) { + try { + RecitationModelManager.get(ctx).refreshManifests() + } catch (e: Exception) { + Log.saveError(e, "ResourceUpdateManager.updateRecitations") + } + } + } + + // Tafsirs + launch { + if (force || local == null || remote.tafsirsVersion > local.tafsirsVersion) { + try { + TafsirManager.prepare(ctx, true) { /* no-op */ } + } catch (e: Exception) { + Log.saveError(e, "ResourceUpdateManager.updateTafsirs") + } + } + } + + // WBW + launch { + if (force || local == null || remote.wbwVersion > local.wbwVersion) { + try { + WbwManager.getAvailable(ctx, forceRefresh = true) + } catch (e: Exception) { + Log.saveError(e, "ResourceUpdateManager.updateWbw") + } + } + } + } + } + + private fun saveLocalVersions(versions: ResourcesVersions) { + try { + val file = fileUtils.resourcesVersionsFile + if (fileUtils.createFile(file)) { + file.writeText(JsonHelper.json.encodeToString(versions)) + } + } catch (e: Exception) { + Log.saveError(e, "ResourceUpdateManager.saveLocalVersions") + } + } +} diff --git a/app/src/main/java/com/quranapp/android/utils/app/UpdateManager.kt b/app/src/main/java/com/quranapp/android/utils/app/UpdateManager.kt index 19e559867..8c5a50b88 100644 --- a/app/src/main/java/com/quranapp/android/utils/app/UpdateManager.kt +++ b/app/src/main/java/com/quranapp/android/utils/app/UpdateManager.kt @@ -5,7 +5,11 @@ */ package com.quranapp.android.utils.app -import android.animation.* +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ObjectAnimator +import android.animation.PropertyValuesHolder +import android.animation.TimeInterpolator import android.content.Context import android.os.Handler import android.os.Looper @@ -13,15 +17,19 @@ import android.view.View import com.peacedesign.android.utils.AppBridge import com.peacedesign.android.utils.ColorUtils import com.peacedesign.android.widget.dialog.base.PeaceDialog +import com.quranapp.android.BuildConfig import com.quranapp.android.R import com.quranapp.android.api.JsonHelper import com.quranapp.android.api.RetrofitInstance -import com.quranapp.android.components.AppUpdateInfo +import com.quranapp.android.api.models.AppUpdate import com.quranapp.android.databinding.LytUpdateAppDialogBinding import com.quranapp.android.utils.Logger import com.quranapp.android.utils.univ.FileUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.encodeToString @@ -34,11 +42,41 @@ data class UpdateBannerDecision( val showInlineBanner: Boolean, ) -class UpdateManager(private val ctx: Context) { +class UpdateManager private constructor(private val ctx: Context) { + companion object { + const val CRITICAL = 5 + const val MAJOR = 4 + const val MODERATE = 3 + const val MINOR = 2 + const val COSMETIC = 1 + const val NONE = 0 + + private var INSTANCE: UpdateManager? = null + + fun getInstance(context: Context): UpdateManager { + if (INSTANCE == null) { + INSTANCE = UpdateManager(context) + } + + return INSTANCE!! + } + } + private val mIconAnimationHandler = Handler(Looper.getMainLooper()) private var mIconAnimators = ArrayList() - suspend fun fetchAndSaveUpdates() { + private val _bannerDecision = MutableStateFlow(getBannerDecision()) + val bannerDecision: StateFlow = _bannerDecision.asStateFlow() + + init { + refreshAppUpdatesJson() + } + + fun refreshAppUpdatesJson() { + CoroutineScope(Dispatchers.IO).launch { fetchAndSaveUpdates() } + } + + private suspend fun fetchAndSaveUpdates() { withContext(Dispatchers.IO) { try { val updates = RetrofitInstance.github.getAppUpdates() @@ -47,6 +85,7 @@ class UpdateManager(private val ctx: Context) { FileUtils.newInstance(ctx).apply { val updatesFile = appUpdatesFile + if (createFile(updatesFile)) { updatesFile.writeText(updatesString) } @@ -55,19 +94,21 @@ class UpdateManager(private val ctx: Context) { e.printStackTrace() } } - } - fun refreshAppUpdatesJson() { - CoroutineScope(Dispatchers.IO).launch { fetchAndSaveUpdates() } + _bannerDecision.value = getBannerDecision() + + ResourceUpdateManager.getInstance(ctx).checkAndPerformUpdates() } fun check4CriticalUpdate(): Boolean { val decision = getBannerDecision() + if (decision.showCriticalDialog) { Logger.print("UpdateManager:", "Critical update available") showUpdateAvailableDialog(true) return true } + return false } @@ -81,21 +122,46 @@ class UpdateManager(private val ctx: Context) { } fun getBannerDecision(): UpdateBannerDecision { - val priority = AppUpdateInfo(ctx).getMostImportantUpdate().priority + val priority = getMostImportantUpdate().priority return UpdateBannerDecision( priority = priority, - showCriticalDialog = priority == AppUpdateInfo.CRITICAL, - showMajorDialog = priority == AppUpdateInfo.MAJOR, - showInlineBanner = priority in AppUpdateInfo.MAJOR downTo AppUpdateInfo.COSMETIC, + showCriticalDialog = priority == CRITICAL, + showMajorDialog = priority == MAJOR, + showInlineBanner = priority in MAJOR downTo COSMETIC, ) } + private fun getMostImportantUpdate(): AppUpdate { + val currentAppVersion = BuildConfig.VERSION_CODE.toLong() + + return getAvailableUpdates() + .filter { it.version > currentAppVersion } + .sortedByDescending { it.priority } + .firstOrNull() ?: AppUpdate(0, NONE) + } + + private fun getAvailableUpdates(): List { + return try { + val fileUtils = FileUtils.newInstance(ctx) + + val file = fileUtils.appUpdatesFile + + if (file.exists()) { + JsonHelper.json.decodeFromString(file.readText()) + } else { + emptyList() + } + } catch (e: Exception) { + emptyList() + } + } + fun openPlayStore() { AppBridge.newOpener(ctx).openPlayStore(null) } - private fun showUpdateAvailableDialog(isCritical: Boolean, runOnDismiss: Runnable? = null) { + private fun showUpdateAvailableDialog(isCritical: Boolean) { val binding = LytUpdateAppDialogBinding.inflate(android.view.LayoutInflater.from(ctx)) binding.txt.setText( if (isCritical) R.string.strMsgUpdateAvailable2Continue else R.string.strMsgUpdateAvailable4Dialog @@ -105,7 +171,10 @@ class UpdateManager(private val ctx: Context) { val builder = PeaceDialog.newBuilder(ctx) builder.setView(binding.root) builder.setCancelable(false) - builder.setOnDismissListener { runOnDismiss?.run() } + builder.setOnDismissListener { + mIconAnimators.forEach { it.cancel() } + mIconAnimationHandler.removeCallbacksAndMessages(null) + } builder.setPositiveButton(R.string.strLabelUpdate, ColorUtils.DANGER) { _, _ -> openPlayStore() } @@ -130,18 +199,20 @@ class UpdateManager(private val ctx: Context) { ) val pvhScaleX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1f, 1.1f, .8f, 1.3f, 1.03f, 1f) val pvhScaleY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f, .8f, 1.1f, 0.9f, 1f, 1f) - return ObjectAnimator.ofPropertyValuesHolder(iconView, pvhTransY, pvhScaleX, pvhScaleY).apply { - interpolator = TimeInterpolator { v -> (1.toFloat() - (1 - v).toDouble().pow(2.0)).toFloat() } - duration = 1000 - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - if (iconView.isAttachedToWindow) { - mIconAnimationHandler.postDelayed({ start() }, 1500) + return ObjectAnimator.ofPropertyValuesHolder(iconView, pvhTransY, pvhScaleX, pvhScaleY) + .apply { + interpolator = + TimeInterpolator { v -> (1.toFloat() - (1 - v).toDouble().pow(2.0)).toFloat() } + duration = 1000 + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + if (iconView.isAttachedToWindow) { + mIconAnimationHandler.postDelayed({ start() }, 1500) + } } - } - }) - start() - } + }) + start() + } } fun onPause() { diff --git a/app/src/main/java/com/quranapp/android/utils/app/UrlsManager.kt b/app/src/main/java/com/quranapp/android/utils/app/UrlsManager.kt index 49cec1cac..c2eddeeae 100644 --- a/app/src/main/java/com/quranapp/android/utils/app/UrlsManager.kt +++ b/app/src/main/java/com/quranapp/android/utils/app/UrlsManager.kt @@ -4,17 +4,14 @@ import android.content.Context import com.quranapp.android.api.JsonHelper import com.quranapp.android.api.RetrofitInstance import com.quranapp.android.api.models.AppUrls +import com.quranapp.android.utils.Log import com.quranapp.android.utils.app.AppUtils.BASE_APP_DOWNLOADED_SAVED_DATA_DIR import com.quranapp.android.utils.receivers.NetworkStateReceiver -import com.quranapp.android.utils.sharedPrefs.SPAppActions -import com.quranapp.android.utils.sharedPrefs.SPAppActions.addToPendingAction -import com.quranapp.android.utils.sharedPrefs.SPAppActions.setFetchUrlsForce import com.quranapp.android.utils.univ.FileUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import java.io.File import java.io.IOException @@ -40,6 +37,26 @@ class UrlsManager(private val ctx: Context) { private val mFileUtils = FileUtils.newInstance(ctx) private var mCancelled = false + private fun getUrlsFile(): File { + val dir = FileUtils.makeAndGetAppResourceDir(DIR_NAME_4_URLS) + return File(dir, URLS_FILE_NAME) + } + + suspend fun refresh(): AppUrls = withContext(Dispatchers.IO) { + try { + val urls = RetrofitInstance.github.getAppUrls() + val urlsFile = getUrlsFile() + if (mFileUtils.createFile(urlsFile)) { + urlsFile.writeText(JsonHelper.json.encodeToString(urls)) + sAppUrls = urls + } + urls + } catch (e: Exception) { + Log.saveError(e, "UrlsManager.refresh") + throw e + } + } + fun getUrlsJson( readyCallback: (AppUrls) -> Unit, failedCallback: ((Exception) -> Unit)? @@ -49,38 +66,29 @@ class UrlsManager(private val ctx: Context) { return } - val urlsFile = File(FileUtils.makeAndGetAppResourceDir(DIR_NAME_4_URLS), URLS_FILE_NAME) - val forceUrlsDownload = SPAppActions.getFetchUrlsForce(ctx) + val urlsFile = getUrlsFile() - if (!forceUrlsDownload && urlsFile.exists() && urlsFile.length() > 0) { + if (urlsFile.exists() && urlsFile.length() > 0) { try { val urlsData = urlsFile.readText() sAppUrls = JsonHelper.json.decodeFromString(urlsData) readyCallback(sAppUrls!!) - setFetchUrlsForce(ctx, false) } catch (e: Exception) { - addToPendingAction(ctx, AppActions.APP_ACTION_URLS_UPDATE, null) failedCallback?.invoke(e) } } else { - if (!urlsFile.exists() && !mFileUtils.createFile(urlsFile)) { - failedCallback?.invoke(IOException("Could not create urlsFile.")) - return - } - if (!NetworkStateReceiver.canProceed(ctx)) { + failedCallback?.invoke(IOException("No network connection to fetch URLs.")) return } val failureListener = { e: Exception -> - addToPendingAction(ctx, AppActions.APP_ACTION_URLS_UPDATE, null) failedCallback?.invoke(e) } CoroutineScope(Dispatchers.IO).launch { try { - sAppUrls = RetrofitInstance.github.getAppUrls() - urlsFile.writeText(JsonHelper.json.encodeToString(sAppUrls!!)) + val urls = refresh() withContext(Dispatchers.Main) { if (mCancelled) { @@ -88,7 +96,8 @@ class UrlsManager(private val ctx: Context) { failureListener(CancellationException("Canceled by the user.")) return@withContext } - readyCallback(sAppUrls!!) + + readyCallback(urls) } } catch (e: Exception) { withContext(Dispatchers.Main) { diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationModelManager.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationModelManager.kt index cb230729b..cb80bdd23 100644 --- a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationModelManager.kt +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationModelManager.kt @@ -188,6 +188,10 @@ class RecitationModelManager private constructor( } } + suspend fun refreshManifests() { + loadQuranFromNetwork() + loadTranslationFromNetwork() + } suspend fun getCurrentReciterNameForAudioOption(): String { val audioAudio = RecitationPreferences.getAudioOption() diff --git a/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwManager.kt b/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwManager.kt index a402b771e..adcae888a 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwManager.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwManager.kt @@ -4,7 +4,6 @@ import android.content.Context import com.quranapp.android.api.JsonHelper import com.quranapp.android.api.RetrofitInstance import com.quranapp.android.api.models.wbw.AvailableWbwInfoModel -import com.quranapp.android.api.models.wbw.WbwLanguageInfo import com.quranapp.android.utils.Log import com.quranapp.android.utils.app.AppUtils import com.quranapp.android.utils.univ.FileUtils @@ -73,26 +72,6 @@ object WbwManager { } } - suspend fun getStaleResources( - context: Context, - forceManifestRefresh: Boolean = false - ): List { - val available = getAvailable(context, forceManifestRefresh) ?: return emptyList() - val store = WbwVersionStore(context) - return available.wbw.filter { item -> - item.version > store.getItemVersion(item.id) - } - } - - suspend fun isManifestUpdated( - context: Context, - forceManifestRefresh: Boolean = false - ): Boolean { - val available = getAvailable(context, forceManifestRefresh) ?: return false - val localVersion = WbwVersionStore(context).getManifestVersion() - return (available.version ?: 1) > localVersion - } - fun markResourceVersion( context: Context, id: String, @@ -136,12 +115,6 @@ object WbwManager { file.parentFile?.mkdirs() file.writeText(JsonHelper.json.encodeToString(manifest)) - val store = WbwVersionStore(context) - - if ((manifest.version ?: 1) > store.getManifestVersion()) { - store.setManifestVersion(manifest.version) - } - manifest } } diff --git a/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwVersionStore.kt b/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwVersionStore.kt index c561a6b1f..b1173db0d 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwVersionStore.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/wbw/WbwVersionStore.kt @@ -7,7 +7,6 @@ class WbwVersionStore( context: Context ) { companion object { - private const val KEY_MANIFEST_VERSION = "wbw.manifest.version" private const val KEY_ITEM_VERSION_PREFIX = "wbw.item.version." } @@ -15,12 +14,6 @@ class WbwVersionStore( private fun sp() = appContext.getSharedPreferences("sp_wbw_versions", Context.MODE_PRIVATE) - fun getManifestVersion(): Int = sp().getInt(KEY_MANIFEST_VERSION, 0) - - fun setManifestVersion(version: Int?) { - sp().edit { putInt(KEY_MANIFEST_VERSION, version ?: 1) } - } - fun getItemVersion(id: String): Int = sp().getInt(KEY_ITEM_VERSION_PREFIX + id, 0) fun setItemVersion(id: String, version: Int) { diff --git a/app/src/main/java/com/quranapp/android/utils/recommended/Recommended.kt b/app/src/main/java/com/quranapp/android/utils/recommended/Recommended.kt index af302ad06..e62fea3a3 100644 --- a/app/src/main/java/com/quranapp/android/utils/recommended/Recommended.kt +++ b/app/src/main/java/com/quranapp/android/utils/recommended/Recommended.kt @@ -44,7 +44,7 @@ object Recommended { .sortedByDescending { it.priority } .flatMap { rule -> rule.toRecommendations(strings) - } + }.take(3) } private fun loadDocument(context: Context): RecommendedRulesDoc? = try { @@ -115,9 +115,9 @@ private fun Clause.matches(zdt: ZonedDateTime): Boolean { is JsonPrimitive -> el.content == "*" is JsonArray -> el.any { range -> val r = range.jsonArray - + if (r.size < 2) return@any false - + val start = r[0].jsonPrimitive.intOrNull ?: 0 val end = r[1].jsonPrimitive.intOrNull ?: 0 diff --git a/app/src/main/java/com/quranapp/android/utils/sharedPrefs/SPAppConfigs.kt b/app/src/main/java/com/quranapp/android/utils/sharedPrefs/SPAppConfigs.kt index 89d4303cf..c356eb434 100644 --- a/app/src/main/java/com/quranapp/android/utils/sharedPrefs/SPAppConfigs.kt +++ b/app/src/main/java/com/quranapp/android/utils/sharedPrefs/SPAppConfigs.kt @@ -12,10 +12,6 @@ object SPAppConfigs { const val KEY_APP_THEME = "key.app.theme" private const val KEY_APP_LANGUAGE = "key.app.language" private const val KEY_URLS_VERSION = "key.versions.urls" - private const val KEY_TRANSLATIONS_VERSION = "key.versions.translations" - private const val KEY_RECITATIONS_VERSION = "key.versions.recitations" - private const val KEY_RECITATION_TRANSLATIONS_VERSION = "key.versions.recitation_translations" - private const val KEY_TAFSIRS_VERSION = "key.versions.tafsirs" const val LOCALE_DEFAULT = "default" @@ -50,44 +46,4 @@ object SPAppConfigs { fun getLocale(ctx: Context): String = sp(ctx).getString(KEY_APP_LANGUAGE, LOCALE_DEFAULT) ?: LOCALE_DEFAULT - fun getUrlsVersion(ctx: Context): Long = sp(ctx).getLong(KEY_URLS_VERSION, 0) - - fun setUrlsVersion(ctx: Context, version: Long) { - sp(ctx).edit() { - putLong(KEY_URLS_VERSION, version) - } - } - - fun getTranslationsVersion(ctx: Context): Long = sp(ctx).getLong(KEY_TRANSLATIONS_VERSION, 0) - - fun setTranslationsVersion(ctx: Context, version: Long) { - sp(ctx).edit() { - putLong(KEY_TRANSLATIONS_VERSION, version) - } - } - - fun getRecitationsVersion(ctx: Context): Long = sp(ctx).getLong(KEY_RECITATIONS_VERSION, 0) - - fun setRecitationsVersion(ctx: Context, version: Long) { - sp(ctx).edit() { - putLong(KEY_RECITATIONS_VERSION, version) - } - } - - fun getRecitationTranslationsVersion(ctx: Context): Long = - sp(ctx).getLong(KEY_RECITATION_TRANSLATIONS_VERSION, 0) - - fun setRecitationTranslationsVersion(ctx: Context, version: Long) { - sp(ctx).edit() { - putLong(KEY_RECITATIONS_VERSION, version) - } - } - - fun getTafsirsVersion(ctx: Context): Long = sp(ctx).getLong(KEY_TAFSIRS_VERSION, 0) - - fun setTafsirsVersion(ctx: Context, version: Long) { - sp(ctx).edit() { - putLong(KEY_TAFSIRS_VERSION, version) - } - } } diff --git a/app/src/main/java/com/quranapp/android/utils/univ/FileUtils.java b/app/src/main/java/com/quranapp/android/utils/univ/FileUtils.java index 1802c0774..4a57183aa 100644 --- a/app/src/main/java/com/quranapp/android/utils/univ/FileUtils.java +++ b/app/src/main/java/com/quranapp/android/utils/univ/FileUtils.java @@ -147,6 +147,10 @@ public File getAppUpdatesFile() { return new File(getOtherDirectory(), "app_updates.json"); } + public File getResourcesVersionsFile() { + return new File(getOtherDirectory(), "resources_versions.json"); + } + public static File makeAndGetAppResourceDir(String resourceDirName) { File file = new File(appFilesDir, resourceDirName); if (file.exists()) return file; diff --git a/app/src/main/java/com/quranapp/android/viewModels/HomeViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/HomeViewModel.kt deleted file mode 100644 index 9c2772744..000000000 --- a/app/src/main/java/com/quranapp/android/viewModels/HomeViewModel.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.quranapp.android.viewModels - -import android.app.Application -import android.content.Context -import androidx.lifecycle.AndroidViewModel -import com.quranapp.android.utils.app.UpdateManager - -class HomeViewModel(application: Application) : AndroidViewModel(application) { - private var didCheckForUpdates = false - private var updateManager: UpdateManager? = null - - fun attachUpdateManager(context: Context) { - if (updateManager == null) { - updateManager = UpdateManager(context) - } - if (!didCheckForUpdates) { - didCheckForUpdates = true - updateManager?.showUpdateDialogsIfNeeded() - } - } - - fun detachUpdateManager() { - updateManager = null - } - - fun onPause() { - updateManager?.onPause() - } - - fun onResume() { - updateManager?.onResume() - } -} From c34c4409f832813d68b2279cd64ef932d365467f Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Tue, 21 Apr 2026 01:01:43 +0530 Subject: [PATCH 14/16] translation reader improvements --- .../components/reader/TranslationReader.kt | 210 +++++++++---- .../reader/navigator/ChapterVerseNavigator.kt | 11 +- .../reader/navigator/ReaderAppBar.kt | 11 +- .../compose/screens/reader/ReaderScreen.kt | 7 +- .../utils/preferences/ReaderPreferences.kt | 19 -- .../com/quranapp/android/db/dao/MushafDao.kt | 23 ++ .../db/projections/MushafQueryProjections.kt | 5 + .../android/repository/QuranRepository.kt | 72 +++++ .../mediaplayer/RecitationAudioRepository.kt | 5 +- .../mediaplayer/RecitationModelManager.kt | 15 +- .../utils/mediaplayer/RecitationService.kt | 33 ++- .../mediaplayer/RecitationServiceState.kt | 51 +--- .../utils/reader/ReaderChapterIndexFilters.kt | 6 +- .../utils/reader/ReaderItemsBuilder.kt | 275 ++++++++++++++---- .../android/viewModels/ReaderViewModel.kt | 11 +- .../viewModels/WbwSettingsViewModel.kt | 35 +-- .../res/drawable-night/quran_page_bg.webp | Bin 0 -> 694 bytes app/src/main/res/drawable/quran_page_bg.webp | Bin 0 -> 696 bytes app/src/main/res/values/strings.xml | 6 +- 19 files changed, 552 insertions(+), 243 deletions(-) create mode 100644 app/src/main/res/drawable-night/quran_page_bg.webp create mode 100644 app/src/main/res/drawable/quran_page_bg.webp diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt index c7506a71d..d1cf8ad9e 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt @@ -9,6 +9,7 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -28,8 +29,10 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -43,10 +46,13 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.imageResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle @@ -55,28 +61,23 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.quranapp.android.R +import com.quranapp.android.compose.components.ChapterIcon import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.db.relations.SurahWithLocalizations import com.quranapp.android.utils.reader.LocalVerseActions import com.quranapp.android.utils.reader.TranslUtils import com.quranapp.android.utils.reader.TranslationPageBuilderParams import com.quranapp.android.viewModels.ReaderViewModel import kotlinx.coroutines.flow.distinctUntilChanged -data class TranslationPageItem( - val pageNo: Int, - val juzNo: Int, - val hizbNo: Int, - val chapterNames: String, - val translationSlug: String, - val annotatedText: AnnotatedString, - val verses: List, -) - data class TranslationPageVerse( val chapterNo: Int, val verseNo: Int, @@ -86,6 +87,26 @@ data class TranslationPageVerse( val rangeEnd: Int, ) +sealed class TranslationPageSection { + object Divider : TranslationPageSection() + data class Title(val swl: SurahWithLocalizations) : TranslationPageSection() + object Bismillah : TranslationPageSection() + + data class Text( + val annotatedText: AnnotatedString, + var annotatedTextNormalized: AnnotatedString? = null, + val verses: List, + ) : TranslationPageSection() +} + +data class TranslationPageItem( + val pageNo: Int, + val juzNo: Int, + val hizbNo: Int, + val chapterNames: String, + val translationSlug: String, + val sections: List, +) @Composable fun ReaderLayoutTranslationPageMode( @@ -101,7 +122,7 @@ fun ReaderLayoutTranslationPageMode( } val context = LocalContext.current - val colors = MaterialTheme.colorScheme + val colors = colorScheme val typography = MaterialTheme.typography val verseActions = LocalVerseActions.current val translSizeMult = ReaderPreferences.observeTranlationTextSizeMultiplier() @@ -270,6 +291,9 @@ fun ReaderLayoutTranslationPageMode( } } + + val bgPattern = ImageBitmap.imageResource(R.drawable.quran_page_bg) + SelectionContainer { LazyColumn( state = listState, @@ -290,6 +314,7 @@ fun ReaderLayoutTranslationPageMode( TranslationModePage( readerVm = readerVm, pageNo = pageIndex + 1, + bgPattern, ) } } @@ -301,6 +326,7 @@ fun ReaderLayoutTranslationPageMode( private fun TranslationModePage( readerVm: ReaderViewModel, pageNo: Int, + bgPattern: ImageBitmap, ) { val i by remember(pageNo) { derivedStateOf { readerVm.translationPageItems[pageNo] } @@ -318,21 +344,27 @@ private fun TranslationModePage( val isRtl = TranslUtils.isRtl(item.translationSlug) val colors = colorScheme - val displayText = remember(item.annotatedText, item.verses, isPlaying, playingVerse, colors) { - buildAnnotatedString { - append(item.annotatedText) + val sections = remember(item.sections, isPlaying, playingVerse, colors) { + item.sections.map { section -> + if (section is TranslationPageSection.Text) { + section.annotatedTextNormalized = buildAnnotatedString { + append(section.annotatedText) - if (!isPlaying || !playingVerse.isValid) return@buildAnnotatedString + if (!isPlaying || !playingVerse.isValid) return@buildAnnotatedString - val v = item.verses.find { - it.chapterNo == playingVerse.chapterNo && it.verseNo == playingVerse.verseNo - } ?: return@buildAnnotatedString + val v = section.verses.find { + it.chapterNo == playingVerse.chapterNo && it.verseNo == playingVerse.verseNo + } ?: return@buildAnnotatedString - addStyle( - SpanStyle(background = colors.primary.alpha(0.2f)), - v.rangeStart, - v.rangeEnd, - ) + addStyle( + SpanStyle(background = colors.primary.alpha(0.2f)), + v.rangeStart, + v.rangeEnd, + ) + } + } + + section } } @@ -347,46 +379,83 @@ private fun TranslationModePage( modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp), - shape = RoundedCornerShape(4.dp), - color = colorScheme.surfaceContainer, + shape = shapes.small, + color = colorScheme.surface, border = BorderStroke( 1.dp, - colorScheme.outlineVariant.copy(alpha = 0.45f), + colorScheme.outlineVariant, ), + shadowElevation = 1.dp ) { - Column() { - TranslationBookPageHeader( - chapterNames = item.chapterNames, - pageNo = item.pageNo, - juzNo = item.juzNo, - ) + Box( + modifier = Modifier + .fillMaxWidth() + .drawBehind { + val scale = 3f + + val tileW = (bgPattern.width * scale).toInt() + val tileH = (bgPattern.height * scale).toInt() + + var y = 0f + while (y < size.height) { + var x = 0f + while (x < size.width) { + + drawImage( + image = bgPattern, + dstOffset = IntOffset(x.toInt(), y.toInt()), + dstSize = IntSize(tileW, tileH), + ) + + x += tileW + } + y += tileH + } + } + ) { + Column(Modifier.fillMaxWidth()) { + TranslationBookPageHeader(item) - HorizontalDivider( - color = colorScheme.outlineVariant.copy(alpha = 0.55f), - ) + HorizontalDivider( + color = colorScheme.outlineVariant, + ) - Text( - text = displayText, - modifier = Modifier - .fillMaxWidth() - .padding(14.dp), - style = TextStyle(textDirection = textDirection), - ) + sections.forEach { + when (it) { + TranslationPageSection.Divider -> HorizontalDivider( + modifier = Modifier.padding(vertical = 8.dp), + color = colorScheme.outlineVariant, + ) + + is TranslationPageSection.Title -> TranslationReaderChapterTitle(it.swl) + is TranslationPageSection.Bismillah -> Bismillah() + is TranslationPageSection.Text -> { + if (it.annotatedTextNormalized != null) { + Text( + text = it.annotatedTextNormalized!!, + modifier = Modifier + .fillMaxWidth() + .padding(14.dp), + style = TextStyle(textDirection = textDirection), + ) + } + } + } + } + } } } } } @Composable -private fun TranslationBookPageHeader( - chapterNames: String, - pageNo: Int, - juzNo: Int, -) { +private fun TranslationBookPageHeader(item: TranslationPageItem) { val typography = MaterialTheme.typography val scheme = colorScheme val juzLabel = - if (juzNo > 0) stringResource(R.string.strLabelJuzNo, juzNo) else "" + if (item.juzNo > 0) stringResource(R.string.strLabelJuzNo, item.juzNo) else "" + val hizbLabel = + if (item.hizbNo > 0) stringResource(R.string.labelHizbNo, item.hizbNo) else "" Row( modifier = Modifier @@ -399,8 +468,8 @@ private fun TranslationBookPageHeader( contentAlignment = Alignment.CenterStart, ) { Text( - text = chapterNames.ifBlank { "—" }, - style = typography.bodyMedium, + text = item.chapterNames, + style = typography.labelSmall, color = scheme.onSurface.alpha(0.75f), maxLines = 2, overflow = TextOverflow.Ellipsis, @@ -414,7 +483,7 @@ private fun TranslationBookPageHeader( } Text( - text = stringResource(R.string.strLabelPageNo, pageNo), + text = stringResource(R.string.strLabelPageNo, item.pageNo), style = typography.labelMedium, color = scheme.onBackground.alpha(0.75f), modifier = Modifier @@ -429,8 +498,8 @@ private fun TranslationBookPageHeader( contentAlignment = Alignment.CenterEnd, ) { Text( - text = juzLabel.ifBlank { "—" }, - style = typography.bodyMedium, + text = "${juzLabel}, ${hizbLabel}", + style = typography.labelSmall, color = scheme.onSurface.alpha(0.75f), maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -443,7 +512,7 @@ private fun TranslationBookPageHeader( @Composable private fun TranslationPageLoadingSkeleton() { - val scheme = MaterialTheme.colorScheme + val scheme = colorScheme val transition = rememberInfiniteTransition(label = "translation_page_sk") val pulse by transition.animateFloat( initialValue = 0.1f, @@ -521,3 +590,36 @@ private fun TranslationPageLoadingSkeleton() { } } +@Composable +fun TranslationReaderChapterTitle( + swl: SurahWithLocalizations, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically + ) { + + Column( + horizontalAlignment = Alignment.End + ) { + Text(stringResource(R.string.strLabelSurah, swl.getCurrentName()), style = typography.labelLarge) + Text(swl.getCurrentMeaning(), style = typography.bodyMedium, color = colorScheme.onSurface.alpha(0.75f)) + } + + VerticalDivider( + modifier = Modifier.height(32.dp), + color = colorScheme.onSurface + ) + + ChapterIcon( + swl.surah.surahNo, + fontSize = 36.sp, + modifier = Modifier.padding(top = 8.dp), + color = colorScheme.primary + ) + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ChapterVerseNavigator.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ChapterVerseNavigator.kt index 18b99b5f1..55a731d54 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ChapterVerseNavigator.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ChapterVerseNavigator.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState @@ -41,7 +40,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @@ -152,7 +150,7 @@ private fun Content( if (showVerseSelector) { val chapterNo = selectedChapterNo if (chapterNo != null) { - ChapterVerseMultiSelectList( + VerseSelectList( currentChapter = chapterNo.let { no -> surahs.getOrNull(no - 1)?.surah }, selectedVerseNos = selectedVerseNos, onToggleVerse = { verseNo -> @@ -237,7 +235,7 @@ private fun RowScope.ChapterOnlyList( } @Composable -private fun ChapterVerseMultiSelectList( +private fun VerseSelectList( currentChapter: SurahEntity?, selectedVerseNos: Set, onToggleVerse: (Int) -> Unit, @@ -270,7 +268,7 @@ private fun ChapterVerseMultiSelectList( } } - Column(modifier = Modifier.width(100.dp)) { + Column(modifier = Modifier.width(110.dp)) { Box( modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp), ) { @@ -316,9 +314,6 @@ private fun ChapterVerseMultiSelectList( modifier = Modifier .clickable { onToggleVerse(verseNo) } .padding(10.dp), - style = typography.bodyMedium.copy( - fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, - ), color = if (selected) colorScheme.primary else colorScheme.onSurface, ) } diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderAppBar.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderAppBar.kt index 7d6046832..6c05d65ee 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderAppBar.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/navigator/ReaderAppBar.kt @@ -80,7 +80,7 @@ private val ReaderAppBarHeight = 86.dp private val ReaderHeaderHeight = 52.dp private val ReaderDividerHeight = 1.dp internal val ReaderAppBarExpandedHeight = - ReaderAppBarHeight + ReaderHeaderHeight + ReaderDividerHeight + ReaderAppBarHeight + ReaderHeaderHeight + (ReaderDividerHeight * 2) @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -181,6 +181,11 @@ fun ReaderAppBar( } } } + + HorizontalDivider( + thickness = ReaderDividerHeight, + color = colorScheme.outlineVariant.alpha(0.5f) + ) } } @@ -526,7 +531,7 @@ private fun StickyHeaderModeTranslation( ) } ) - .padding(8.dp), + .padding(horizontal = 10.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( @@ -540,7 +545,7 @@ private fun StickyHeaderModeTranslation( Text( bookName, - style = typography.labelLarge, + style = typography.labelMedium, color = colorScheme.onSurface.alpha(0.75f), maxLines = 1, modifier = Modifier diff --git a/app/src/main/java/com/quranapp/android/compose/screens/reader/ReaderScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/reader/ReaderScreen.kt index 815f49c8b..23bfc3cf4 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/reader/ReaderScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/reader/ReaderScreen.kt @@ -29,11 +29,13 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.quranapp.android.compose.components.player.MINI_PLAYER_HEIGHT import com.quranapp.android.compose.components.player.RecitationPlayerSheet import com.quranapp.android.compose.components.reader.ReaderLayout +import com.quranapp.android.compose.components.reader.ReaderMode import com.quranapp.android.compose.components.reader.ReaderProvider import com.quranapp.android.compose.components.reader.navigator.ReaderAppBar import com.quranapp.android.compose.components.reader.navigator.ReaderAppBarExpandedHeight @@ -66,6 +68,8 @@ fun ReaderScreen(params: ReaderLaunchParams) { var isSyncing by remember { mutableStateOf(false) } val syncIndicatorLocked = playerVerseSyncPref && isSyncing + val readerMode by readerVm.readerMode.collectAsStateWithLifecycle() + var lastInitParams by remember { mutableStateOf(null) } LaunchedEffect(params) { @@ -101,7 +105,8 @@ fun ReaderScreen(params: ReaderLaunchParams) { scrollBehavior = scrollBehavior, ) }, - containerColor = if (isDark) colorScheme.background else colorScheme.surface + containerColor = if (isDark || readerMode == ReaderMode.Translation) colorScheme.background + else colorScheme.surface ) { padding -> val chromeCollapsedFraction = scrollBehavior.state.collapsedFraction diff --git a/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt b/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt index 420c3b9ee..e4b6799a6 100644 --- a/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt +++ b/app/src/main/java/com/quranapp/android/compose/utils/preferences/ReaderPreferences.kt @@ -459,23 +459,4 @@ object ReaderPreferences { fun wbwTextSizeMultiplierFlow(): Flow { return DataStoreManager.flow(KEY_TEXT_SIZE_MULT_WBW) } - - fun validateWbwId( - id: String?, - availableIds: Set, - fallback: String? = null, - ): String? { - val normalizedId = id?.takeIf { it.isNotBlank() } - val normalizedFallback = fallback?.takeIf { it.isNotBlank() } - - if (normalizedId != null && availableIds.contains(normalizedId)) { - return normalizedId - } - - if (normalizedFallback != null && availableIds.contains(normalizedFallback)) { - return normalizedFallback - } - - return null - } } diff --git a/app/src/main/java/com/quranapp/android/db/dao/MushafDao.kt b/app/src/main/java/com/quranapp/android/db/dao/MushafDao.kt index 1f654f4cd..773606983 100644 --- a/app/src/main/java/com/quranapp/android/db/dao/MushafDao.kt +++ b/app/src/main/java/com/quranapp/android/db/dao/MushafDao.kt @@ -5,6 +5,7 @@ import androidx.room.Query import com.quranapp.android.db.entities.quran.MushafEntity import com.quranapp.android.db.entities.quran.MushafMapEntity import com.quranapp.android.db.projections.AyahPageProjection +import com.quranapp.android.db.projections.PageHizbProjection import com.quranapp.android.db.projections.PageJuzProjection @Dao @@ -149,6 +150,28 @@ interface MushafDao { pageNumbers: List, ): List + /** + * Hizb from the first ayah line per page (by [MushafMapEntity.lineNumber]), matching [getHizbForPage]. + */ + @Query( + """ + SELECT m.page_number, a.hizb_no + FROM mushaf_map m + INNER JOIN ayahs a ON m.start_ayah_id = a.ayah_id + WHERE m.mushaf_id = :mushafId AND m.page_number IN (:pageNumbers) + AND m.start_ayah_id IS NOT NULL + AND m.line_number = ( + SELECT MIN(m2.line_number) FROM mushaf_map m2 + WHERE m2.mushaf_id = :mushafId AND m2.page_number = m.page_number + AND m2.start_ayah_id IS NOT NULL + ) + """ + ) + suspend fun getHizbForPages( + mushafId: Int, + pageNumbers: List, + ): List + @Query( """ SELECT a.ayah_id AS ayah_id, MIN(m.page_number) AS page_number diff --git a/app/src/main/java/com/quranapp/android/db/projections/MushafQueryProjections.kt b/app/src/main/java/com/quranapp/android/db/projections/MushafQueryProjections.kt index 88b539e71..5a3a47db0 100644 --- a/app/src/main/java/com/quranapp/android/db/projections/MushafQueryProjections.kt +++ b/app/src/main/java/com/quranapp/android/db/projections/MushafQueryProjections.kt @@ -11,3 +11,8 @@ data class PageJuzProjection( @ColumnInfo(name = "page_number") val pageNumber: Int, @ColumnInfo(name = "juz_no") val juzNo: Int, ) + +data class PageHizbProjection( + @ColumnInfo(name = "page_number") val pageNumber: Int, + @ColumnInfo(name = "hizb_no") val hizbNo: Int, +) diff --git a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt index a885832c7..09414c994 100644 --- a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt +++ b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt @@ -474,6 +474,56 @@ class QuranRepository( .associate { it.pageNumber to it.juzNo } } + suspend fun getHizbForMushafPages( + mushafId: Int, + pageNumbers: List, + ): Map { + if (mushafId <= 0 || pageNumbers.isEmpty()) return emptyMap() + return mushafDao.getHizbForPages(mushafId, pageNumbers) + .associate { it.pageNumber to it.hizbNo } + } + + suspend fun getSurahsWithLocalizationsByChapterNos( + chapterNos: List, + ): Map { + if (chapterNos.isEmpty()) return emptyMap() + return surahDao.getSurahsWithLocalizationsByNos(chapterNos) + .associateBy { it.surah.surahNo } + } + + suspend fun getAyahEntitiesForMushafAyahLines(rows: List): Map { + val allIds = LinkedHashSet() + val intervals = mutableListOf>() + for (row in rows) { + if (row.lineType != MushafLineType.ayah) continue + val start = row.startAyahId ?: continue + val end = row.endAyahId ?: continue + if (row.startWordIndex == null || row.endWordIndex == null) continue + if (start > end) continue + if (start == end) { + allIds.add(start) + } else { + allIds.add(start) + allIds.add(end) + intervals.add(start to end) + } + } + val byId = HashMap() + if (allIds.isNotEmpty()) { + for (a in ayahDao.getAyahsByIds(allIds.toList())) { + byId[a.ayahId] = a + } + } + for ((s, e) in mergeAyahIdIntervals(intervals)) { + if (e - s > 1) { + for (a in ayahDao.getAyahsStrictlyBetween(s, e)) { + byId[a.ayahId] = a + } + } + } + return byId + } + /** * Preloads full-word lists for all ayahs touched by mushaf ayah lines (for a prefetch batch). */ @@ -482,6 +532,7 @@ class QuranRepository( scriptCode: String, ): Map> { val ids = LinkedHashSet() + for (row in ayahLineRows) { if (row.lineType != MushafLineType.ayah) continue val startAyah = row.startAyahId ?: continue @@ -499,6 +550,7 @@ class QuranRepository( } } } + if (ids.isEmpty()) return emptyMap() val flat = ayahWordDao.getWordsForAyahs(ids.toList(), scriptCode) return groupWordsByAyahIdWithLastFlags(flat) @@ -680,3 +732,23 @@ class QuranRepository( return getSurah(chapterNo)?.isVerseValid(verseNo) == true } } + +private fun mergeAyahIdIntervals(intervals: List>): List> { + if (intervals.isEmpty()) return emptyList() + val sorted = intervals.sortedBy { it.first } + val out = mutableListOf>() + var cs = sorted[0].first + var ce = sorted[0].second + for (i in 1 until sorted.size) { + val (s, e) = sorted[i] + if (s <= ce) { + ce = maxOf(ce, e) + } else { + out.add(cs to ce) + cs = s + ce = e + } + } + out.add(cs to ce) + return out +} diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt index 126eda701..58a11b059 100644 --- a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationAudioRepository.kt @@ -94,10 +94,11 @@ class RecitationAudioRepository(private val context: Context) { */ fun resolveAudioUris( chapterNo: Int, + settings: PlayerSettings ): Flow = flow { - val audioOption = RecitationPreferences.getAudioOption() + val (quranModel, translationModel) = modelManager.resolveModels(settings) - val (quranModel, translationModel) = modelManager.resolveModels() + val audioOption = settings.audioOption val shouldPlayArabic = audioOption != AudioOption.ONLY_TRANSLATION val shouldPlayTranslation = audioOption != AudioOption.ONLY_QURAN diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationModelManager.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationModelManager.kt index cb80bdd23..3ba282ac3 100644 --- a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationModelManager.kt +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationModelManager.kt @@ -67,15 +67,20 @@ class RecitationModelManager private constructor( } } - suspend fun resolveModels(): Pair { - val audioOption = RecitationPreferences.getAudioOption() - + suspend fun resolveModels(settings: PlayerSettings): Pair { + val audioOption = settings.audioOption + val reciterId = settings.reciter + val translationReciterId = settings.translationReciter val resolveQuran = audioOption != AudioOption.ONLY_TRANSLATION val resolveTranslation = audioOption != AudioOption.ONLY_QURAN return Pair( - if (resolveQuran) getSelectedQuranModel() else null, - if (resolveTranslation) getSelectedTranslationModel() else null + if (resolveQuran) (if (reciterId.isNullOrBlank()) getSelectedQuranModel() else getQuranModel( + reciterId + )) else null, + if (resolveTranslation) (if (translationReciterId.isNullOrBlank()) getSelectedTranslationModel() else getTranslationModel( + translationReciterId + )) else null ) } diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationService.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationService.kt index ffebdc3a0..efa47f4ca 100644 --- a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationService.kt +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationService.kt @@ -150,7 +150,8 @@ class RecitationService : MediaSessionService() { private var verseTrackingJob: Job? = null private var latestPlaybackRequestId: Long = 0L - private val chapterResolutionRequests = mutableMapOf>() + private val chapterResolutionRequests = + mutableMapOf>() private var repeatMessage: androidx.media3.exoplayer.PlayerMessage? = null private var repeatRemainingPlaysForCurrentItem: Int = 0 @@ -521,7 +522,10 @@ class RecitationService : MediaSessionService() { * continue downloading in parallel. */ private suspend fun resolveChapterAudio(chapterNo: Int): ResolvedAudioResult { - val inFlight = chapterResolutionRequests[chapterNo] + val settings = state.value.settings + val req = AudioResolutionRequest(chapterNo, settings) + + val inFlight = chapterResolutionRequests[req] if (inFlight != null && inFlight.isActive) { return inFlight.await() } @@ -530,7 +534,10 @@ class RecitationService : MediaSessionService() { try { var terminal: ResolvedAudioResult? = null - audioRepository.resolveAudioUris(chapterNo).collect { result -> + audioRepository.resolveAudioUris( + chapterNo = chapterNo, + settings = settings + ).collect { result -> when (result) { is ResolvedAudioResult.Downloading -> { // Playback resolution no longer emits this; bulk/explicit downloads use WorkManager UI. @@ -549,13 +556,13 @@ class RecitationService : MediaSessionService() { } } - chapterResolutionRequests[chapterNo] = deferred + chapterResolutionRequests[req] = deferred return try { deferred.await() } finally { - if (chapterResolutionRequests[chapterNo] == deferred && deferred.isCompleted) { - chapterResolutionRequests.remove(chapterNo) + if (chapterResolutionRequests[req] == deferred && deferred.isCompleted) { + chapterResolutionRequests.remove(req) } } } @@ -876,7 +883,12 @@ class RecitationService : MediaSessionService() { val chapterNo = current.chapterNo val verseNo = current.verseNo + if (player.isPlaying) { + player.pause() + } + val shouldResumePlaying = player.isPlaying + val requestId = ++latestPlaybackRequestId awaitChapterResolution(requestId, chapterNo) { @@ -976,15 +988,10 @@ class RecitationService : MediaSessionService() { } } - private fun handlePlaybackEnded() {/*fixme: temporarily disable + private fun handlePlaybackEnded() { if (!state.value.settings.continueRange) return - scoped { - val meta = requestQuranMeta() - val next = state.value.getNextChapter(meta) - if (next != null) reciteVerse(next.chapterNo, next.verseNo) - else stopMedia() - }*/ + reciteNextVerse() } // ==================== Session callback & command dispatch ==================== diff --git a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationServiceState.kt b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationServiceState.kt index 6457ec498..0f903d457 100644 --- a/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationServiceState.kt +++ b/app/src/main/java/com/quranapp/android/utils/mediaplayer/RecitationServiceState.kt @@ -20,6 +20,9 @@ data class PlayerSettings( val translationReciter: String? = null ) + +data class AudioResolutionRequest(val chapterNo: Int, val settings: PlayerSettings) + data class RecitationServiceState( val currentVerse: ChapterVersePair = ChapterVersePair(1, 1), /** Helps in indiacating if the player is resolving and also for which chapter no */ @@ -103,54 +106,6 @@ data class RecitationServiceState( return ChapterVersePair(nextChapterNo, nextVerseNo) } - fun getPreviousChapter( - repository: QuranRepository - ): ChapterVersePair? { - val currentChapterNo = currentVerse.chapterNo - - if (!QuranMeta.isChapterValid(currentChapterNo)) { - return null - } - - var previousChapterNo = currentChapterNo - var previousVerseNo = -1 - - if (QuranMeta.isChapterValid(previousChapterNo - 1)) { - previousChapterNo-- - previousVerseNo = 1 - } - - if (previousChapterNo == -1 || previousVerseNo == -1) { - return null - } - - return ChapterVersePair(previousChapterNo, previousVerseNo) - } - - fun getNextChapter( - meta: QuranMeta - ): ChapterVersePair? { - val currentChapterNo = currentVerse.chapterNo - - if (!QuranMeta.isChapterValid(currentChapterNo)) { - return null - } - - var nextChapterNo = currentChapterNo - var nextVerseNo = -1 - - if (QuranMeta.isChapterValid(nextChapterNo + 1)) { - nextChapterNo++ - nextVerseNo = 1 - } - - if (nextChapterNo == -1 || nextVerseNo == -1) { - return null - } - - return ChapterVersePair(nextChapterNo, nextVerseNo) - } - fun toBundle(): Bundle = Bundle().apply { putInt(KEY_CURRENT_CHAPTER, currentVerse.chapterNo) putInt(KEY_CURRENT_VERSE, currentVerse.verseNo) diff --git a/app/src/main/java/com/quranapp/android/utils/reader/ReaderChapterIndexFilters.kt b/app/src/main/java/com/quranapp/android/utils/reader/ReaderChapterIndexFilters.kt index 4632b952f..b038197a1 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/ReaderChapterIndexFilters.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/ReaderChapterIndexFilters.kt @@ -67,13 +67,13 @@ fun List.filteredByChapterIndex( when (filters.length) { ReaderChapterLengthFilter.short -> - result = result.filter { it.surah.ayahCount <= 100 } + result = result.filter { it.surah.ayahCount < 50 } ReaderChapterLengthFilter.medium -> - result = result.filter { it.surah.ayahCount in 101..200 } + result = result.filter { it.surah.ayahCount in 50..150 } ReaderChapterLengthFilter.long -> - result = result.filter { it.surah.ayahCount >= 201 } + result = result.filter { it.surah.ayahCount > 150 } ReaderChapterLengthFilter.any -> Unit } diff --git a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt index 6d51e339f..33a0c0b69 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/ReaderItemsBuilder.kt @@ -22,17 +22,24 @@ import com.quranapp.android.compose.components.reader.QuranPageLineItem import com.quranapp.android.compose.components.reader.ReaderLayoutItem import com.quranapp.android.compose.components.reader.ReaderPreparedData import com.quranapp.android.compose.components.reader.TranslationPageItem +import com.quranapp.android.compose.components.reader.TranslationPageSection import com.quranapp.android.compose.components.reader.TranslationPageVerse import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.ChapterVerseBatch +import com.quranapp.android.db.entities.quran.AyahEntity import com.quranapp.android.db.entities.quran.AyahWordEntity import com.quranapp.android.db.entities.quran.MushafLineType import com.quranapp.android.db.entities.quran.MushafMapEntity +import com.quranapp.android.db.relations.SurahWithLocalizations import com.quranapp.android.db.relations.VerseWithDetails import com.quranapp.android.repository.QuranRepository import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.reader.factory.QuranTranslationFactory +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope private data class SectionSnapshot( @@ -499,6 +506,7 @@ object ReaderItemsBuilder { .flatten() .filter { it.lineType == MushafLineType.ayah } .toList() + val wordCacheFull = quranRepository.preloadMushafLineWordCache(ayahRows, scriptCode) val wordCache = wordCacheFull.takeIf { it.isNotEmpty() } @@ -559,12 +567,37 @@ object ReaderItemsBuilder { val distinct = pageNumbers.filter { it > 0 }.distinct().sorted() if (distinct.isEmpty()) return emptyMap() - val scriptCode = ReaderPreferences.getQuranScript() - val mushafId = scriptCode.toQuranMushafId(ReaderPreferences.getQuranScriptVariant()) + val mushafId = ReaderPreferences.getQuranScript() + .toQuranMushafId(ReaderPreferences.getQuranScriptVariant()) if (mushafId <= 0) return emptyMap() val linesByPage = quranRepository.getPageLinesGroupedForPages(mushafId, distinct) val juzByPage = quranRepository.getJuzForMushafPages(mushafId, distinct) + val hizbByPage = quranRepository.getHizbForMushafPages(mushafId, distinct) + + val ayahRows = distinct.flatMap { page -> + linesByPage[page].orEmpty().filter { it.lineType == MushafLineType.ayah } + } + val ayahById = quranRepository.getAyahEntitiesForMushafAyahLines(ayahRows) + val sortedAyahIds = ayahById.keys.sorted() + + val chapterMinVerse = HashMap() + val chapterMaxVerse = HashMap() + for (entity in ayahById.values) { + val c = entity.surahNo + val v = entity.ayahNo + chapterMinVerse[c] = minOf(chapterMinVerse[c] ?: v, v) + chapterMaxVerse[c] = maxOf(chapterMaxVerse[c] ?: v, v) + } + val chapterNos = chapterMinVerse.keys.sorted() + val surahByNo = quranRepository.getSurahsWithLocalizationsByChapterNos(chapterNos) + + val chapterNamesByPage = LinkedHashMap(distinct.size) + for (pageNo in distinct) { + chapterNamesByPage[pageNo] = + quranRepository.getChapterNamesOnMushafPage(mushafId, pageNo) + } + val out = LinkedHashMap(distinct.size) QuranTranslationFactory(context).use { factory -> @@ -584,32 +617,147 @@ object ReaderItemsBuilder { ) val paragraphStyle = ts.toParagraphStyle() - for (pageNo in distinct) { - val rows = linesByPage[pageNo].orEmpty().sortedBy { it.lineNumber } - val drafts = ArrayList() - val seenAyahIds = mutableSetOf() + val translationByChapterVerse = HashMap, Translation>( + ayahById.size + ) + for (chap in chapterNos) { + val minV = chapterMinVerse[chap] ?: continue + val maxV = chapterMaxVerse[chap] ?: continue + val range = factory.getTranslationsVerseRange(slugSet, chap, minV, maxV) + for (v in minV..maxV) { + val idx = v - minV + val transl = range.getOrNull(idx)?.firstOrNull() ?: continue + translationByChapterVerse[chap to v] = transl + } + } - for (row in rows) { - if (row.lineType != MushafLineType.ayah) continue + coroutineScope { + distinct.map { pageNo -> + async(Dispatchers.Default) { + val item = buildOneTranslationPage( + pageNo = pageNo, + rows = linesByPage[pageNo].orEmpty().sortedBy { it.lineNumber }, + ayahById = ayahById, + sortedAyahIds = sortedAyahIds, + translationByChapterVerse = translationByChapterVerse, + surahByNo = surahByNo, + paragraphStyle = paragraphStyle, + translationSpanStyle = translationSpanStyle, + translationSpanPressedStyle = translationSpanPressedStyle, + slugSet = slugSet, + translationSlug = translationSlug, + params = params, + juzNo = juzByPage[pageNo] ?: -1, + hizbNo = hizbByPage[pageNo] ?: -1, + chapterNames = chapterNamesByPage[pageNo].orEmpty(), + ) + pageNo to item + } + }.awaitAll().forEach { (pageNo, item) -> + out[pageNo] = item + } + } + } - val ayahIds = quranRepository.ayahIdsForMushafAyahLine(row) + return out + } - for (ayahId in ayahIds) { - if (!seenAyahIds.add(ayahId)) continue + private fun buildOneTranslationPage( + pageNo: Int, + rows: List, + ayahById: Map, + sortedAyahIds: List, + translationByChapterVerse: Map, Translation>, + surahByNo: Map, + paragraphStyle: ParagraphStyle, + translationSpanStyle: SpanStyle, + translationSpanPressedStyle: SpanStyle, + slugSet: Set, + translationSlug: String, + params: TranslationPageBuilderParams, + juzNo: Int, + hizbNo: Int, + chapterNames: String, + ): TranslationPageItem { + val sections = ArrayList() + val drafts = ArrayList() + val seenAyahIds = mutableSetOf() + var hasChapterTitleOnPage = false + + fun flushDrafts() { + if (drafts.isEmpty()) return + + val verses = ArrayList(drafts.size) + + val annotatedText = buildAnnotatedString { + withStyle(paragraphStyle) { + drafts.forEachIndexed { index, d -> + if (index > 0) append(" ") + val start = length + append(d.annotatedText) + val end = length + verses.add( + TranslationPageVerse( + chapterNo = d.chapterNo, + verseNo = d.verseNo, + rangeStart = start, + rangeEnd = end, + ) + ) + } + } + } - val ayah = quranRepository.getAyahById(ayahId) ?: continue + sections.add( + TranslationPageSection.Text( + annotatedText = annotatedText, + verses = verses, + ) + ) - val verseDetails = quranRepository.getVerseWithDetails( - ayah.surahNo, - ayah.ayahNo, - scriptCode, - ) ?: continue + drafts.clear() + } + + for (row in rows) { + when (row.lineType) { + MushafLineType.surah_name -> { + flushDrafts() + val chapter = row.surahNo.takeIf { it != null && it > 0 } ?: continue + + surahByNo.get(chapter)?.let { swl -> + if (hasChapterTitleOnPage) { + sections.add(TranslationPageSection.Divider) + } + + sections.add(TranslationPageSection.Title(swl)) + hasChapterTitleOnPage = true + } + } + + MushafLineType.basmallah -> { + flushDrafts() + sections.add(TranslationPageSection.Bismillah) + } + + MushafLineType.ayah -> { + val ayahIds = ayahIdsForMushafAyahLineCached(row, sortedAyahIds) - val transl = factory.getTranslationsSingleVerse( - slugSet, - ayah.surahNo, - ayah.ayahNo, - ).firstOrNull() ?: continue + for (ayahId in ayahIds) { + if (!seenAyahIds.add(ayahId)) continue + + val ayah = ayahById[ayahId] ?: continue + val transl = + translationByChapterVerse[ayah.surahNo to ayah.ayahNo] ?: continue + val surah = surahByNo[ayah.surahNo] ?: continue + + val verseDetails = VerseWithDetails( + words = emptyList(), + pageNo = 0, + verse = ayah, + chapter = surah, + ).apply { + translations = listOf(transl) + } val annotated = buildAnnotatedString { withLink( @@ -666,47 +814,64 @@ object ReaderItemsBuilder { ) } } + } + } - val verses = ArrayList(drafts.size) - val annotatedText = buildAnnotatedString { - withStyle(paragraphStyle) { - drafts.forEachIndexed { index, d -> - if (index > 0) append(" ") - - val start = length - - append(d.annotatedText) + flushDrafts() - val end = length + return TranslationPageItem( + pageNo = pageNo, + juzNo = juzNo, + hizbNo = hizbNo, + chapterNames = chapterNames, + translationSlug = translationSlug, + sections = sections, + ) + } - verses.add( - TranslationPageVerse( - chapterNo = d.chapterNo, - verseNo = d.verseNo, - rangeStart = start, - rangeEnd = end, - ) - ) - } + private fun ayahIdsForMushafAyahLineCached( + row: MushafMapEntity, + sortedAyahIds: List, + ): List { + if (row.lineType != MushafLineType.ayah) return emptyList() + val startAyah = row.startAyahId ?: return emptyList() + val endAyah = row.endAyahId ?: return emptyList() + if (row.startWordIndex == null || row.endWordIndex == null) return emptyList() + if (startAyah > endAyah) return emptyList() + if (startAyah == endAyah) return listOf(startAyah) + return buildList { + add(startAyah) + if (endAyah - startAyah > 1) { + val i0 = firstIndexStrictlyGreater(sortedAyahIds, startAyah) + val i1 = lastIndexStrictlyLess(sortedAyahIds, endAyah) + if (i0 <= i1) { + for (i in i0..i1) { + add(sortedAyahIds[i]) } } - - val hizbNo = quranRepository.getHizbForMushafPage(mushafId, pageNo) - val chapterNames = quranRepository.getChapterNamesOnMushafPage(mushafId, pageNo) - - out[pageNo] = TranslationPageItem( - pageNo = pageNo, - juzNo = juzByPage[pageNo] ?: -1, - hizbNo = hizbNo, - chapterNames = chapterNames, - translationSlug = translationSlug, - annotatedText = annotatedText, - verses = verses, - ) } + add(endAyah) } + } - return out + private fun firstIndexStrictlyGreater(sorted: List, v: Int): Int { + var lo = 0 + var hi = sorted.size + while (lo < hi) { + val mid = (lo + hi) ushr 1 + if (sorted[mid] <= v) lo = mid + 1 else hi = mid + } + return lo + } + + private fun lastIndexStrictlyLess(sorted: List, v: Int): Int { + var lo = -1 + var hi = sorted.size - 1 + while (lo < hi) { + val mid = (lo + hi + 1) ushr 1 + if (sorted[mid] < v) lo = mid else hi = mid - 1 + } + return lo } private suspend fun mapMushafRowToLineItem( diff --git a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt index e0b6d69a6..0c4505879 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/ReaderViewModel.kt @@ -18,9 +18,9 @@ import com.quranapp.android.compose.components.reader.ReaderLayoutItem import com.quranapp.android.compose.components.reader.ReaderMode import com.quranapp.android.compose.components.reader.ReaderPreparedData import com.quranapp.android.compose.components.reader.TranslationPageItem +import com.quranapp.android.compose.components.reader.TranslationPageSection import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.entities.ReadHistoryEntity -import com.quranapp.android.utils.Log import com.quranapp.android.utils.others.ShortcutUtils import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.quran.QuranUtils @@ -44,7 +44,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -439,7 +438,11 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic val page = translationPageItems[pageNo] if (page != null) { - val firstVerse = page.verses.firstOrNull() + val firstVerse = page.sections + .filterIsInstance() + .firstOrNull() + ?.verses + ?.firstOrNull() if (firstVerse != null) { lastKnownVerse = ChapterVersePair(firstVerse.chapterNo, firstVerse.verseNo) @@ -449,7 +452,7 @@ class ReaderViewModel(application: Application) : ReaderProviderViewModel(applic viewModelScope.launch(Dispatchers.IO) { val ayahId = repository.getFirstAyahIdOnPage(pageNo) ?: return@launch - + lastKnownVerse = QuranUtils.getVerseNoFromAyahId(ayahId).let { ChapterVersePair(it.first, it.second) } diff --git a/app/src/main/java/com/quranapp/android/viewModels/WbwSettingsViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/WbwSettingsViewModel.kt index 83d5fe363..3b6c3d2f4 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/WbwSettingsViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/WbwSettingsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.quranapp.android.api.models.wbw.WbwLanguageInfo import com.quranapp.android.compose.utils.DataLoadError -import com.quranapp.android.compose.utils.appFallbackLanguageCodes import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.DatabaseProvider import com.quranapp.android.utils.managers.ResourceDownloadStatus @@ -147,38 +146,24 @@ class WbwSettingsViewModel( } private fun resolveSelectedId(rows: List): String? { - val availableIds = rows.map { it.info.id }.toSet() val downloadedIds = rows.filter { it.isDownloaded }.map { it.info.id }.toSet() val preferred = ReaderPreferences.getWbwId() - val preferredByLocaleDownloaded = rows.findLocalePreferredId() - return if (downloadedIds.isNotEmpty()) { - ReaderPreferences.validateWbwId(preferred, downloadedIds, preferredByLocaleDownloaded) - } else { - ReaderPreferences.validateWbwId(preferred, availableIds, null) - } + return validateWbwId(preferred, downloadedIds) } - private fun List.findLocalePreferredId(): String? { - if (isEmpty()) return null - - val candidates = appFallbackLanguageCodes().toList() - for (candidate in candidates) { - val normalized = candidate.lowercase() - val match = firstOrNull { row -> - (row.isDownloaded) && - row.info.langCode.lowercase() == normalized - } ?: firstOrNull { row -> - (row.isDownloaded) && - normalized.startsWith("${row.info.langCode.lowercase()}-") - } + private fun validateWbwId( + id: String?, + availableIds: Set, + fallback: String? = null, + ): String? { + val normalizedId = id?.takeIf { it.isNotBlank() } - if (match != null) { - return match.info.id - } + if (normalizedId != null && availableIds.contains(normalizedId)) { + return normalizedId } - return firstOrNull { it.isDownloaded }?.info?.id + return null } fun selectLanguage(id: String) { diff --git a/app/src/main/res/drawable-night/quran_page_bg.webp b/app/src/main/res/drawable-night/quran_page_bg.webp new file mode 100644 index 0000000000000000000000000000000000000000..9261ca7bc78f0d119ce91576c014b6d7e0553288 GIT binary patch literal 694 zcmV;n0!jT+Nk&Gl0ssJ4MM6+kP&iDX0ssInBESa_$DkOJBxd|+j_4%rmji{Owv8n7 zM>B|q#Qd&ZMQs~N=8t9&4T|n;CadDza#!7{yXJrM zzxf~N_kjCf5P$?pa^r?oHF5$FBtZ~7q$HyMLD@?*I-HGqD6OhSs_iiD6gwl8OygV# z6W+I#5kUlY7G$>Q)gmCWO1erq+|ZIqoeS1dsd1#leQJ!tiAHv&GnG4QODpt5|E+=} zH{rdI2aP~R6n0`U;krH}-ECB1WG``rT*5v2?38k+(mS4zy|s~WgEl7Yp}OM7I!aun z>`6VLN_Vhi(P8G{togbZ2_XpZbecP|=KwSA&<#xI z8=C>GW4X-QLGM-B1IESTz?cL2?h=O*@u%J8#Y0<${#u84WgKN}%AJMf#ZE6|r(_6W cQf@8si%&}4UPegIklzfRu|@BFY^L)902PW^XaE2J literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/quran_page_bg.webp b/app/src/main/res/drawable/quran_page_bg.webp new file mode 100644 index 0000000000000000000000000000000000000000..afa59042463882cd0812d2cf5f58c28e928479bb GIT binary patch literal 696 zcmV;p0!RH)Nk&Gn0ssJ4MM6+kP&iDa0ssInBESa_$DpW<6lnZALjpedT??pC)V7gi z{>%o#^4Q-MMQs~N=Fe;(ERX$NLDRO8Zp(o@_D|xy5lqrl+c7Yzpwng!f`wEM02T1B zqN4i!o_KoBHBAfo@lZ9PL5)JB*F`$VXOH+9NLuTugM zrxs98-gn=riYi_^h-_WQRYB#XSfm&fjVr<#9eR&ETJvgoj?PQfjjT8#&?anylE|C4 zuE&>hRk%f9n$wgRJ*=C;=zdFsW}qdIa+&k+3R%?W0O=7T&k=FnP0X=>!}xvj2dVI^ zN`4hY2<<;=^OSL~G}0z1_$M$GYV(kCW9V~=u&t9S$?I>d*~8U9b)Wt(&d?ibH?>5R z#vE7^vKD99%Bm~cfL);q6wN$ivqn#9TZlz!2XE}rjoP8@)OV*gFeB$U(HczSiiR}> zwa(Rn%Y-#zi^4Q_?J(xwP}yR{yJ0J*eaD9cqMW8bL`YdPZrg=x>^$HNvZKP$y-LS7A7ET?*=*6qyK%hC8OY?6G{32QT9^UMm8bZx}hUte;K2z)9aD)IherI3uA(GLSBv;DKpnsv05D$D3Pil5|0THBl!eoXRecs z9dv8?tAeXW3^nS1XgipP)R#LubwHVcO`@sat`B|X9p-5DJi3Fex%*7}Dyvp!cW4&0 z2Bne>9}0Wn(%p9@N{MU}X2G?>91~Vssg$=F| eiYDrL%enc}FRzq!r{)sP-%y^}GD>-Ti17lMJyMqd literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4a2045eba..d18fc19e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,9 +99,9 @@ Any With sajda Without sajda - Short (≤100 verses) - Medium (101–200 verses) - Long (201+ verses) + Short (<50 verses) + Medium (50–150 verses) + Long (150+ verses) Clear filters Chapter filters Menu From 2cbce62dbfcbede3a9838bc731feeb1846c56d77 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Tue, 21 Apr 2026 02:33:41 +0530 Subject: [PATCH 15/16] recommendation notification --- .../components/settings/DailyReminderSheet.kt | 4 + .../utils/RecommendedReminderScheduler.kt | 33 ++++ .../utils/preferences/VersePreferences.kt | 18 +++ .../quranapp/android/utils/app/AppActions.kt | 2 + .../android/utils/app/NotificationUtils.kt | 32 ++++ .../workers/RecommendedReminderWorker.kt | 143 ++++++++++++++++++ .../utils/workers/VerseOfTheDayWorker.kt | 5 + 7 files changed, 237 insertions(+) create mode 100644 app/src/main/java/com/quranapp/android/compose/utils/RecommendedReminderScheduler.kt create mode 100644 app/src/main/java/com/quranapp/android/utils/workers/RecommendedReminderWorker.kt diff --git a/app/src/main/java/com/quranapp/android/compose/components/settings/DailyReminderSheet.kt b/app/src/main/java/com/quranapp/android/compose/components/settings/DailyReminderSheet.kt index a799bff21..f1b9f6bb6 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/settings/DailyReminderSheet.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/settings/DailyReminderSheet.kt @@ -27,6 +27,7 @@ import com.quranapp.android.compose.components.dialogs.AlertDialogAction import com.quranapp.android.compose.components.dialogs.AlertDialogActionStyle import com.quranapp.android.compose.components.dialogs.BottomSheet import com.quranapp.android.compose.utils.NavigationHelper +import com.quranapp.android.compose.utils.RecommendedReminderScheduler import com.quranapp.android.compose.utils.VerseOfTheDayScheduler import com.quranapp.android.compose.utils.preferences.VersePreferences import kotlinx.coroutines.launch @@ -62,6 +63,7 @@ fun DailyReminderSheet( if (permissionState != null && !permissionState.status.isGranted) { VersePreferences.setVOTDReminderEnabled(false) VerseOfTheDayScheduler.cancelDailyNotification(context) + RecommendedReminderScheduler.cancel(context) } } @@ -102,8 +104,10 @@ fun DailyReminderSheet( if (key == true) { VerseOfTheDayScheduler.scheduleDailyNotification(context) + RecommendedReminderScheduler.schedule(context) } else { VerseOfTheDayScheduler.cancelDailyNotification(context) + RecommendedReminderScheduler.cancel(context) } } } diff --git a/app/src/main/java/com/quranapp/android/compose/utils/RecommendedReminderScheduler.kt b/app/src/main/java/com/quranapp/android/compose/utils/RecommendedReminderScheduler.kt new file mode 100644 index 000000000..8c7a3e9ea --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/utils/RecommendedReminderScheduler.kt @@ -0,0 +1,33 @@ +package com.quranapp.android.compose.utils + +import android.content.Context +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import com.quranapp.android.utils.workers.RecommendedReminderWorker +import java.util.concurrent.TimeUnit + +object RecommendedReminderScheduler { + private const val ID = "recommended_reminder" + + fun schedule(context: Context) { + val workRequest = PeriodicWorkRequestBuilder( + repeatInterval = 1, + repeatIntervalTimeUnit = TimeUnit.HOURS, + ).build() + + WorkManager + .getInstance(context) + .enqueueUniquePeriodicWork( + ID, + ExistingPeriodicWorkPolicy.KEEP, + workRequest, + ) + } + + fun cancel(context: Context) { + WorkManager + .getInstance(context) + .cancelUniqueWork(ID) + } +} diff --git a/app/src/main/java/com/quranapp/android/compose/utils/preferences/VersePreferences.kt b/app/src/main/java/com/quranapp/android/compose/utils/preferences/VersePreferences.kt index 31b09ede5..0081be5d3 100644 --- a/app/src/main/java/com/quranapp/android/compose/utils/preferences/VersePreferences.kt +++ b/app/src/main/java/com/quranapp/android/compose/utils/preferences/VersePreferences.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey import com.alfaazplus.sunnah.ui.utils.shared_preference.DataStoreManager import com.alfaazplus.sunnah.ui.utils.shared_preference.PrefKey import com.quranapp.android.components.reader.ChapterVersePair @@ -17,6 +18,10 @@ object VersePreferences { val KEY_VOTD_VERSE_NO = PrefKey(intPreferencesKey("votd_verse_no"), -1) val KEY_VOTD_REMINDER_ENABLED = PrefKey(booleanPreferencesKey("votd_reminder_enabled"), false) + private val KEY_RECOMMENDED_NOTIF_EPOCH_DAY = + PrefKey(longPreferencesKey("recommended_notif_epoch_day"), -1L) + private val KEY_RECOMMENDED_NOTIF_SIGNATURE = + PrefKey(stringPreferencesKey("recommended_notif_signature"), "") fun getVotd(): ChapterVersePair? { val chapterNo = DataStoreManager.read(KEY_VOTD_CHAPTER_NO) @@ -59,4 +64,17 @@ object VersePreferences { fun observeVOTDReminderEnabled(): Boolean { return DataStoreManager.observe(KEY_VOTD_REMINDER_ENABLED) } + + fun getRecommendedNotifDedupeEpochDay(): Long { + return DataStoreManager.read(KEY_RECOMMENDED_NOTIF_EPOCH_DAY) + } + + fun getRecommendedNotifDedupeSignature(): String { + return DataStoreManager.read(KEY_RECOMMENDED_NOTIF_SIGNATURE) + } + + suspend fun setRecommendedNotifDedupeState(epochDay: Long, signature: String) { + DataStoreManager.write(KEY_RECOMMENDED_NOTIF_EPOCH_DAY, epochDay) + DataStoreManager.write(KEY_RECOMMENDED_NOTIF_SIGNATURE, signature) + } } diff --git a/app/src/main/java/com/quranapp/android/utils/app/AppActions.kt b/app/src/main/java/com/quranapp/android/utils/app/AppActions.kt index 3038161cc..d521f0efb 100644 --- a/app/src/main/java/com/quranapp/android/utils/app/AppActions.kt +++ b/app/src/main/java/com/quranapp/android/utils/app/AppActions.kt @@ -10,6 +10,7 @@ import com.peacedesign.android.utils.AppBridge import com.peacedesign.android.widget.dialog.base.PeaceDialog import com.quranapp.android.R import com.quranapp.android.api.ApiConfig +import com.quranapp.android.compose.utils.RecommendedReminderScheduler import com.quranapp.android.compose.utils.VerseOfTheDayScheduler import com.quranapp.android.compose.utils.preferences.VersePreferences import com.quranapp.android.utils.Log @@ -42,6 +43,7 @@ object AppActions { fun scheduleActions(ctx: Context) { if (VersePreferences.getVOTDReminderEnabled()) { VerseOfTheDayScheduler.scheduleDailyNotification(ctx) + RecommendedReminderScheduler.schedule(ctx) } } diff --git a/app/src/main/java/com/quranapp/android/utils/app/NotificationUtils.kt b/app/src/main/java/com/quranapp/android/utils/app/NotificationUtils.kt index 8e1b56a0a..4de27c7ef 100644 --- a/app/src/main/java/com/quranapp/android/utils/app/NotificationUtils.kt +++ b/app/src/main/java/com/quranapp/android/utils/app/NotificationUtils.kt @@ -28,6 +28,11 @@ object NotificationUtils { private const val CHANNEL_NAME_VOTD = "Verse of The Day" private const val CHANNEL_DESC_VOTD = "Daily verse reminder notifications" + const val CHANNEL_ID_RECOMMENDED_REMINDER = "recommended_reminder" + private const val CHANNEL_NAME_RECOMMENDED_REMINDER = "Recommended reading" + private const val CHANNEL_DESC_RECOMMENDED_REMINDER = + "Curated reading suggestions based on time and day" + const val CHANNEL_ID_RECITATION_PLAYER = "recitation_player" private const val CHANNEL_NAME_RECITATION_PLAYER = "Recitation Player" private const val CHANNEL_DESC_RECITATION_PLAYER = "Recitation Player notifications" @@ -42,6 +47,7 @@ object NotificationUtils { ctx.getSystemService(NotificationManager::class.java).apply { createNotificationChannel(createDefaultChannel()) createNotificationChannel(createVOTDChannel()) + createNotificationChannel(createRecommendedReminderChannel()) createNotificationChannel(createDownloadsChannel()) createNotificationChannel(createRecitationChannel()) } @@ -100,6 +106,32 @@ object NotificationUtils { } } + @RequiresApi(api = Build.VERSION_CODES.O) + private fun createRecommendedReminderChannel(): NotificationChannel { + return NotificationChannel( + CHANNEL_ID_RECOMMENDED_REMINDER, + CHANNEL_NAME_RECOMMENDED_REMINDER, + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = CHANNEL_DESC_RECOMMENDED_REMINDER + lightColor = Color.GREEN + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + vibrationPattern = longArrayOf(500, 500) + + enableLights(true) + setShowBadge(true) + enableVibration(true) + + setSound( + Settings.System.DEFAULT_NOTIFICATION_URI, + AudioAttributes.Builder().apply { + setUsage(AudioAttributes.USAGE_NOTIFICATION) + setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + }.build() + ) + } + } + @RequiresApi(api = Build.VERSION_CODES.O) private fun createDownloadsChannel(): NotificationChannel { return createChannel( diff --git a/app/src/main/java/com/quranapp/android/utils/workers/RecommendedReminderWorker.kt b/app/src/main/java/com/quranapp/android/utils/workers/RecommendedReminderWorker.kt new file mode 100644 index 000000000..4cccba8c4 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/workers/RecommendedReminderWorker.kt @@ -0,0 +1,143 @@ +package com.quranapp.android.utils.workers + +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.quranapp.android.R +import com.quranapp.android.activities.ActivityReader +import com.quranapp.android.activities.reference.ActivityReference +import com.quranapp.android.compose.utils.preferences.VersePreferences +import com.quranapp.android.utils.app.NotificationUtils +import com.quranapp.android.utils.reader.ReaderIntentData +import com.quranapp.android.utils.reader.ReaderLaunchParams +import com.quranapp.android.utils.reader.factory.ReaderFactory +import com.quranapp.android.utils.recommended.Recommendation +import com.quranapp.android.utils.recommended.RecommendationRef +import com.quranapp.android.utils.recommended.Recommended +import com.quranapp.android.utils.univ.Codes +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.time.LocalDate +import java.time.ZoneId + +class RecommendedReminderWorker( + context: Context, + params: WorkerParameters, +) : CoroutineWorker(context, params) { + override suspend fun doWork(): Result = withContext(Dispatchers.IO) { + if (!VersePreferences.getVOTDReminderEnabled()) { + return@withContext Result.success() + } + + val recs = Recommended.getRecommendations(applicationContext) + if (recs.isEmpty()) { + return@withContext Result.success() + } + + val first = recs.first() + + val zone = ZoneId.systemDefault() + + val epochDay = LocalDate.now(zone).toEpochDay() + val sig = dedupeSignature(first) + + if (epochDay == VersePreferences.getRecommendedNotifDedupeEpochDay() && + sig == VersePreferences.getRecommendedNotifDedupeSignature() + ) { + return@withContext Result.success() + } + + val pendingIntent = pendingIntentForRecommendation( + applicationContext, + first, + Codes.NOTIF_ID_VERSE_REMINDER, + ) ?: return@withContext Result.success() + + val manager = ContextCompat.getSystemService( + applicationContext, + NotificationManager::class.java, + ) ?: return@withContext Result.success() + + val body = first.description.trim().ifBlank { + applicationContext.getString(R.string.labelRecommended) + } + + val notification = NotificationCompat + .Builder(applicationContext, NotificationUtils.CHANNEL_ID_RECOMMENDED_REMINDER) + .setContentTitle(first.title) + .setContentText(body) + .setStyle(NotificationCompat.BigTextStyle().bigText(body)) + .setSmallIcon(R.drawable.dr_logo) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .build() + + manager.notify(Codes.NOTIF_ID_VERSE_REMINDER, notification) + + VersePreferences.setRecommendedNotifDedupeState(epochDay, sig) + + Result.success() + } + + private fun dedupeSignature(recommendation: Recommendation): String { + val ref = when (val r = recommendation.reference) { + is RecommendationRef.Chapter -> "c:${r.number}" + is RecommendationRef.Verses -> "v:${r.spec}" + } + + return "${recommendation.title}\u0000$ref" + } + + private fun pendingIntentForRecommendation( + context: Context, + recommendation: Recommendation, + requestCode: Int, + ): PendingIntent? { + val intent = when (val ref = recommendation.reference) { + is RecommendationRef.Chapter -> { + ReaderLaunchParams( + data = ReaderIntentData.FullChapter(ref.number), + ).toIntent().apply { + setClass(context, ActivityReader::class.java) + } + } + + is RecommendationRef.Verses -> { + val ranges = ref.spec.split(',') + val chapters = mutableListOf() + val verseSpecs = mutableListOf() + + for (rangeSpec in ranges) { + val trimmed = rangeSpec.trim() + val chapterNo = + trimmed.split(':').firstOrNull()?.toIntOrNull() ?: return null + chapters.add(chapterNo) + verseSpecs.add(trimmed) + } + + val desc = recommendation.description.takeIf { it.isNotBlank() } + + ReaderFactory.prepareReferenceVerseIntent( + recommendation.title, + desc, + emptyArray(), + chapters, + verseSpecs, + ).apply { + setClass(context, ActivityReference::class.java) + } + } + } + + return PendingIntent.getActivity( + context, + requestCode, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + } +} diff --git a/app/src/main/java/com/quranapp/android/utils/workers/VerseOfTheDayWorker.kt b/app/src/main/java/com/quranapp/android/utils/workers/VerseOfTheDayWorker.kt index 7d5bd3515..1e8356e8b 100644 --- a/app/src/main/java/com/quranapp/android/utils/workers/VerseOfTheDayWorker.kt +++ b/app/src/main/java/com/quranapp/android/utils/workers/VerseOfTheDayWorker.kt @@ -11,6 +11,7 @@ import com.quranapp.android.R import com.quranapp.android.activities.ActivityReader import com.quranapp.android.components.reader.ChapterVersePair import com.quranapp.android.compose.utils.preferences.ReaderPreferences +import com.quranapp.android.compose.utils.preferences.VersePreferences import com.quranapp.android.db.DatabaseProvider import com.quranapp.android.db.relations.VerseWithDetails import com.quranapp.android.utils.app.NotificationUtils @@ -29,6 +30,10 @@ class VerseOfTheDayWorker constructor( params: WorkerParameters, ) : CoroutineWorker(context, params) { override suspend fun doWork(): Result = withContext(Dispatchers.IO) { + if (!VersePreferences.getVOTDReminderEnabled()) { + return@withContext Result.success() + } + val repository = DatabaseProvider.getQuranRepository(applicationContext) val votd = VerseUtils.getVOTD(applicationContext, repository) ?: return@withContext Result.failure() From b2910cbfc3bc9ec0a13884c45d173e058c73a603 Mon Sep 17 00:00:00 2001 From: Faisal Khan Date: Wed, 22 Apr 2026 00:43:41 +0530 Subject: [PATCH 16/16] search functionality --- app/src/main/AndroidManifest.xml | 3 +- app/src/main/assets/db/quranapp.db | Bin 22482944 -> 24113152 bytes .../java/com/quranapp/android/QuranApp.kt | 3 + .../android/activities/ActivitySearch.java | 610 ------------------ .../android/activities/ActivitySearch.kt | 83 +++ .../adapters/search/ADPSearchSugg.java | 156 ----- .../adapters/search/ADPVerseResults.java | 448 ------------- .../compose/components/common/AppBar.kt | 2 +- .../compose/components/common/MessageCard.kt | 98 +-- .../components/reader/TranslationReader.kt | 3 +- .../components/reader/TranslationText.kt | 3 +- .../compose/components/reader/VerseView.kt | 4 +- .../reader/dialogs/QuickReference.kt | 47 +- .../compose/components/search/QuickLinks.kt | 157 +++++ .../components/search/SearchHistorySection.kt | 332 ++++++++++ .../components/search/SurahSearchResults.kt | 65 ++ .../components/search/TextSearchResults.kt | 205 ++++++ .../compose/screens/BookmarksScreen.kt | 2 +- .../compose/screens/ReadHistoryScreen.kt | 2 +- .../compose/screens/search/SearchScreen.kt | 313 +++++++++ .../quranapp/android/db/DatabaseProvider.kt | 17 + .../com/quranapp/android/db/QuranDatabase.kt | 8 +- .../android/db/dao/ArabicSearchDao.kt | 22 + .../entities/quran/ArabicSearchFtsEntity.kt | 17 + .../android/db/relations/SearchResultRow.kt | 13 + .../db/search/SearchHistoryDBHelper.java | 26 +- .../android/db/search/SearchHistoryStore.kt | 39 ++ .../android/db/searchindex/SearchIndexDao.kt | 77 +++ .../db/searchindex/SearchIndexDatabase.kt | 19 + .../searchindex/TranslationIndexMetaEntity.kt | 15 + .../TranslationSearchContentEntity.kt | 30 + .../searchindex/TranslationSearchFtsEntity.kt | 21 + .../quranapp/android/frags/BaseFragment.kt | 102 --- .../android/frags/search/FragSearchResult.kt | 290 --------- .../frags/search/FragSearchSuggestions.java | 138 ---- .../android/repository/QuranRepository.kt | 7 + .../android/search/FtsQueryBuilder.kt | 31 + .../android/search/SearchIndexScheduler.kt | 99 +++ .../android/search/SearchNormalizer.kt | 53 ++ .../android/search/SearchPagingSource.kt | 317 +++++++++ .../android/search/SearchQuickLinksParser.kt | 124 ++++ .../managers/TranslationDownloadManager.kt | 2 +- .../android/utils/reader/TextDecorator.kt | 4 +- .../android/utils/reader/TranslUtils.java | 4 - .../reader/factory/QuranTranslationFactory.kt | 114 ++-- .../utils/reader/factory/ReaderFactory.kt | 17 + .../android/utils/search/SearchFilters.java | 83 --- .../search/SearchLocalHistoryManager.java | 43 -- .../android/utils/univ/StringUtils.java | 2 + .../workers/TranslationDownloadWorker.kt | 5 + .../workers/TranslationSearchIndexWorker.kt | 137 ++++ .../android/vh/search/VHChapterJump.kt | 23 - .../quranapp/android/vh/search/VHJuzJump.kt | 21 - .../android/vh/search/VHSearchResultBase.kt | 27 - .../android/vh/search/VHTafsirJump.kt | 62 -- .../quranapp/android/vh/search/VHVerseJump.kt | 61 -- .../viewModels/QuranSearchViewModel.kt | 149 +++++ .../viewModels/TranslationViewModel.kt | 2 + app/src/main/res/layout/activity_search.xml | 123 ---- .../main/res/layout/frag_search_results.xml | 38 -- .../res/layout/frag_search_suggestions.xml | 29 - .../layout/lyt_reader_juz_spinner_item.xml | 13 - .../main/res/layout/lyt_search_filters.xml | 22 - .../res/layout/lyt_search_result_item.xml | 50 -- app/src/main/res/values/strings.xml | 14 + .../android/search/FtsQueryBuilderTest.kt | 23 + .../android/search/SearchNormalizerTest.kt | 32 + 67 files changed, 2564 insertions(+), 2537 deletions(-) delete mode 100644 app/src/main/java/com/quranapp/android/activities/ActivitySearch.java create mode 100644 app/src/main/java/com/quranapp/android/activities/ActivitySearch.kt delete mode 100644 app/src/main/java/com/quranapp/android/adapters/search/ADPSearchSugg.java delete mode 100644 app/src/main/java/com/quranapp/android/adapters/search/ADPVerseResults.java create mode 100644 app/src/main/java/com/quranapp/android/compose/components/search/QuickLinks.kt create mode 100644 app/src/main/java/com/quranapp/android/compose/components/search/SearchHistorySection.kt create mode 100644 app/src/main/java/com/quranapp/android/compose/components/search/SurahSearchResults.kt create mode 100644 app/src/main/java/com/quranapp/android/compose/components/search/TextSearchResults.kt create mode 100644 app/src/main/java/com/quranapp/android/compose/screens/search/SearchScreen.kt create mode 100644 app/src/main/java/com/quranapp/android/db/dao/ArabicSearchDao.kt create mode 100644 app/src/main/java/com/quranapp/android/db/entities/quran/ArabicSearchFtsEntity.kt create mode 100644 app/src/main/java/com/quranapp/android/db/relations/SearchResultRow.kt create mode 100644 app/src/main/java/com/quranapp/android/db/search/SearchHistoryStore.kt create mode 100644 app/src/main/java/com/quranapp/android/db/searchindex/SearchIndexDao.kt create mode 100644 app/src/main/java/com/quranapp/android/db/searchindex/SearchIndexDatabase.kt create mode 100644 app/src/main/java/com/quranapp/android/db/searchindex/TranslationIndexMetaEntity.kt create mode 100644 app/src/main/java/com/quranapp/android/db/searchindex/TranslationSearchContentEntity.kt create mode 100644 app/src/main/java/com/quranapp/android/db/searchindex/TranslationSearchFtsEntity.kt delete mode 100644 app/src/main/java/com/quranapp/android/frags/BaseFragment.kt delete mode 100644 app/src/main/java/com/quranapp/android/frags/search/FragSearchResult.kt delete mode 100644 app/src/main/java/com/quranapp/android/frags/search/FragSearchSuggestions.java create mode 100644 app/src/main/java/com/quranapp/android/search/FtsQueryBuilder.kt create mode 100644 app/src/main/java/com/quranapp/android/search/SearchIndexScheduler.kt create mode 100644 app/src/main/java/com/quranapp/android/search/SearchNormalizer.kt create mode 100644 app/src/main/java/com/quranapp/android/search/SearchPagingSource.kt create mode 100644 app/src/main/java/com/quranapp/android/search/SearchQuickLinksParser.kt delete mode 100644 app/src/main/java/com/quranapp/android/utils/search/SearchFilters.java delete mode 100644 app/src/main/java/com/quranapp/android/utils/search/SearchLocalHistoryManager.java create mode 100644 app/src/main/java/com/quranapp/android/utils/workers/TranslationSearchIndexWorker.kt delete mode 100644 app/src/main/java/com/quranapp/android/vh/search/VHChapterJump.kt delete mode 100644 app/src/main/java/com/quranapp/android/vh/search/VHJuzJump.kt delete mode 100644 app/src/main/java/com/quranapp/android/vh/search/VHSearchResultBase.kt delete mode 100644 app/src/main/java/com/quranapp/android/vh/search/VHTafsirJump.kt delete mode 100644 app/src/main/java/com/quranapp/android/vh/search/VHVerseJump.kt create mode 100644 app/src/main/java/com/quranapp/android/viewModels/QuranSearchViewModel.kt delete mode 100644 app/src/main/res/layout/activity_search.xml delete mode 100644 app/src/main/res/layout/frag_search_results.xml delete mode 100644 app/src/main/res/layout/frag_search_suggestions.xml delete mode 100644 app/src/main/res/layout/lyt_reader_juz_spinner_item.xml delete mode 100644 app/src/main/res/layout/lyt_search_filters.xml delete mode 100644 app/src/main/res/layout/lyt_search_result_item.xml create mode 100644 app/src/test/java/com/quranapp/android/search/FtsQueryBuilderTest.kt create mode 100644 app/src/test/java/com/quranapp/android/search/SearchNormalizerTest.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 20716836d..ab4dbc6ea 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,10 +27,10 @@ android:fullBackupContent="true" android:icon="@mipmap/icon_launcher" android:label="@string/app_name" + android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/icon_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.QuranApp.Splash" - android:networkSecurityConfig="@xml/network_security_config" android:usesCleartextTraffic="@string/cleartextTrafficPermitted" tools:targetApi="s"> + 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

>{Gbs_sc4W-RNm!RjMb2$gBA4XP#Ab;yB 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%0Ds~#lk4u#e;Y94vJJ9J_ga$ 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 w{;doHs6#UY%PjTn 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 z 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 z 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%0D79rm})u4{zsp z-cRKxv)%tUltQEWGW$Il9M8zpse9R}k}q7V!&iEJPv>?jzL@R)i>OEu+mi+i!96dspSq@!?VD~c15h$h|p zvaPVU-q!Ix%M-HQ|1mn|H+Sj9@)IwmY{Q?Hw@p`Ww~XqVv|z`*tK%(~XJotoyxeXl z6hJPEG7SwEz=m}k)?0UNdap&YW!t6kZRvPN z*uf5(Moh?vsQ?~n$VXDlDI3o1vY&kh@FTp+B(Pb%woz30f<7qP8IU#_)4IHHq z96^B`jSq~p@Z|uN)1fF&NjgyC3A|J&3S*cB9Y55$R=u2`?fz~Qt>~AMX-;_p*Wfkr zQh+-YQWp{g(8((SQk$WaPchOTDsyc|*MmrIe?$W;GZoBDu-7ucDGhHf7;;yL4^C3t zM4nJay6v*g)d=pTZ1*2Ryw011-G}Chr$k7kO7upgr~u_Ofr5}TI#*Hu?ritJ!x0$6 z^qP%K_z?#*9lW{OYB}zMohwE7Y1!`P9~9w7bnK;PwLO@_xh44UATebbYwg+_ic_76 z5-M@hRFs_5xdKYA&UXL4L`(w=t%y2ij%r4QCOVrbv@zTLdt6*J*mh0la#6z+?lzmB z*C&$;%H1Sy;!gpg#xx{Bv6#V0lyoAUppDBy8~s~d1#=T4I}iqvf&v&t5yi6Mg5CUK zaXWa9>eSGOI`2_K=V$#}_^N3!8vPM#)dLNBbW{drDTq9t&x`GP_1{Pn0l>9veQeWb{e<7EFdcCK0`Uj`Jy!t(-o@pv_@h{ zjMs~`-Gondba=dVXV&K~G^kF1?y0dsL_AgKqbAU9?dPF_Ah(V1HTM`cl9r)Eb`j`p zd!)muaCUQEo%L@-nlfN3Q)z15{tCBhqe3mdYG!F|LW#CYx`?8sZLZ3**oQ(TWDujw zogE$KRu=sRP4#GEm+Z+Fsdb8ANloo9+p>&MliypHFdfA+uhe+Q&m?v(P}4_d{p-U; zY~g7pPE2{^`l9o?mI*3&k;jRBz#L6s|D&yqcBs4Uegaj=c}SAw#uD3WkY%Z{tg zxe^3BuC5h%yod-3fxtXkpccs$`@Md{B1}rKET!>E&R@%;i4<2$xzH`0bJYGs*6d$H z2U2TVb2I#^G%7Q-(2BuW5|*3!DQ^`(XBTzOMszHZT^*Vc?h}bF!%xfGBZH_wFDL}I zuIQZAq%Ikxt8kkHU1ju4(=iHQAZZ@82Ld>ts09uQ)qtdF`#ZDGXX`(^{^WJPyzcL; zyKwS*lRrPXF!}uC9g|0{{WojBxb`D!FIao{#J`-FpLlruPsbO=KQw;k_|({MjBOt~ zXU%aBuKv#IeXCDiwXgBFjh|Zi!|4Wdf5fJ8BGN2~+;vt6a4W**3v1{7bO5a7mG;MDA;;OS2?ojo3f?C>M;mZ+>4rnqzSCiGB^>pu;HcQQrolC+b(4> zM8;1;R<2peZ}{_&bQ~vi%0i{qEgpB~Y1YTPO18D`%a;CasKHx8oKq432py&{ZCcwm zh!}#qz1*R0L3A1@&ewpVlvaG|xr@d_jLhgGX?ijW5x9A4eOKpYxGQGqH#|;Iv^1ik zqsFamMDK&k@nu+q6phH*LEGTFOs5-K3YBezOd8ZVNX{(PQXBnHUrl>j24e^`O^0=T z?&id7>EF;{wQsWo$^<=SKsRc;gmno_hJ*g3T2#q?xwE?*bs3$;PXCpbvXP^_K+`PO zEk^=(UD}7BSlI{3Uy`-=4o|RFU~CD9ZxJZqJID<|A$m)`Sx4p6;c^aO#piRL>G}-K zz1h;QLvG6c2Q~$hNfyGv;n+}-VC-c>rX{BU#vSWN0zB03FSI!XHQO5Rj!004W=p>| z(1sKTMU~94UuzX6g;zMJ$gxHEJhhexw91H8EnV_}m@WO1<{GDWUgFyw*`By@txP0|U)aB-;VAg)No6rgJSthEnVI5*^$v~&!#|9 zv|K9R;W*x$#(;8SiE>Dphi(&oJm2w2kw>#VM&LV?jMs zojtqrJXIga_8b9fBo6gEXF8)|2@2^Mpm^XZ0adQ*JSVA-WP3JB?Y#A5AjwO&a4*Gv zQLU>mP%%YU8p+YFj1`lhl@mIjpySK3J%@*tAGm;UPUl%lJe2L(Ak`)r6)9x)z^E=` zodS6|JUX}Yaq2vh?K#YK5H|VFR1gVeO~7oYN5765dlX}Wl4fM zl`j?SD{F%Yji*R`bLVMly_)SgBqFFcX%{yVW#~&IqIMF^fj`K4I9ytBnpV@qrJbi} zg2lu2F@{lET4Fp}L`vI5W{S%YG0#){p45J9=VR2qFx#^Z-TCKkg4opfnH`-7xHoI= zndH4Zaob={cAz*79fSx?4heL6n?1Jkk!Wx&$#r5O$;!qUn15pDNnT%>?U{foIC_I2 zA8(8@x-SHHdSB-W8OO$K&$vjJIDFv&1tBZsCrsDyC^5ru=}`1I6tc@Bp*uQ{tG!dR zJ!1%r4j7TtsCdAD(JT+sz=(@g-^z&_Z%I*5A$%Tj|;PY2#~i!Uu>fikPe9+;xU zjDrk{%-+?}&i>XJ*`8G*NR0&kO_fNjE_FHR@oOpWtKa4*<_UB{wtohY7hpb*$eb}lGt;!l-Xnd}* zdF9_5{izjyx#Dvpe>Jj_YPJ8hF6e4g^z@V2J`bH}9{EVVvPZ2fl%afDB612zO0Y;- z@RARwTFQ8!0P5)xda~+hYdNqI1m1J(gqlU{e3lML8`cht)B09pwcbS*CO@Agi zQN8ly>UMisCwGf`lxj_MwcC4oL$*&hd!+Ds|G-vsmC>HX=>Ft}vOwAkFF0VPB6h>k z6sbUGm0}?OkQT1e8TzD$k94&;ymec)Pr2GePWoM9Nsrz&j@9O$Vg1+wi40z{t$%y% zb)hH_kLO)LhTpYx;Q|tCZR%<__w-rWzBj369WDO_x-@iSC(@sjV?#Cip%soEG_1z; zAdrc6WPVyBP`goOno_%v1L+6i54$?kD&5kJUlcpz$+sw?StfdKz1-FA@R{ebef<2L zge*+aJRS3rYqxE7PF7Cz!@A|Jp9jS(>GrCg*9>Pa@TXBqqC|-a5!( zx1t$JqVI{Q66%DF(YS-ToH)Y6@YmdL3ioKzb##Ff1k+qy^dJ^ z`{M8J355te=7!{slH9<-OeT8SHWg+$VK#VZPN|ZEMJ6Lkv6b1W9_T7?ja1ph3gD{ga1xTels0#z+3V~RU2mHwIr_)c*2qeq$!pc| zPKpA}HrI4K&0Kn&O4qog3m;AE5KDPJm-ru4XINVoceP`l^Eyl4qqBhIQVe$8?FcI_ z60o8I@Wb?!+;no>o~Ka~e9&mdR(41R)QSqrV6YYdNEi~Us|Q4|B};Ib+?c=3Qz`H- z>s_C3XpaukYJO|1>rMIVv!(AsJ@7^)+9v)y(81X&3kMjbY>U>)ArIHuZ$y;o$-3N` z9WnS|=z%P=Jmi`dU+cp!Nhm`SOW+PZmN*75; zeG{cHI?WqIwAklE9w7`Q1srBr$puM@E%FZ<;Z}CNk-w2G{X6(n9L12Ca$CMfKmmj9 z#EUD>>ThD?OL3lRQJvuj@I5}Wg~B#{zbtgh5I%erD+Ppq$R44FAWrUTHHJ zlOWSCJu1>t1$wLILah_JIvp^5X}0uxNaFuPz-Z8k^&h1dbwQ;~IQex~(SU;A6J zF3encdqc;G%?&l44KkxuUCY zx~`0`=tnbN@2twi0irqmSPaJnY2!;WAQ6M9Y0ZLv6LE^UYXgGd!O&VD;*{TW{uB4t z35sdl|F=aHL;^7t(UNFA=A?)oh2s?%0$<8mJnhQOg#&mGBGmC{7_qv%ToIZyMxrm~ zAGPatTi15iN!Y8irQd>71mhk2rX`INNE}N=3(C4BQyC7mR6-8ym9RwJiL*Op{o1Z=Pk`-S(`?hOF zzQdh;@xR8-Ge)yRMpiaaT@ z>$B0=`8Ivh-bv~EiRt}R`aD6OO$r{bKgJq6kJBgLk4*)R(I*WYot|&fC*_XP=jz7J zBh&LE()W$}gy6%|^9}l>{9)<)q57oWA?f*gKAY-bU3xL8&ozymYxT+ViS#+1o{#C1 z2G-~kLRagP4p!-Nys@*PPY774Ps)wzlXh0<6UmI|lk!=7UuceIO%+`DOcY#rHNAKx zeZH*EMq}aA`W$O4yp+CwDt*3~o_|uGYZ?nLq|fKm=X3g`{wMTFKhNrORb%1f>H9P6 zFcF0dPwT;GW8o=%u4pWLEPel|K6(EUeNyqs^!^Eb((vQy{bTy1{-f#rhxG};kEG`h z>yvgKN}ms=&j%2JU)-NQ@6#s@e<*#wH+|ldKJQMSccsre^$B4g)F<`s&?kg^K%a2@ z{`CCz^!&E;d8k9W%{Jy_oUBD^+|s;75(5D*j01c(4p6~3z;ojO z4H^e{Zyeygaexku1AI3QP~JGebK}5<#)5Hx_r?M08wV&S9B4{J0)mmXjRoTX1&jlH z8VC3^4p7cGfXIyld>RK3t#N=);{cz=0ldgKz^8G5PvZdfj01cJ2k`%qM$cZby3sQZ zj5T`30lpgt_--6n)#w=q8jW7Ufu3=I=f;7_M$b4f(dZ={=otrS$2hRE(K8NE-#DNW8(ntjRSlp9OxMbC}$ktxp9C`;{XI12k6i^K!?Two*M`F zZXBTggXWx$9uNV18V4v~9H4@6fak^mz8eSlZXBRo!hxP~fDRK5^b!vAj04m&4p7fH zKt1CC1UC%?JTMN>fN_BD#(@=$o^gQZ#sNN!1EY;z!hxP~fbYfu${PpxG!F1-9N^P9 zfQpO*5Mmry)oAt%1W>YB65!J~00G8 z0iGKN=+HQT$c+Ph8VAOkjh=yEywNibz@c$~28;uI8V4Z6IKX$~0H4MI8a58lpmBgt zP83$;cHz@d$vap1^C&p1HG#(@hOJ>vk9F%HlUIDr522S9Mh z$U7Qcd&7oC*Eq1Y(KQb6X&hMB=o$wOYjlkRs~cV80OgGX$27Xefnyt8cejjnNE zeWPm}pq_Ey_(nJ3K-V}h+2|SvV8l2;dE>xDqx+CKrh^9)0T1Ytg2n*~7zf51UE=^8 z8wY5>IB-a#YaHOYaez?XGjjnM3f{X)nY#cyh#sL~K4$!f20Ld5!MjKt@z$uNcaRAjB2lzA&tZ6j61_CHF z4)9{LBtVD80lpgtpx8Ko?u-LGHx3-y=o$y;*f=oN=o$wg+&Dlv;{Xg92at$y0F@gD zj%hZ!1_F%3I6#BO0gT8vKm*2sGa6mv0OgDWG-w<^62<{+!Z?5=jRSld2k6K+06_@{ zy2b(8H4Ze7Yjh0+aA+JrlqX06BaNco)jRUB}IDo{A18`^@fI#EG zgqB7R&+=^jvFje0T$((4?LVIw8Q;vs=0COO)K!0b*s7x&uQd)^dH3iqte9PK_{f7< zuC2`ELKPH79Jq*++eB-LBPVn)wwmi-o*%_(=L3AvJ1 zo(`v*^fcj43u*dQ*S(sn@H*LSf_WoLUZp(pfa~yzS)nhRr{}#iT*yEnEwtm=)wo(Kl(~_ zX^*4`hT~b&;sgrKlVVYVgN>X)B2!a}k?Er)*FcUfP0A&yXSzOV!q$p?zid9eW4KGr zGE?K)n!o!Av)oedNXLoC?ar?!HHpKN09F=EnG#RMcfgCL`E)sbX_wO`qgz>^`*zi( zwpNf$gGLuuvRhQc2f8g^DNzSsPMOp$Khg}`;&nnsyegR!6L?#SGT5zxqmC*2qK3J= z2XQ#NWAP#Xl+TIG0oe`}O05J{q0p+a#dvCYXp;t8T4c4$Bn{Kr3uv1p5l1N$Kg`xZ zsSd%Azv1+`Ip0rL1&>SP9xg5%($ztl>Ep6}zl0+7GiIbIoaRk*^dV9YWkHw?BGHyV zw)Lb$465N7N|6Md)6>>=rAKIHXin8WD&=Tc_)2~SJCUxXO0^@Rb$(YzaHb#6_Wkdb z2ho3*(sNiTjI-pYZey`w8p^adiwLw=vYDh=OFO1aOsYveYVyOS_6H?%R3D*jSBogi z%L??v$|SxFffAkEMGGkLlP?wpMbaUTE{21hac1HC2B%*+w6O1=N8WQvhL@U6X6(El zi!IBPhk@|=TL?{#Au5z-)A2l%Y}H{i7AdmRtF+){1W|FVe#_Tb+MaE8-;DrUFK7FH zv6g+M+8%5-a;<6@k;%9575lyxT(JMLfb^X5vi+b)t8#I0QcTHCSZk3q116p4S{ksl zEew08t20D1o3nkt;Iq$2HC?Qsn|{Sxu5&Sj1osmSJ!!6p&!#uhtaL z4JafjOS4Z^+`U&(WQX)0UL9m9r`b|_Xh@xk1)a;r)^dpTE^QgaYyYpV`G zP$yuDd?>6b{ir9FOqC{wlQdw7HyQwcuow8FLv@iT92#_}};{Q^sdmaKt7BWT?wl zKU{Ou_^E=T-%u^Gv`vR{rW@I8hkQPyS_BjqENW?)Rg$v>A(v+Rey#u*D6z9#OG?A0 zNKP6y1L%thLxSRZp zou6&D{6t`$S;v#ra!x>%M4qkaGE;OeqjTJ(mhfmH@NU|ky4;XsCAp=oO?CIB~B z7KF0W>TN#NYJn>!C4*TEo8@+i|Ch7(fU~Q*?)~@7=+0=GWWaO;q6pEE5FJDly%*C= zk?F=5Fvd2<4!)U@G$Y~k#LsrhKY_VTV`EtsMh3+piTtAXqQpv2BJPzO@|^OLm*mB9 zdUNvs{(ftpd+$iXCJ%j>bI&<@uf6(Sd+l@w;NKxD1wDJHitIVChW7AMKBbr#2dyHEE8MCCEE46WvZ89% z+ou3CPWieJYU`L_b)pw>+S*~EX0fUlKy6s~%~Q>j0-3=Emu6W<^|pI2J0KB30sq$S zhPn{>X^lbC0Xdi;LC96CEJP_k3Fkqs zyw2}EH~Z*ZosY1W%HTBo&aX+FO+&<>X_N~}yP;JQ|JbtcEfSpGqy zHAtk(MEzKd%F#&I*6GnR7`3x|KFz&s&W1Vrj4m4)AAW9l!O#PPFL0{ZUk|)Ga8>{J z`m3eCEPZO$c;EN>CVKul{c~z)=>FmIbyGLX_6|%N*9xhQ^h8~o=dvyQlw=`v(2>>6 z7kNz~A%s!5GB+$M)|o*pE8d9P6<4F0RlPmXhf&aoXCQQ%=@|2;9T0SvL_(rs3@BPZ z3mua?oc2?2DR%Hmv%);GuD%Esgl6``T@HpCUKK4zu(65P4K9oMh2+o~pd)#aE}_Y> zF%$+EYD6T*qZjmc#k;3;oYBOBw6RY6?T;-)2WRBuBB{{s#&OCD-O+t{&g{z!7oGtZ zk8ZAs8XQ{={&c`CT5FFzqu5H&AksXO{CFvWZsGaL|SSUrna}{ z8)`@e2O(VL8Le!>!hFg5Ow_Ut{e)hLM$1&QXg)IKGoc$+AjU5{K8Ur40wrNDV0bii3mN+;a)m2kh3GUpq@t0J8ruYdRE&efyUU5qno9sNcCCmi&_>__XqC!PVQXayhd{GUO?{B2cbSxG#V`>Z~tUv%DA<^X9ZEodxzl+W7xjC%e|4 zijwTy39}HY&=6?0k4{}qrCZa+U%+4F4qVTpnfS&xZHe*v4E>6pDIM^&3P17lurWpm zyd$gAbeOU`(+Zvy$FGA4dmB-I#s@QdQ*A)_5`+Q<{EfnxhaBR%5D@>R0vb!gEy~>Wg`TD`qG0Th;l2957-60 z@K-b(II8>#89p(Bb(|hS=)7WK@zXc?1Djpk83(g)S7BrdF58ROLrZxNT@3oTvIfY3muTHh+B6+Qz%@)lhVWk$6)xXojEbqnr$ zB_>6{?F_EY%Qk~Md=nV}(wAHzAR95!*9B9{G6_Em5*^lbwa@d8TaWg;{W`C^45$$* zk&V3_&c^Nts}!P9GkYQ7^N=8B$rXiQWe&}n7?9=Mn>;c+uow`I014s1vE&{sv z4u<}IfRr@y47-bYufAivXG+J#tth7r+d)fxB%fiX$H2@5O1DZ3LY1`DZEMR2W^2)i z%J%-fm8#*49ctz!Dnkf;XgC z5e)#4doQ(xS_+Upkdokcq;Pl{u=eDUD}yZlLye0Llbf@GV@szll8QW&HoT$KbsNS1 zY62Co#PjpPD>52t$ZDXD-hp?J1Gcyeerqf`0cAov0&{)ffvxGnR1^z9J5!o94>y3e zc#M%Pj{)h`Qx{5-`=kxOo8?t7Szrp&qut2>sw|QMog%B$Sz}vPSn7r~Fr+V*@C}R< z%=~5}YPBY1n+#N8I|NezGcMD5|s^7spHw;B;_o zbN$%iQ#v|u{G7DmcR)beW)=) zCuBm&pVzM37mRCXR1|Pm8u4VciO;>WU$-@5O2p&q&YIEzhvSc>4d3k2w>_|{29hyD zDE-5+PQ%d#Kk+}j?R=_G{fdY5k${1ECskAlwNM3fLlh{#AX3nOkIyxr%`v2X;cD5j zo@m;ORE1c%p{bEFxNDyMke|NA&(x@5pE0b@3@j{?n`jKJ`JF!HH>;qI6Jd z2lKR5ksi?&x?}|}9zi)l&VMy{-%Hw!t^?4t@co^u)P_Z>Fti>7qS z!Sb|eg_#&3YGiKRB4`n`22;Fm5vIIr>QQO@ytD~22OL+@mVLuJ-IUIH73uO4*Qg)pp+9?{Nt29aB^mzcyfH7N&=ng-5 z%hba#ZS3f@QQ2#6-X_Gmt528C&b8zZIef@r`u z;*fCbB1F2NZd8mHb^}v7i(-5@ZQKM|Dh(hy|P5TEkJ$N=nrP=soE0~pk@ za6CE_45u;F!&|^%;>N(Z@&~5$P2I19ug3e*##c4jkDSCe*(*OfNHx1+`QoE)YOezi zUG@xNPHL*EqFm%6jR|%)^R_)V0td|ycim%GPTdC{D@N1CuZ0S9+Ws#w1TH9}-W3V9 zv5+N$LG;W*0j-8o>A#r~#N@nmF}y>5GwAF(w#}oEy`?Iio_Zg&j4|5RIW!t!HTWxA zwBE+hnx|4BzKvoYY86c#t}7e>A#inJH4gK%4~bDlyaP}Gl>L2)=Nc(X!{<%u8yMqf zrHwlMf)+KE7ei%W(6OjK@K&^}BQgSlb$y#mhJa@4?4#(pbQ^sT9#f?-nTetro=Xur z+ypnV8X1T2>fYL=xiS$qQ}6Gex>t<3IBooD6XR5tVFRn4x)seX8JO}ljc8DL-&drS z7L=9Q+5L|4(MsWmK>Vqxd(_JEwDDz3%A=WvY6p$ogKc|l&in_9FLi$b7m7NGDzy$W za&nmX+oh-8H+8ofU79xPyK#|vYvxH>`R_K5#UjEigjS;w!i{nsmQx7YbW+u7FqHtf z(a|m;U>=MeI(3%>yd-V>%AS;U$F4}B?M;v!-W7e1K5DmoTXO@mUfFP^o#sDHwmJJ|5F5Y6EN^PtfJ80^?!tdm?asBQ_EG3nPH6~ULLRwRY{u@Qwej5w5+{F+h z5C*#I^Px2w0>$?r3TG3xOgmr>z6=@t5r6@0hwnFrjxHr7;7^bcQx-DydLB>QF0Gh>u8#V`3_)3*}MF zj;^JKk4@b!*tevOd@_)-ngz7SaUbM9LwZF(i1o_QgfeH}pv%GJkR+JG<`|Ol+VdJA z13lHkL$A7A^|7hj)EpV-m&8xnLNz_2mIK$|P%Ij=ofGlISUu7j;)a-|I8faUIS-F^ zq8YKuWMhbw1j+Wqt>rRa-hb*=nqEnwMymfj43eqkBq*x2wf1%FsKuz-UDmKnRfVsw9eWXvX&x=4^4$C zlfD#G=w7@>6Eu4l17rwphwc){s%AwffT;SvFzx(|nxLaWiA8rx4=cJ7&A_Sm0yK^QZ32vcdO}^kQ#YIV{DQqh*KJROZYW$IpKa#`X zXdP;9Y)NM7bMVJdQ7{ZXgl1}K+6zE5Jj)tdxQT`7_oq4Ene+Ud%SL~9w_dMoPj=LU~FYd;uA1>+)0(BFJRHh8a8#M76ilWbANv63!&ISM& zMGQ915M}fqh*`XSK^6U+8wQt1TwJykfndIb%fQ@g`3cRFLU+@at`tNiIrC2r0Ly8{ zq1HKdMT~&a$pv+#46J@*()Czrf)KzZNQz)YgxF2!`$7!7f_IU#Yk+@PV;6gM=A!vq zAOtpE2pQZ00T8T{$Z;(DSlaYC6NDqmVww+gSy&^BcD_Z-kYf%VZa>$ppPN0J9@r?n zIdjQ9wTR%E0!XUxdy0iGl6|fxJXA9TmA##WES5AI!ZBp9N;C&BaT|qr|CEk?9lI`V z`YaAjX$YYn6@BIk#z8NnmiJiF7@ad^ZAVfx6lZrJ#JVPZWwZXtspr(z(`nOx0}Ba4 zp##Q(hZX}e&j820Rhur_5>sK(pc3uKB%iGTU@5J>1e_I7dLf&jFc;DgLF!nV^5Lz| zrA?m!oD&FPz|v6Bn5AuHg`+-mz@%$t5aaLSO+&BWhT*yE-Yrv_k+^0`XTGl3FKzl6 zkWd=EpdBJ7WjryirHkhC%{oZM>MuX#^O*;@tm>=9I%$a&E)Yhs zHPO=1I~b#(tTo|9O48%bbj|Y2(y6jVX9V|1pEwWLtlu^D2?=#c+Vs=ZnSp(QJT1&b zJ`z*A>uEmT3gX;f%CyS$v8$$@6~E@DO+Te^B4NLDQK2!0-d5EvmsfNzC?!5ws?ngF z`Vr_=l%!lv?q1{QO~{Y4+!YC$x7K;u;>>vjjc!r^!*in5O4!NT70RMQQYxmhI(2f| zC{{v%#`vr$9ksi%ls5e&;FaBWePXas?HrKFS8GjfocX?65x;|mC}Jh=Dggxyw=`~v z62a^22EDeVU>fM=A*vK7yHb8CRR|HVh|hv(iv|PC(F#KhRV;$^!qqxB6g;^~ zxCe!M@tNaCPU)!Nl^3Q>KcRlT&_E@7@Zs_Sw8`qK%ok9FubNbvj%zR*YmsURTcA zSQl#o0F|Yg$thm!y;CMU2DFt((Td}Uho?RQVk{%x^rJu%be}n;Htp1;*=?51FkYvE zrD+W5U{mb4F==9rx>8vb%wrZ#jdFU94xi@qoJ~K{G;ei=Q6#YC=wyxjd7cni@|59; z3+;@SnS%a>uPxY&EmC=sx)F|m4Sl}^qNA{lL0(UxC{2KQ(U6g-L>C79j0QP#U(40t z#)^y`wbaW%YJ*phGXB8Shh-qk(xy*$83?dLoHMM%Of@ND6tzX!%Zyd93ab?O7pYt6 ztWvZ=QPyFNg}O&Kg&snk5G%BmN)ZW7!;Yd?fi0SDoi>;PRb_WWUCvTA2` z@L*DF@}FyvIcI3%4NzAB&f`?wuvi|F(2o13o)D0W(GYH zH+bvppPzl!z^@GSm71j=;j7rM_FmZY*{6Q}=FfcgqWAS)T^>K9q9f#1K9XMl2rwjW zp`srW${txMn$%iaC0G!f3_4z|xn{!ND%Z03t@H5ef-3`D(0T5(;p- zya{!KC?Yl!B&sbGHDnr!2NZ!~HJiK7@sCw>GTo|2)9X(I{|B-TtV~?~=yoxSUGKIe} zpX;umT>yh>Hi{w*YloJVXguAJ59DP{L9o<@E=&9u>$L5}j#d&3{|jFoeSmQ`ck=)Y zBzsAoT5_e4WH!A9TkPO~EuX6BFut*8)9W9Wm_Q!H)!xhy0jlE=LUh5a=7K`{7pW)v zz<6&?=Trum@1Y5xAp^CVO_DR)xqiYr+^9)TDXGEgFLz>afqxLNV!!l zPM)S2#Uk1V2(5eI*23JOC_+<%cBT-Ol*$7$-W##@P>!i0v`1dgWC(sRg~k}9z*tS6 zYYY*qQ7Orc466oWR0GSE*fzo|Tl8zhp6>p1$)4u>;0kn!Uik$&dOR0sY+z&{B%~gu zLJ|_}R<_`9x49(TxvHWM{;~A=^$*dgmsEAJ(KTT`Wb5EpAqeyg(!4;g+GF|gg%y3A zZ{;=V^(R%%1srQx#lbIlRGQL#(>2ap+^*B>|Q zS6jd>cbQ(JDvosYo&Zys3<*R0S24-Utm?Q)#Kw!=bSw{|+I_(N6@S(5;`I6l+X~(e zP&0}sEO8TXhv53JciGqlPropiu;V>TT>9z84m)HIaSKgF1{cKZ{6uEY{|F=eEzd0iUDO65Q zqoui<&JXdC7@#!?uAbFV;NF?F;3+~arY`7~+7iyPU`2wIq9b<5tymMyLwQ^bfd^Jh z>4H!ESF=S&1m*6!%@#CJVw9bTNvbc@bdyyGWV`yeCn*Wk$Z=)YX5XTb^l|B#GLq`w(&ftx+e}DEb&R#t5PXnJBxN>0s{*C>2lzy}HMCrI$ z-<e>1;)LFH`L|9&!U{xFso zs|6t5zGNIxfF76L_yx-|Mcc=ujv2=Wl-8r0|TSaDb3OYK}`rr*2klLNaPX zI4QI^o<}XoEom%6U)EfAOGW1=v50u{2eDgt+-0(gXJbs!fhgxA4>Kd15)yhSAC>(z%FzvLeuyNbnsIwMra5BhT0s+9qNwRyI7fWui!}unga9363j^SHovfGWTXnD<@DJ zvw9FZm5Sl}C~XNS?u|Wddw2)l*s5-?=4rG$2HR;G7_cn45opu)MY%V9~-LZD}8*J zdGq^hCyoW=IQdusXlWbDN>FsJ%&_>`)z{LrdTRcMJ78}Kbn^WA%m*ziyMcYq7pEb4M{BnTe7RZ@RVUdbC|I$&x$)}CQNjrR-%aCym+p^?q9BO= zN2tJ8j)Mf+?nr}&p0eDZzw4G)^m(_j^U~(KXq1nY!Z;w=IU5~?Y=WUF&4RDUL+#PG zl1@Nnn$y%$0N@S64Elm%CCsqTvnunU@$t0zz1&ItCKUYv0r+Gz_zMBqKQhxT|2Co3 zAkB4;RgMsHx1`N?iW}|$CD~90Nx)OP44R~*ae@C@1&rgC6H&(lgZL|bXn}XJ76M$* zra<&1XHC{EsT>ZX52ej_K#<*!=v$51Dmz4Yo%?C}Ey+oyi`jj=GEYcC-|dtIsWRO1 zGFiUYh7!?qOTM3%+36b_xg0+?lf()1P(?Zjw~LtGmJ|vWz)_Pz+h2UHqVKu!<D@NwVs9a%Kt@y0U~*$cA7*1M z^yXWo5!L_(oxEz>pVeJHhbIn_qT#XPLeDzITYAOl!M=D`#a~v#x#Effp| zaq{A;*#CT{Xvx`wG?zBeYYJ&QaAB6@5#MZ**_I1wq9s3*d&1v{u7rD~ku# zO?0#?RHP~Tceo`t(_Hs~ioR>cQ68IbHt`rRqf4zZf>Z=KgI*Ef<1M8&+4Dpv+muZx z&@fMB=XnFOCWaL>Yed{^99KCI?YtpvzDWbAc5YUF(u>Rmg}omd^sjrO;%|xFnO^?{ z3J{*0*OMLst6c2#%Ie>>K0+!ey9y2Nw4%C@?RhY1OUMB>tO6RQId*o%UlDsez5c9| zt`I5zMG5(D9D@15G9IL{CpCg}67ssHx9Zt+-t=ZOjxpyW58w3j310MXxZm#SHf-8r0{p>H;UvS}sE>i_r)X7iyD8L_ zZdBL77WAJR z?bZ^>ZFH4LUk`W_V=vof9as%Oh<|Oddr%m@04C%WC`tmHohH)U*XN!y=Xd5@JNkR0 zKQnsO$Uls%8@Xxte-2lM%fs&-K4W-z=ud|>4gJK>V?$RD9W?l7gBu3#A3SOHZ_WPn z>^o;4IPgaUUmW<*z<&K(`ycH;sr0v{A1#$nn)T+a&&|4Z))9Sw)wj0qeSHJH>w7=c zduY!$dp_B-B)v(CGyc22;)Ngwr!5abtv#EQh#riHJupZ7L^TkD^*{8;gTwZFIOd!a z+aT_&c$LSgY0HCHiK21=fz{Fn>RQN+Ru~T&5?@@^twd|j#N>p7k0^viH@~JHw>5ED z#fv`%(iTP^R@owyLm9L@7X6nUB4q=_*GZOvL7tkFeofq1(ee(u?kzFapj6R6wcU^0 zAl4Xyo0PV#6d_h3sG6LQHV9*B1ZH$~Mce<&#HL&BE0Bwz)~G?LgWyov474Cx(fmt6 zzb0mYc5_7=?#GTyTi&NgPF%9$(kgoF-eR3l&=|5&yaSxp7!g#+uz(`Po@_DIsGf@Z z3o6>>{>ijuIY1E%PYI72kbw6PxMJ)}Rcp2ApQzID%^zP%PZR1J~o)P+zq<+fbs)*O(DDr@Xj5<37`^>+kuFM zWM}nPC|*=~7}k~3mbQ#kd+@=Ys*le z$B0j3e{~;93d{LWWl7~hsyvjo+{yEa;B8wcr9S$AC9X=$QIlo4YGM08V0#CqBuZ?9 zRU8s_L&t@ph!s5KQ;yuCVq%F5f3;-rBiarz10%bb<-f9y^ zB)}C;1Ex`=D!Z8s;bb*eMFp5lkBg@^y!$KK{mc5YE$?v)(v}zOr|eo2%PZbrdtTadQ`7dE zGsMEaaCF9~tur|er9hivK?`zVnTOkG9y}JcWqh=D*N)+%Zv>wlPI`4;B(T1qWTMAi zRnUtGJh>|PBU6hW78QPPMY~|f7N;#YfRTcY?ucFEU33~eodE4NYUxwPe)q7r>* zP!4yYDUUG822j6-{4-y`8vjkDRi{>N1;jmR%hl9tWS(u{EpB7Jfzb8_F!fA3H6|{o zyoVZe6IUS)Mh|LF5rDK)`UnPe=8~761eRdMcjUl~M!jg=-iD9TlcdoR<0Dlv}LK=-rWc(S&U+js_0ZmNI75m zZ}D7npwT=Y9)l6tM^$tTB&E(n62i~@CdAw9BnTI}EDD{Q3t=TdL+Rav0M>;rjY8*S zN=2YTI0`ZUp{2WRUnO!?sMB4x-?CAMmqvR32h9845D%W<%QiTTC zHq?rXjiJ2wNj(;|MHU1^*<%DA4vgdnoT<61;uHKxDg6))TC=7Zij8Y7Q+&t{)WTO= zab&g@Q^+jC_7EJsS%H#6GdM<5>z7q7mPwJd|D@yu|CPZMwZ4!y1rcH50&_&#{E3VtQ!hMF5@s0B4_c@EQCxE#sXPDg$a zSrGqG+#?OW(;jDSnx&MuBs2iqc(`(&aJVXM{wNB-bA!l!U5*xY#$noYd7F9fScYy# zJ-@~w{OL^Ogm#E%M~r!Xt31|QIhUsR0@3D=@J=qBYLiryBL^+>Pn2W~C19G=14PF{ zBtMUPR%-FY6f5i65Qj4E;xMcZl>vN)R@U;T=bK->pmGlS!ZiHTv>c78YTiZ#V!?x! z#|lU#+@Ff%ndZn3tW9%Yn)~3~1#@=J`ShFxqhA^Q*ytsry(7Oo^7zQH!(ShMV)%~X zbA}Hc`p2Q)9s1(XilO_5E?zP;IJj$Y?cj$7Po4euvp+QZ+<|Wm{M5kt{ZEr1XqHx% zE}!+Evo_9pVAg(pzuC94Z*lK;d+WW+dq;b|(Q|Llap~J>t+whfE}v074*VZYTd#t9 zaK_n=V2X7MN|@%)d2CGtO^Z9NiA$@VeJiD{S4PEY^jKoc{Ee-KMMbNKw(~KD7Uf^S zE4ec)FR30&`wyk9R~U={gr+tm#c;+ss}z!O&-y^s6JpEK*2~+~guO>`;yy}CGvQOD zgFsbStI31sk-o-$E`u0fEmT0k?#$upd`WbL(|qv15lWy*KRjO*W22q9G@&*8W{de0V5zUt9piPAZ=YX6Qop2 zSmmK^&aq0k9kKdr9_o`=J9I_WlViha>m?oj*Z`WiB@FpP3lguZdNS+4v~?-n!^8)JdZQ|YzC%zQtGfeZ&h{| zB|Xe(*~_Y)okFSsA6?R)W!==4KXso;u13dH% zwYsT#po9mD&Q^+JHScxO7&C%e#vO-;nlmCXv=sh}s~ZUFV{ z6F|>Wd*T5eq1)q_M4SznWb<_=X+m4MKVTRO>riuCB7@^gtD0aMgI{Nw(5}fXu!Z2U%c`2{VWaHUGoz~5*ny51!D_|H}B%??2 zw^TJJL?UwQ>AapsCN(_{HDu(aAQl6Xw5#VK-=w&afc9f(e{eORDdR{&6w#$Yv-$*etTmbjH8nC;C>6rK+}-5esfz1X1i- zqD8A*J0vy9I*6CFZ&pHws_}e-nW1@Esy+{kVsa7`LeScB2ZzBZ3vDl)IlDRs)SpaS zPem^jIxEi%7INA=fsIq7k)Q<~&KR_FC>K;msr$*a^%PZTO@& z&S+SF9n5Msn0yRKyc%ttSRJCqt!e9t)xe8@ZG|UiY=-$HWAf>NMTTcKG zdlx;r3ZgvrQzzwT&H72z*%ZA!Z9P5{(S%xRWgf)HSL;IbtXV(2IzaLB($?cbD7Z1b zLu)iy%pA?J)qVkqARdQ{&TY3tFj@OfFkPHslL zFYE1L)gIoSm9`!QR~3S)pD`ZV8n%AFYU1^RwDri`gr8npDa!=*Pg_3H5eu@&t`OxR zuUb}(U{bKSWlPK+(_%!*UK#yx#b*v(m9{)hP4Q2XEgWlnl2O2TLI=@o7%pxdapor| zJi|fd;~H)TwCw+3hz42hWYeXU=Y)AFZSlUu;FX1mcMnY=Zz(|ubUcCc+N79yk9D*r zuC4q~X24U{2U?eZ?;7{$s3x?-NlIy!dgbHiDi{f3SM8@@}Z)v!`LiM zd0YEXm;!fV-d1)Eo1T}c1RIxDK7p{FODH=qDj%l~S>VTE0(B5UM1UGb6B89dwyj)o zD$bxCLVl>CGhfC?%Y6{s+Br%&F<{XLWo;4psO%miPxy;F6`J@R@uO>|ru;VZZx2*- zehSNOw>+k*)IvdGS{vl0``>5fZ86D$hm4_<(1J{af;YpAscu(+d!n@~7ZJU4K26r0jJgWq(~f-yzu?(|ll>=@ereX<^=<0=>Apw%&glJK z@Alpodf(rBaqpa-Z}+^?^XZZ-e`>MyP8RRhLtAdD?bX6fHuD$qIguaWxgxM;{kZB~QFckYwP21;(Pi#3iZ;;nH78WxOVL}> zw$q|$+miAy4mB19&@m|nX=VxrEiQu+F%U}~?bU-raicjZD?h5ozNKTTnvsG*i;4z( zI1bp`#f%9Ed_1$N8K}q7wo`L4+NF<)4of_)vVKXY1_%Xs5Tpv!3fdvut|9wstEZrs+w(jG;KQx*oyf~ zG}x3I&8hj#hB%)iT6#$W33%lt2aBt>Q1QOB?L->QDVR{F+=fyeIZH(}YZff5YT^jA z6lH0V)kZuu)~9IJPMD{y75IuZ3##RtD0)@ec07;dUgQoULNWG;3m4p4Gk$vYMm)jF zhtjs=ER-A?N^W#k5P-M9 z347`6kJZS@#wlKihcs;G>3Lo$IF{un(zUEUP=)W5fY`RBG7g({Bs@E8inV+?zfmMm zU{wq6N+AKxi8JYlPM};N8Vm$xX(}{I?2b~j4G0$Gj~!pV0nUx%I2O`^0%Gi~E$+zW z&rE@J-kR0f`Ady8@E}5zRfGT#Zw&U0SEv!JSw$;)LW6s+6qAe;@m?aqizjQw`m6ea z;P_zLb}VhshYHyS1zv;m9&u;Bh5!^kU{wmF5XGL-F)RG#E0kA;rq0(aSZztl|FuP# zWi|<~QO*ae`ikJ#Wog?2xGg>QB)H&&bhB1LXpTK2p& zL0<^}$NH+*isZhujW|7+4~9ShS;XKG^!hwN;+NeYJIyz6Q}r?g0C&^EmhWA%s& z!q6^Mn#>T*bT%-zmkd5p)tCIn?o8W`?$pk9mg`r9Of=dsaBnw~W3rRutq}q=KdIBK z!4&!sjkH$Legw(rxO;-J!>d9NxAbkAPsP-1#{#<&EDE1*(SoITxMu8w)hnRAyaL!qsE~9Q+seY!!cvS> zeNeDY)lgZ{RrnC3{33+ao0V4Zt$3-g`v@)mO zipcojV5BB4UJGiRaDo4s#o8T63~&*}i2)80bEK*|jC)N~;3Pv@z}W1)^Q#5b3#ma5 zdLZsVdlIZ7?pp$pRUnG!7F0F4O1T4~oO~hR%9%6fnq#Bms!J(|4)1S9XJTJOlK`)S z%3)=W8BeO7Ptn`cws)J0s3_E}r*7v9A4}z_lq=M5+kq=U1CfNPA;F{o?6v}AT{>ESUnE#fKTy>hUjE=WCIiy z1#-V@=9Q~wQw0a~F2@RbDtsB;@{^VpR?p%+vCLe^azBf~!34CXGq*42yyl3i_Ter~ z+vcFBghH`g1V}L_jtGvfYP&7{$*6g=N=z!Z&m5J~XoN$Pr$>_`5No#pzS)L%}b1h?+fmNrP}ZkF157xLI>#buq;+ zPTK})M?iFD0ycxx-b=(x$L&KuR!_`yO z+mx?LTW?e=d9ohX`sn+1hwgNboJ9kfIe}~mMJMN0^lHZRy?7*qV%lrfL)BB%(iLgz z4K!qy$}dOHR@RPT@CO4al~Z#1xelrTks}a!oE5rP+`8v6F9yJB%<9Q%=*YD7dPxY5 z(G2`5`dt|w%;WJ2o?)r{EMwl$T~WcMT>$)Hh)wDGsyDwLoVH#EUo}r)xB3#~VO@@> zt=($KqVQ{kAqpMSOAJhx)4cR+;@;|sLi3!ol|^!%gwt!<60XbuNM%vqEK+qpNJsq; z64pl<+>jtejK_1Q)vWPQ2Hm$-Pf!aB)7ER?nAi|F%XZQEJUeQADf2*GktIn3Z9SqK zaikm5#DD=aBjKH~%t%}vfQ7eMDPd8BpVV;B46H)NyeQHufrnbwWD> z(4B+-I{2%DV}q9t9>{KiAD?|XU!MQ9fsYMb-~Z?RpXtA|zqj;K=_92pX8kdn1s0fCOOmqm(x zTg{V|$EWRwQcvbO z(Csfb87QJ5yvcnw`3LEk=AqPa(s0*40JlQKz_=|RU@4r(L7AR&aZTIh?n~PbHXAxY z(0pJs3!%|$4$vh$@_)_sSJpgnIhwW~lq;3#@|1#(k%PtDUTPLzicy7#niNbKCEVxC z#OC^iwf$-Ty0ra3nxe-MqeWVFVzPZiqgKzVy_=Gcr|k!HFx7k${#ko@n)CpUD~hUW zf}}Wvu79}ZDZs1J_8HMYou*`THd5r`#m3x$$wpN+Q{iOzYwO$F;As03l9$&!EjXCA zzq^B_BHgyfi-khdhD}55XhTFd%sCgnsJ0K(mecnAC@B|W%?HT{k0fpl<8wBd_>0aB z<%V5$c`u6@%WFDRbL_mdecxu)4MI$N&xJs#qsx9cx-O8&p}VCo-?-79D9#(A*(l$D zwM^%nP@4;ux1{p+eS``GOgl)C%|zX9ny#$*v_$mgU2W$4KdQ}rHJy4mc468+SEMQa zep}j##emSF%!zkn=fFyjWHyuPzho!jpy-vfkLZM&W&m$V+vg|{qW}q|h{p;YE7X%o zu=QuwH0$?R+CEBY7$wybS`JXq&a6M0rP23P@KM@^k9OW%(~);{xZ6iy3t1psgN34D zUdG77xmc-6;HuI8gv+Rt!0O3Ac-mPyFXZ{`^m}SPjc!5OKAeTmyS(Khph}qDJtZw9 ziBoiN;Dy`b*?57q8245BOgzy!HBBf!m9`Jbp<0D-$esjx&D~bZ`X@e0A?ie}A_3um z;ElBbQ93VeALM}sMI^K1eGG~UmCRZkbb_Zy#&Hm`Ndg(HK|WB^$!=xlIkwM+?_Cok zQ=luRAYf3^MPx|+{3LzFYZh)v-`zlt3S-SOu7D^;P3A6XK~1xnIFJFiFFjYJ&1;6^ z+Di{D**EHZvoUnMA7REQ3qJk7xW*G|n)gKgNb7=Nx`R_j&6eQEnFi<6ZkZ80lx zy8?bsz(I96sGtVAe0w;~7!ZSC9jT!^YCVE-a@yWkFy`%ChC&uXChwjptM?OM)or1i zjvDDF)o^%Wbno1nCRv|O+j~1alr7MA`~dU9x~{BxE*8G_?1itf2niBmlQ(>C53~2u z=bTXeR4_KZgNDzoK4+}o_|JB`=#F}I)TZTO>WezS%YF9b?%mt@)gPkvGwF^0ppxPq z)P3dW`;4zK5nUIpTT_6bJ;jwxebrt#kF-hOM}x)@WnY$3>Hd~ z4%r1WR;^^)z|;`Y@~Y=f&r5Io6XBUA$gn24Ep-_&Kx%1;zV;6qFX@L6rjU;=)Urz3 zHRBH%HuzFg=?C))?{{_8^Qrr!H~tY+rUBD6*S+g=ZClllBm4zFL`*?nthItM#1m&$ zHKF=gdgC7uHo+@853<6i{2ISKVBsl5IH?&-uvfgin&(pA_w%s{9kR!lz4`C@h^ty8(qt6c_PXliRW3wN-Cx z-aozZw^XP~RK+l|spuNJ*4hK5Q3V(ER1=;JH?k&Wo z_81085X#Pq;L*4$o>F~+ilhrKm$Fk$8`bhS0AUsd1>2a-b)~n zO9?cM zqOQa9RHs+9L->)j?GoY-c;v}J`NNQ=5eV{(J;ngR%*7s7eUy4a(-_L7i$S!JSlR<+3uQdl*OQ4h`#^jUsWCqy zQ0`^g5|U|==6aaoq`5AjILS$<4AjLHCo7MtK17LSY1>jUo;FrBE7X@OLG7(xtu?n) zAC!|lx}3J14-ns$Fm~L zkXVEr4uXcrE^E^*>T)h+@c#f0vuIY-VJ9{ualZKGcv9Ur}U^gxym{LIM1>>2p(@UIO2@bFc`2MqloI|e>AbluP) zgMT>q#ld?APaN!@{j0NoboQcw?+yIcz~}ql>Mwtx|JMGaO21cnp>${I*jc|Z>!)Yk zIP0LkKkZxJ_o=?+eJAy$-rw&1eDA|O|IxFx=ZT*4dIr+(fl2=FoSGI7JeA&jHw?i< ziyqO)R~1F@D_m|tQv2zmn${DPm!~)Pdpp4g+w#gjL0VkX!h-wKoBJY*h>F7fpk~-C z{G>A}gs!t}sI#c1g#|Q2QntB=e&zh5uy^D!v2PTbNm29RScv?sS@ zjeFp>xTeJn2xTPm%KqOrB)<(DotL3?%lHXxwRSUF%s?~c;dk85@inb%xGB9k)YT3Q z0C|CwmvEdG@U@l!_=7(HycXeW^b3H)YFf>3R(f-`(i!k^S|XEEHT*?2EnxuUzz+b~ zfw;I9ix=MPmjZ+r%=`9~#=+Z@f0c`y)IwARNoI{!FVGHyhj%D}GiqAJ@MwB-Rx2-W zr#%?b5N|>i2*pBS6pMPm{9$@f@<5 z;8&cZC)!?yfFu!38H2&UzXJ^Lf2wm!?Mh*AblUN6(CTDE_!guu5onSgCbti2#3IzM zBY%w~O_2UFU$O;g8rr5*pIv?cY{>r1wT0zn6TPY~{H zbHP;29n*-$1@Ekp_tv~ScyZeC4}~TJ7W>ur+OVr=!5X$VZa7^jPp&a1otcw4uBO#S z=$odPLFn(_F+$5~-U-~7c9b<5cphug@Ri2DD0*|$hv)(`PPc$^=X9j!RrHW)bG7^t15B9ch{x48F4`2I2qr>{QRLX|DRowBxVgjt7z^58hjYU<0r# z`ahck&)Wfy=B;V&|L)mbur^>=3$y*7Qq#;eTKHFp!ktExA#7j($N8~Rf62*{XV}Jx zHO*V!ns)qUG#r95{i-iriKGN3EEUl#HYokY?kJUuAioPITA|MAHO*%~l6L$bA#M1S z!4aS;Zb9C!{haNkf%&4ECb?c|L(3-p#k%vy)^m3@UDT(nef?O5arTboUWy$G0ht%cV`FTj?C6wc4G9 zl{S3CS!#UZ2NbBT&I8UU-`Xy$c{*)&m^i6+GAP`hcKk0O=CT@asbt&yXeJjwyLJ-A zv7SFv2`Uu5r$UVa=heJ>g|keuY87=)uANBTJJXK0qApZv@j(XU@9)YIJjxepC)Q2~ z?)-sUDz@qC2HLSo5v!4qNG8ZUp3-Nf9pAE}ViQL8@0eY@KQ7+y+N(|-Qd`LDC27ab zs2;fI?CE~BV|lqC?PwX3e31K@wFNw7c4MI#1(TF7Ci(5p8<2nXyp;5MC6dx|M+F_k z70fUmLrrEFk8PsVnN3Y)8f|~}dJ0>k`=s-lY_314b~JTvPumwDRHtpeYM0?)VTDXo z4$?Usy5jYxmTN~*{+_h`n4)~nTS-i43+!pj7gfL?T~s@g8qBX9&D)%inrcn*bA@oB z^Cwn;m(*7uN^Ra{v&~bN7uDtmh@;5ANXMxpNk#qW*ClVn7U31ZnV!j73liiqPpush zh#x8D03xUj>_c{X4E_-9#OrM}Z%1Y-Sl-b+>GRENH5=!wOovX@liK95ojOJMz&|1z zY#siCo0&+?2-xe%5A=Ml@1pdJPuzb=&mmv^;f3e+{^FvWF75eNxfrOI)?U`fm;DAx zYcJKcR9d@C*P+tdOLQGBt-V;+!P44`be&yVd!eqgN^39Bb)>X*sjk#JKVQ$w@6Xk> zueA0YUHeOG&(@XtXX#p|fiv@qGjs*O>G^(%t{`-pt^in^-!IaYhELTMfT!g5C+kXi zQWEd#=`F22F<(!}&yUad$LUJD3%QmB_}E-vfvzBMjIK0rbiN*?E9H*N@8{>|N95;+ z>k5bF=}N!_~OAIaBYT`515-w)~vqO*0KQ(8NKl^Afp9?(!JUuWqG#(la{ zp;y;^OKW>{1<|DI>{6qw>p-dTDP2cOjpuY7Dm8vc*L_QkPwF~cYJ5W1!BXQ{UH2(9 zKCbIQrN%P`aA>LVF+BjlM|C}=)c8oge_GcAON|ffO2wyi-M`fMkgoGejVE=bfhTmG zTWUP6>%pbQ2X#HF)Obu+>U}_05PekFIi<$?b){VS5&f87YCN1@Jf!PkrN)E0($E9C z9$sqPuj>h=#(lb$N{#pF3c|~EeOIY*udZ<99$ohnU24b?h;{Y@T{&|5TunyP{0Kk3# z%*Pu^)f~_5&!X{Q&Rn2aYH;><55nKd_+GupdCn?FT?4`+{qJW4;)mg+YcOAs@o47 zQL5Vy>{F`S54^in|F|n2TdF@J1Wqm0?FZ(T>h=Tsmg@Echm`8}1N)ci_5%R4AK<gE_5)O~9~dpw z?FRs4KL7yx0m|7A99pU`7Xct(KR`YEfd!?y{Qwl(4^Yp3fUEt$NU2=6BbZ&P+YcO5 zs@o3?mg@Ec!=<|Y0QdF-vr2XQfl{e%KhRsM+Yj`W>h=TurMmq9@9hV;w;!OM{Q%GH z2e`K%D3wcfJA#2y-F^T-_5&2KAK+>~z}0?$tNj31`vJ<^4{*Iu9H0UF0p8mWaJ3)c zx%~hL*$?pCexS?)I|2}}AK-=k008X=pwNDRa`poNvLE2R{Q%GH2jGzX0C4OFC}%%F zIr{~zyQ0OAOIwIAT#et-h@1KisW0N8$jd;0;d_5)n)2e{e~ zP|tpV2JHu^XFmX^><75FAE4Zz?VpOXGv@XKys#etfc*eh`vEH25Afc8fUErgSNnm! zQqAVf)qbG2RI?x8YClja)$9kj+7D3f*`mV7Lj*PZfmx-R{Qwp12PkMiz}0@Bzf`jy zpq%{x_x1za+YfMWKft~H09X3~uJ!}Ku^-^RY)8Nk`vI=@0~D|y;NE_qr&O~a;NE_K z_x1za+YdmI{Q&pNGa>c^+}jTfmTL9`+}jUuZ$B`rRIb?(0Kk5L0`>zmU_ZdU{Q#V> zAK>18fb#YO+}jUuZ$AL#_5(b(AK>18fP4Fa*`=EO0QdF-{pC{4j(`XD0~D|y;NE^< zpHeORfm-$hwd@CK_5(wun*9Lf?FVS^!qBB!_5(Hh0iN3rP|kjUtNlQkA9e&>?FXPd z`+=JM0MG3QxMn|4%YL9{KfrVQ0j~A~aKe57IQ9emni{<+y_)9!_T1{+56``WW7Pg> z&Nt@#t_@8+TI`PEnn6< zujdbXeuPum{}?1^{Q-Lv8!y#6I5_S6cPhN3)rB$m)tFWzVdWMDQfQKC zSnbTa308S$hPWeH0yO6tme%#PuV>TFe}g=wm`pL5BJn{@YJ|<`T{$_|7yZQMA z7SxqGx+?AbSBmY0&0xVy68Xluaz>A&o&QomYM)tPjG?-Vl}?_h+6i@~i*89f|C#g$ zi@Jn|K4hoSy1rDQalU2ZQ(hO=l`KN${{$-9>!++GP>NNcX3Hd8TD7RIMA7oJQ)&Jg zCBe09R%1_)S#@X;jRtXf)EdI?GVomMu?SK7mjfvu5MH1JpSH%O|-?&iGKhm*YO%0zuM z?fhFxXwjDBpO>hUbkPKW<|Z}IPEtLmNOU-x;K2}OqhQE)K?fBrQc^T8o5(3)N#C17 z_2AfYUD>E>($2q8DPA;yT|q*b6HyhapQs-tgMb;|0h;o(o?R3cqCFEd)nrCcB9`3= zh{^^utrn^aJkLn4=}YrSfb80o5Y=Omtc9yu7eb3m>q=w6&A;BQK_!<|pveE2t6HMR zq1vJ@e;N6Vx^h^Lrk(#+4YaIoU3iZ-8!(C4P1^MqW)PI!wkD3LD{*y2+WA)ilTBI+ zS$Y<30^-RWw^q%oA5O7z($2pGW0tu{HYf)xNBMy`j;PP${nE6Pl)h$=$gt|M$j|Dy zw0@Xomm%^0@UVUSO;FMP>RM#qAUZbjn8f_@=(@ImT$XnJIrTYAM>Cy5!e!b*KC-SI z9~Y;cf3`;%PIo9&qkb@jE=)WBH(+L>B4Wjt!|Df7_T($4>7Xj*hp)~X9o z^B3ovYY(aKONk|ECuwo1Co8PDXdnOTX5*l`_AQ*6b}H|k1@6^o&2@*=br2c#{(x8d zf>|7Y;)QkV=GC>607dy$Rs^Mv1@}v2*D1elXXGG`V_;gr5pks+I0v3j_vf~*5shl~cdTL#VfZdaJzDWW4BDUyZspzS{6x57qIz6HS z{z6p_s1H)*#I$nC>P5KE<>d5l^cx^Vk_1hpxl^j;{|;7;XDK3U}7o z&`gxn>R#_(Qj=%Y`zega*dAQemb~_{XiNeGU$Pg#An3+W#MZ<)^%C`-N;}_ZHlR+* zf~S+AqxbxjyU^z+MZ1@c%TI{mI^yTF`Yb9xkaqqqH0X#4R(j+P%+3N-yza;fqZ_q& zYQ2w2@bhn#ZFm#dajP50aL;C$a*g|a01&5oz+duBk4=Au!3100d1~bQ177- zk-#^(rw^fGc59MNr`tHPo&wl!@99yf84cFt+i38&LhFlp5KB3q#CKG!tmW#<($1~$ zO^hXeviAZj&Dpr?KcV&ZTGNKRo$Ttm9$ZrMxn4bK=l?YSXV{BoN2Pso zlPD|FDDVCKB{iSs1(g4R*6e1Ia)$VnE+{jB6t9o3eFD7Ey)BTYUT|8fs)eD_4_0}x z&jS7DH@n{#)U@av*8c|VvJt6^pKMBhyO{(lFGwkqW%rmS2QZ|3`1 z{n*+wl%iYzMl&y#(bRV?8k3W77Zl2e)jmc^JnUx2gnS_$y5y&!8<)AT_EBCFj{Ul% z8Vg}j4PH%yT-du->4<=jP!a-uEjJ0#E*J8YM}NrbC_i&iY>3!%_~s3Zxt^j5Yq^f$RW=&$?vc4+5Ssbq ze>OB~lab7qCCO4l!Okl+7u7x#%^v-Zn?1XxRnt$VH;81j3p?qT(XmQvHm5%0K>qic^*<+AkV z;WF-)J8ipBH_>61mQe$E-rEA+V|CM_+M`kDungILy32FRQE4Jx1WCkNJE3LOG;`?N zHe+k=QiT(1k5J`SR#zU<)r>tdp{U$5-yemRFQKAt;o)fE;CI}@fwhOIa&mg}Adrl8 zXDD3&doX}$Tj)Dn-MXOm0ClcPZ)$@mz94UZZxPhWjdU%I)XHgCvHv?n^ugF$eRBGf zH1}8Mer)dTbC=Bd*E#h$%jX<7`mNE=kQrDs@~c~vkt`x5 zR(}`n_^|H>9^T5&&%tl!OhHpT{l8({K)z@mtxNR9b+0 zmBII*k2qUev|7H(FABJny4y*0D;|#KvB~%c>i5vts+-bV^fp!!MOvo6SRMjZIzSp$ z=ZSUY6x88&Fc54vLYHUvkw;hx7wf7ie_~nDzK}200aBcviIyQuLZx9x8Wxt@^>fkK zC|VqA!gs*&47fZMU%R3LZSDJ-1W5D@vE4(RI4Od{T_l;s3@5c`NHE#Q1W=PJEY%~g z6zTHvto4vqn2{2Q8IG>GVCPkYxS-jNYo!q|t+{?|LH%wRK7K`dOS+kt8)d;d-{Ey6 z?q}##)Ti3x2Lz(!78Z{?RTTZ zeZ~cDa%H_8{+Vt#&nnuyz*mhEp=LymM{}NWdSzsD3*Qtb8uL#X8h( zH8)#h&)41?;+uG{v>nYf<_kkne{s>tGA~euNVvVTxA)f9QW#FRgzWRIA;)Ut0 zwU`=<`s7(c5AnZU;(GfIq|I!zp*YmTH`?I`Iw#e&XXN&@3wXH|vjmv!;X|m-*Hrw( z`pp!-HI;W2hqy(CQWlJx)|cZ1oubaBA6C~+k+agS!;u8*^z}x{nopdcSHF?h=cHYP zwD>HY<0d5&9Qf2)bx~cLJ<4ep=bWJ>QjlhNIK>}Q85LH0Bsw&3qEb9i$8Izxmej8Y z#zSeB;@OmcLam9j>(^21$+YW`-CK@^%Wiq^bx)~ZOWnKDF2%RxR^1Qt z13ie!@wEWDr#x_9T)&12_oZD2QDFv|QND}H7{*7Y<7z5BkaisiW9?heudvU|qudLS zcF_?NmdBF%RaAl*2f%RUl2&$DS`?4tieR<_Gja6tx_4^~rd>J}#q%D@G^3~VN|K1= zZG(smz2Kr<$j9KPn_j`Fs>j!_p#7WDE)G2bKpw=)JSLjj{nDJ~NB%JHqWa~OU7mKa zU#|<-2=vMXI&8np{#x#t*sHbb%=%^2c`WVP7x)T8WnJ!iJ6~T^yI_ZU=OK&=oLRp# zD(=H;28Vcvc0k$Q(^?=5Z_0!m>Oxs6Twg{_4Cq}(o|9GDT5Rrw@q~^-=1KKSqWD}h zhtkT3PMV97g$v@4@KAz&L|vO5mZn`gSp*ld0;=jTy)2Cn1dgm}YmO#}osB#ZRFxk|-sCofxG zz`3!0w&2W5yGpy$q9X~X;Xk#)|wX2yA24-e|(N3QduuI2$H2 z%4<4fcw}8WBT%qD?(xuKS=s$HGsZv`Dokf+9|WGhSNavg$zS2p>nrM~i=g4Ot7j%- zXs=7!8lACY3L%R%3e%z0mP#70MHi!qEbW4L@Y%F0b+d-!g2C5WclZK@Gi2+udVZ}b z{C(0rY0ev?e=&N+$SK3s;lqX&%pM>3;r{LYmz2IX>zTf9aG3ci=^l-=mz2k@t83%` ziUZSI{~dhzoV$=K0w`LVRInhsEsT?AQARy^C`LOBi=aZfCIWphp{lL~=@=8BxB)G{ z#nUk|h({{LT}4`_pN5jZ|F-+^iqXgnIFf9(>#@7*Pl_S}+iw8QMh!guP^p=5yXTZN z+Ka)h2{j(8@h~7D7cjrsfcYB`PZx%Au?S7R8|%!ytv3k(4CFD|5WVc!g$0YWn(Rp* zWJ1^gE*E8AlOYymcMA`Br@D$*JStmpTKx$qoVYK&wb=nN*yBH~n09o;#V=~ygmgUg z2>e%u<}$?UlqRRd)B?m&1B^hC>iKBxSSEJf% zTrqTlQvfT7;!h4MJ&Uhb#zs|$=jiEqKq2B*L-y$%9l~c2)6od4hFkZjuBD$5V&~~W zD}*0IvC`FY;}c`2)*nZ3<9DUEew}-Iva*z4`4D2a5RcSE=LvDC!U;46Fj*eEahy(E zaTkc2N0bx{yjt*Bw}sJ~tTxvCm=ura?zq-EH=piQs?jV_%D7epB?Bal^RVhvfO z#)0|RYZKGxswm{?XY!b2tiS%4|LK#$v_OzgSTAm%+@EfcLY6*%Aino@ps+WX#E311%LlK4U|{FC)>}oI0b>&MIN-} zZVEYu1EVRuou)ZCA{T9h=HV#7S8mSe8FxEN)u35+<;YuV0K{-xWsUWhbYjc;vE%EH z3MI0gzY^dy@~~o<%=iE#(?;QERM{r;iWuPrJ_Nn^&!FC0io3EpZpeaoiV>D7&BMR-yg|5&Ce#1CX zNI7IYoRS8wxJ~#UcOj!rexZv%P>iZH{*{)KkRd)hfI7!_ZJchO!Ru zQ>61>?_yURQGWy;Oq`eA`eis^s|g-R2+|wlPiMtjxpWZs0r|Y)CrM3iyv z?e;-!712ni+i%Fh6kV62fFHokW>)kVH{=9de8+hG*wgigrKu03w>D)4%?y4Zr!3;7 zSfaAay9FmT!wxsXBWi&Ia*}5&K1(1Wyxf{ZJKgKX-bW~CxRQXXZl{Y>Rkh;hW|%S^ zg;HB4I6u&$VEa%j6ruT|HE9}IA%PBVfbO17N4k!W#@Lb|hcb}fdJP6;{vyL7QIPco z-ZH08@<8&}vl#T7|Dh#j%bs&9?U@zXq-X@+Zer0r^~~hY*%Xm6A;{RxP}BrpEO8q} z7R~I4`4PzWgoJb)?SyhEgu7o`hS(ILr7L9?*}jVB8umIWYfX&rU;qC(dlxvn%KF}W zugvTuGnq+<7%@N~1H=RZgj=|ZfZRmn4k7|_7ZDK=Q2}wgC&^66YrWJewe2|;a+$T) z4r){sti9Tv-Y>THylbEOp7$-Ep4ieJJkpl7w4A=*-|zpdwf9UCq(?qx&0g!d{I9>~ z|NNh4J?l0o*|@s#-=Ai_3#%IGbYh5+aPQEfM>v5GWvk=6U|33n$pJAjD2b%OM3;1{ zq?yE{XIpx98yMmT{;WMk$s|!KTt^RPf(z=TJDz-&BFvnL#SAo^1m8ZOin zDG)WG;goxL9aY9CNLx zTLRQWF%<=DC$Opnm!G<_ifLaAqJu%&nCe+UI@g5i#tU=q=gPE@o$pP1P~&1b#4OUI zVan#KfC;oCROQmhq8a~d8pkx2T)Oz0@rTE5U-YHXe;@hqf*;R+dH(6c|1kX8q2|0R zv%k&Se5(AIM*FyYo=pA5#h+ChAE$SN zQVMOo)lPJ8MM7Z;6(FnG7%Su}zAGV0#%4iOu&0}15Ryu2#4BfyDl)9fT_r7q(Gwa2 zi?V!s;hlK{`q~#ap8Z7%3o%JhME{HeluPVK0EmUe8l1*o7|{MY*-El+i&u#S>!XSv zV2`+@H+B1rPk=qY@$8o%bk=(GoGR)+1s&pX;Osret=_Jxl@NB0p zRiFLO{Q(2s@aBkgsa6f$Wr3OkX7HRC32^jp%F1hT_fb1cN9;NN>Dl9qfwprP739pa zEn_~gqShQ5sDR6*SsOp6CKt^-0?ixuZ#?^-5RT3+&{@b8x-ph%{0u`0_l)=#G}pjI zewX$|OoR$jr3YQ+S3bN-PZXev6U%Ee@^ElBhergx9-GM4BttJ(FYZkdG}7vXlLLj@t3v|rejm2Tz9xf zoQ@Ht5!;I2d(>pRM0~O7r83?DOWD&v$bVJ4QqG#uNy(EBH=g}D9K1h`3&&98P@$*c zTcMc80s+0V^I=hQ>Wp=IJ5t2{Zb8V~qAOfNPZ8Q6I?E;;S9b0vk84F=_$Y`e8*nSUz~G$BU?Vh~lDu0{4-jFB`#}awREZYn-*&jS zGGS91xh{6cDoCiV<-;eU3Vi!jmb zK1gz$h~w!5vhd2v4%wIuwR;TB5$?_+kbDW0e5HNPj7~joP2#nmM|{Ci!gUJh1+H*I z`U({24aNWnJ}<#R35pTS7u~U7s%8Y(4A;4+M3jb94d04(BXQocLp&kzs%|cg`Ohe; zj-@$GXQfMfkpdnN7dD8v_Mg$o>KhMlJo_`iraKe>(I7*({6vU|>Ig-94E1gWa)DO5 z;foP^u|+mgQ)=2&pU{IMSY$ZdthTi`kMk+j{&xKlJ#e>bUuj^oG7x_VY0GVD$6}-R zdj83cx#3kI)-{_I!Vj!aM*e-DfikE*Y7DrQv0(}_Dtf7d$$qHt zz>LnZ-*9K+*`MAgXC_9bnW!4%&`tLMSI_@2C3r9R?YU#QU52(5Ui!DWXS^|W78C)y zFi2VD+O$>EzpMg7I)%czFk@gaXerf9AimZg6`AQ-2*ABpQlJOzp9Vuy+}S>MMknu2 zUe|c`v*Ag~*$;$osa|iW#!6-P2;)xV`vdIe6dYi5tK-wXn$Ho>1m#tQ2(jL z2lpeb&xl}&Hi#D619(V5tOAnK214%$$d)#iT)y}p7XRGX=f+lz-p>iA5BuX{Nz~;u@4)L{i5hq|6>MG}4-uirPo!`d}1GK)VS^HsS~}0YX!@e!(n-qegYd zN*x^0R6(ML)HF3qUDRLRi~eOZE?eC5jS_U1QhlHK7fP_(C8&nH1W9*2p z%HDi1*9W*Z+|t-B4T;Azze!=A2s4WspZSQNbuuU@%pjK-vGQ0sQEaN;(%OIwqe#Df zrB!5czK=px7{2iv@fRYogECHsK~tP5A-fo&4&Pl-FQ9!z^1xKNCQ58GR8+g?`NPW) zlk4*(vXVO+yESJW;Jcvx&E?~|8RNAUP&^-?kfua6jS|bi2#)SpN`-Q%R{sbh^)d%_ zWW<(SQng3mD7uHM3Ph;q5kn|~gUWS8j}CF%Qf^aN=gJt6_j=2PwY*w&0Vqnz5CK8D ziP34*_NfTcsA~e{SnQhphPH{O1~h6%UQXGzs0-he*Sw2kE?}>HbFPo1Z8)>B`x6qs z90<9ReYLdH*0KZB>vrt1MfW1T@!Yv`sX1bo0DBM!!m;kRYOPpFO#s4dN6jd90 z)2dZ3%jqE!!anMw1!ff+NYd<$OUp?6Rq1a-mh1CvOx$*p2cuys@S0c6q`I!e-w#hL zS4vJ-XnMNr<^h+a3`NVHge8H<%P?NzTEO3sE}BSI)FW zexk98HjzU$4N;859i|;BlFDyIs4NIrXQG*URS0TcpK#y0NrwaUvLxa3Da_NVic%MC zyFTLJhZKe7l|_%yg90gZ;kN-MSd}a^*Vvv2vT!tpK~uzYG^VA$ynSsxCSzOM*!^** z#A&jWDqe*qpa82P@{Kwg+1t5mAf_^n=gc@sq27(hp2`k)E(m_ zmKei%eaCddD)@}iSrl07bOi^nK#OVV)HYCqpQ1Uqjh380vg_D(pN4ctdQ6Ee3hA#3 zzm#~;ITmT=q_M!PQIkqCCjDeokzG{JW|=jRFS??f*tWim&kUCOf)@h-h@y{J7fgmU zK*r3E@dG)u=xj*E1Rb%&qB6-$3ENI5Vu4weW9=g>W8vz;9>WBT* z&SuEl%#=t~_VzZ*#-UtadE7kIcsBOwLZ)Zg8cp0N1GibxU)Lpd!QK#-1Ydhp(yFxTby4h2flDzfTo zy1;EXZ#gQE6=TDD*ZDwzRO!p+GA zJyENOAfcOVIYuVga2+h9djq_QHN50&uvRN4I0shJP}Q3AJVYz~r2q+@rmoGKSVDVk zB;0js(q`Xv8 zqIEc6;z}DL!S>Plkg)ff#~)PL%NK7Q|M&5YW8WLQnltZ~jed6IH%77r z!}D)%{@&0Z4V^phGue;6u(3i55{4BEgytKC-<@ zD|#XcRmwJPR&}yc9SWg3Qs9bwl@xeNuRsdTSth*>l@F#o4QJO z;v$-;@XMrNm+lEaA*xVZ6K~Bk9g>qd3~+hD6ZwZee~BJo6P83;89-`Mqbl5J2BZ1p z9EsMW`d)VBF31lT&5Ih(-9euQP{kPNNDwdQAWTq`1%H^%YDNl6btr(eBoDp5lWNqB zrAS!#BX25#`n`=Z8$O!r__pbL8qeK6AU&!i`Rzq)_Vn!~mWEKYsVOur0i|q)7X`2} zt)pY#G$jk<;;}2#fdPtRweo~wEL4$fhd&*pkd5SV9Y8tBM=8 zHCm9#8eKq@X_q3#0JZv4Ac)d%(y&kn2xhm^2^MRyTNnd&C@VE~J?=S9WikPyEor%+85Tw? z30R8!K+=A)uYtSbq1tKox_m;mcx2di04n|zwJ~pyeZ({A_m1avlrTqS9vf70xfP8 zJkuzp$$>}az4I>CSyb693N3!Sgj5)2#bio7nu^}$>Rte>C>um_8@)XnF_|H%N~T19 zSg^(pt3&cj47AV84{5eA(T_BCe+Jtb7&|QHbe3w!9O!_#9pTpUK8<6VYN<~b*8qh# zNat3PY!U<#k*o+HCciZgkU07`3RCWtj&JbnxuOdLV}{NL(4%!&1i0H5=PPis4f{8C zf4Wzd?31J^X;`gH4?4LZ=zd9FDiCU^NeK(4VkaxKQ&7s+lTD>jS) z=|DVc;cVRSmZ1;;t%V5(HLO-KgOPZ&J7NPWo@zJpgJG!sNMrY>=xDA=UWp-%np#|h zUQ-HC10c3hm{Han$OG1-w5nwtAEcLUl4Dh2$$9xfl4`TD`=@9QB4nWTi&6a-vXNQ5 z-E6~Uv_SxhCN$FpOE6qyjv2uplu7gYLLjfp`GA&uN3h{ZD__)wad+BA?smHm zF9Lj70%w&qTIUD1BAsnQ+~^Jsw?=%4u1Z?ThSue;r+b3R-CK|coxcLEGlQXH$OEJO zr{y}0AfQkI9A^NeY$W79`jalxbH0fe-)& zc8(<5h-^g{Zvt;vt&I%Qzf1C8h3j<8?Z)50z!Gu%b; z)v8QAS|I+F5~fNevthdl;s90AheU?9kcw!BdZi-Be(8=a-WXoDDRV;;uB_rTECjWjH=fz9R=d_%5#)ExIuW#C+skONON!KMH<}!IbJ)k3(T$& zO=(~t_beIo%{Mv-$q38gIs!`DGI)rrh#;aswI@Xzg5Q(-e1=OJ&tJnsr=L|>hLZOr zY{LLNcnu7qMKk?cCKPC^Xqc)-%XlR;*MJZd5__kh1@#NJ$ zz=lu=$n6lcTV?W+e+Zbl z)|KLrM`b|Nu^8n~F`=1tc z)rTXiOedjl^&z^Pn9Ctafa3Yp67n94@Vrpb(J znO1I6!zPFp)R-obDY8w2w{kaAlSk+JM#jc#8qZ&fECz;YI@?$6<_Xgq&0WeNUxhHeV#BB?1&&4UdoSrj=2 zhb?Q)fqj8I`!2 z?9p?VS9o*FDWxst4oo^^Dk)1))#A3?S*^KDs6%0khu{QwgL6T&M@+#2_~QI1=$~pW zZ#?(DJsCi^_}NN1mgr6ytY|)k6Lf*2YFMA)U+oiNvYQZ8wqOnMB=Q1Kt%8B#o-gkZ z)w~cH)-MD@B2+^@!4EIFAku+5vOHR#sjX281e+c#fZqW_Dv(3|bXm|N^_2q^!tbK)OqT;2N-) z?Y2rovv#4eugY3npC5@~!MQa?UN z=P11Nm`Mc8AJhp03QyAu;nONH*rEsBn4a0VkT(&L43^3Xy=W?a*Zk=Z`LEP7CRe_qLL<`|%aAaTwzL53K~0U6pNVV>v2rS4K1 zkl-sNAU8w2Ok_J<%l|2uiXk}ztBZ&Qh}NUo9ctSgq+?R_%KJMmLd7Z~s5PMB!N#mM zAV_H?3M-dUGG~R3@W(n?qYsTpqrs5`v5frun|Jh>p=+MWGMAHqdlMU%0Yt zU`wAh&)v1>mk5qZR&HVsrsL}>^sO=dsY?1pcco)2Q7MbpJe$8HraqnO!;I41K8jF@ z_+wUxEqF~zMpT~hNj$!3$Mv6sBj|L+In^p~zAIlXH@d9x++9+r>FU}2A$(Q`>4o5A zx>7+uGoXSoZ_wiu`KAe))+yZ*HiE2qh$Lbi7_O?>orF?hB6~7yqRC;CirD=r)ypVC z_G-=w(gWLyGHJ4xS;?|G`GeVL&HRAIXBtZ;7Z-~!9^XCocVp))`re}bN6#Ah{K8*d z@NWwqS+L*y(dIqFzdrQ0L$9BAYxc9*8jAaWkG_oKC=|)(+>#6JhPQ&7zq@$F|(+FPH8%34)^4jL09|Q z#*2r$2@6PPL<~$CP_kYZMcji!mO#yz&VxBNvqvINmLNJ2X#0ZvQrXW)^MqrC2 z_>ggG9!2R$ClJBQ2YD?_9oiCQs(N2a=@BfxeL{YTlzL0!#R+O6pM3#ty^5zo8fTrO zQ+W|AEt<`W#vr!h%kMYtxl@EfVb9PDJXN!NQSbVLCe1!zrP~ZBoHOjTdo; z5END5QjUPf1@;oMLoBYIn9ydjr>Q$9B zW^Av-9``M5G>ZX*HV9ADxN0gOO50)E%kzsw?7GH_E3oT83uz4esal7iScO6)(qO%1 zKn3i_dx)L~o8=}l%*Xj7a=`piuOf+Cf3->?lYkBG%8?P>mAsng<(|oTxsFHPm^EHJ zm<>NPknn7<9n+_ED(x>I22q-AliQ2PL6X4;Bml?~QAilO^%;C|SDmZy2GpSNNBh8Z z?hw$TVV|iT_OAk(S}JX3q7Ba48t^1f2Q(~fRU6RZtsMZmW&R{FhXRWj+^UhaDhY8O z;Z4G$tRihGw_#Nq)Yp6S3lT&6ipGlv$wkF!UXxn(7xd$WAt!3hDcydHlDv*3w_`jR zoOsi?`i4fSwQs;5%P-I_g7yQA7Z1cz1~vx>EE(OBh!{%@E&cOE=mp>3BdwUs)4fSM zj@s3!7lndQ0Yf$3oRy#NRqTfwFD|bXK6}npBa8JgkRIW10~x*DL1SzUSYi1Vv5qi= ziR3{Ek>=LW_=|tdD%fv2TAK@S;s+JmIzYGcP#NgtrU_AD-q160Y9^ z@h=T`F(IH0P1c}C5Z1~ z&YsZe4_u9M(x{{cvKcv0#V$q+)$lGdaT3sw{0*DAljhC>t0_+B9$HF2uN`d>SM+} zcK?Rl2jZ`9yl^7IbJkFTcYD1U4J9KTO-Loye!?7bKQm|il~$AXnh3o**Gc&sRyAHY zpJed(+~h7~Ke0QQsCEXFxMDH%IN$ zky4U_O)krIFery$zWjHRI~^h7a)(A5RVU62RrnEmEV#?u!^-i_6p9*xgdbGgRTgLg zh>@dc2QhY`CwMMTeGUt6&9fZ@Lg)6Ui3ZI-iG!px7c#I3RwGEPQpo}Ui7FE;L8;z1 z#kdko!XtGWu3=K=$V!GB`|(vYF=+W)kXRC;X`hn&q|&<^FY}FRYD6}nDlSRr9jy{u z715d@#amYc`XMk9v2p^2ElXEDv=rsyMQNLfQ7!~1`BMTJQn>tfvI_2q8Lc!Vy8CTu zxc`de#q+poea#Ji()}d0=XE2OXKI2KX7-i)B!89z^p$BC*)5_B#CrP{) z+qDq(Hrrom8ChIvl|Kdop}oC?(DxR7&1sGWL?WV`*>GTfE0UW!zVY(kPzII;M`c_R z;8|G-ly6m|6j{6b4~Jj~W`?6DNnl95?+K8~cXRz(pc|ePd!+HwYj`Xny>c9wL_BL(ug`ej^iY1YTDq_C($do2hv*UZf8elV_>qWg;TTJu z9nqZ-VXZ_y^@8d_XDuW!kZ)?An(Kp}llL@U+7E--i{jCTECgKf(LSimo}@ep9x9u+ zV|nLJ|0gb2tpi2;u3{Up;?RIeYYm*3kFos6P9vbg)XllS#(8Grr6m$wP>O;?R4ozu z4D^o7Dohs1xV#;`7L6x}h*FsD@hie2c3t0yfY^2Y(qe>*C<>yOV{jPnxw+Vg1u8kDqB_s%DEvJV4`V8>P$^&Xkl+fcyYp!_R`#+pggqk z(xPM`-isJtvy!fm=9W^M&JyeV%}^y=s^k;7(M{6q-f(|QkCDe2q%uc>b9_#)Sr3f@ z8BAsqQo2s(#Y#-0k^z)ga|m=B6Jkj?vL<3MFRK{W{Dv_R_sC*rt=#ii3g7!CVbFUs zO=&I@`cH2cO&*%R19~=|(RgVD0*M|xI3b;MP|d7?%`<#Yjsq4`4YLt%h~x_Q0fLA&6sYCeqPqDrWCf z+=H@}*4P9MIFvNWh^X&zm;#yFb8>yUl5_N4nqNsZ7iB%< z;vlLOs*5PVb2CW}DTpykRMaIMYdEox#R(Q4+g7bXrxz?s3Jqjn z7z-3s6CPQ=tudjM)S<2NxSEp6-*D;F@vEYmrac{z8mI z|M;wCd0;VC>AMOvHw%)bWH$}De%Ax}x=tQGzb!paN{9Ab7A)^n-vgP%$pdnou)1+=mi@O`JTk?WsqVofdZ`9E+NzlpP_jW?23+5h|Hfyf z3>s#Le}H&R@BnVi1J;fF`psm{Sl}UrwPI$-dalEZW_W-+dbn9gCAqUbo;$D(I>ke` zSLw;~+8wGxm`Zz^Cop1QZK|bGAh`^T(K3X?aH7W=Vy4>5a-Vs+I?H;!EvhjXLQ+*2 z%m@ZynrNswiN_7vm&F{n6;PWOZW86A0a0xCKqt0i2p}#{OVZL#?V2Sl_WE2k>bw2q zNE^<|b=c|D!&z4DNm&X=1`QHGg<3jTS;{d7vI(Uh5kx_%d{{#{do^%s3Tc=TUP2tI zm^Hxrc0WmERNV|MG06Mre{`6q0~x854Q@ns$k#?BJ|}(R0Mt-gGC+$OyBI=EpMR$w^ho6Nr*FEW<%jK* zz?);3MT~VU2?k*N<*twAI_kCkK$iXAbCt*tx0kTeRtv(FeBxAtfvA#DLT*)`X>8z4 zGBY5~R?_{zx=RXB+8@e&r0N}6_Fv}^;1tUqb0ndN`cyDby+6U7tyryyunoXHB1;~} zs#DLATBy!T2($omPs(p7NbsIqL3JREyTryVJ@8?Hau(jM;x>`G1OTPls%G7&S(%1; zgpPqhnblvnta2d&&j4Z!ZND+sfvb~<{?BQxw)JUijkj48Vobu2twp9^3b51_W{~z} zLQ8*&jHkOeT`3eo=HMKcZSW+K@I%ON9ODNXgijjv*i0fq?Z*$TChy>l*Z9?S*RGGC zF-5>s@@>B}*SV=1j?c31%hJ?hcrsMYadC%W0Mt{w3n@CFPiTO;V4)hV-9p;d=3U!K zEkaDB4Zfn;2CSWZ-MfNKHyRkz$2I*tbH2a5;sLejaphB~>sj zl!!*H-e8*)A8qm_fuN6%%ZW8@FmkTnsp9BWT+Qx)CeeYrs0MpD4oeaNfC5Fd?OJtR zB$0*2zNYp9bcCdp8?ChL4cCvVC1z0gf(HD3OEGoZU zr!8~A*q#7iMUmPo7G>%QKCSkJ=Co||GypapbE|+RB)Ki>T&^8X|~P+4W29k7>yfAw6Y7LGmM3o{_WB#df&tZOAV(PTC`Hd9f|9=P9!2IQppwx^hV6Qn+Zk)&gkB{pF9NI7$9u5XXF zmp5MiF<{`Cs??;I1{!3T9uc7t_jVXA7UGow<3shBAUjsAmgW9*Xp0rS|K&Pza*d$+ zCpM>2Q4t9GF$^Z}P?#hzkb`;%#M8-j!YT_}Xq2IXr`*u^26=-5q>cnTP>d9-=B+BH zfmOV=Z_NFj&{G;O|Hy)HrrK*p@^SB5G0f`kqBkAmO+~m~-%gH#BbtD@P^j7J^@DB8+OWsBRF(Y+i&Ontdohg~Q2f zbA1JL)7r+%t>>{;t*YTtJ%f^2V@{-%4L^WKA*}SG5jbVZJt_-<1*e3FQCWD!cBaZc z12EKVj0xM-ND6k`K-ogOUJ8{Gfj0?=Zs4}YwrPf6tt>`S(G6z`pCmvFMCvH`0Zkfe&|-SuTFdRL z8xJ>@T(J1Zi|-x(z42Seer@bMW3OBE+0hqAKR0^8$PY(0jl5~$&V?5)_@f1DJ~98V z=3n1@zWLDb3&T$gUo-T-hg$P~Jnw;dOS8Ysp3EN3F3;9x^BY?mk6rP#`EO{g>Kw{f z<}b=da>W7?Q+!Ur3g=)a8#jJ*bLSBG>zUceOljifH3|m=}rq)s`D%e*HgR$hdiK(sIgfK20v%s0gr z>e~IwKA?p$WbPi8_Go9Bca|>An&00ii%=xztHMiu*p%G1xsdoVt3)rf^f}j8%Tm;> z5k%>qdPKnW1k)`>GX^&9Z~9nACvh;RZT^{D!3wd+L86t+oIp>hHWCLOs_=h3lDHxs zfzT?)#3Q9nm{;`A^HHglVg}ivLkU#bviQFJ)YTn-YhhK^Bu19Px5KsVOC4CQ9=EE5 zO-Q0&N>&K>Q|iU$N(Zp}){Z`>&_>ALgC+whtYF158=^&Euuq}%tM=)7fY5yV?gye z>eqL4%KGFnS*!W`kfJCP5QJ6fQ4LjzKCkhkO&CB1PBAL1_qGAChCQQ~YTuNU%B|Sd zqj6a+sX^Js97=mAkB@$VVAUQX8gj1x;*L&9Z||Qqf6tV8!s>xA8cM!yb<;bviY`dp zK;LcrhVF15WmVwl!yTV-eplA~T{uODHk48bfW_#elXa@x^E^$TFnUV^HA7v*E!Mw+ z(x;$vABdX>NDy#qM~9oY&&ispt%x$F)uhk zTf5qeJ36kM$#wI$c>olvhS6Y8XALaisSrq-;iG4im&-`;76r{J=EmP}E$+oA zDgv$oDY-Pdl-=ot14ufrY+TwmwbVY-4UBmX2td^GSnXO6gGBh>xq)&zm|r zW4nDu*8DA+bq*)UB%q`2JKW%uMlwF_*iEvS=tt6Hn#`o5a4{!$jziXe- z?1$7)A{0IF(XVKk$NK0cIh_c72AB7FLby=wy16qXd7POwe*?0d{&7nX0?ue$qcVHJ z&?i|7Nrqqk>^P>bXQ_w{K6Vf=TV@0nPpOag0iAg=r?px0*9W?QN-rSG7vYc~rs^$? zc6cB1&Eu}HA_9}>cg}QF7M7fz*{a2^VMZ>=FWbxPh()#t|Lv@!Zx6RyS@YK@jh~Ae z`GF2jj8-f4JnfiB@sURw#Tdi`w(u}(DPbq$|JM;zC=#>dTmjmX+54_BQSqBhjYsD7 zvQ9%p9F#SGwG4lBS!_}whVZCwEmZ1pi|jk(p*6f&e~NTAWa2Q_9glUGWdKU zdXilA(FEj86UbL`_wt;vMAd>jAI5Canp|xHWpGu)i5O);G_9~rCi&KMtE;fL)~-A9 zN5uPuS@S#Ihd?`ie=`V5lM7-5gr(N?kE}~(t6_@Bda$IH{AhZIR3RaQE?Q-l@l53U z{_Z3J{V$nULu4zUh$_sh6w4_jxdDPF3fEljYGtJ4S{ zEM{8jRXxQ`gJkxmHL%c{<3G6r<#ZD6u2!yZ=(ZounjE@H)26cD6+Nh7{?S8C>iu#N zazIrA^#qD(!cpg~Vj~Bfcn78Bq6S#48`z(ilB8GW`uc2pB5O*8$N;93kWGzjg2fs0 z13dt7Eo*@-^1eeg$A5AB;qkYRFB$uP z#(r+>?y+@aix&OHqSm5w77dR+G5W@le;N7JkuQwg&ZhidUig`XCoK501y_+C_{#kE z%s;OA+s)gWt;2^uGknAF1;cB}6a4Pbr-t4#Z^yik&O0LeLH6nF{A^+4JM=r}e``DM zWGZ`2HvX8=M#9{o{Q(*h&{*h22BM#xi#pdyn@h6sFIYM#ER{jJoR9iVDq7%Lih*%S znv4*#0U`8+ge=FYI_L)uL?t zXOJ)35l}g+1y*Sq%~_qK$262`GMm73EBEmc2-Ur^^VW{GMKP%w|6IxVA$=yG{}zKW zvjJ&P8VA>$j8YlON3%RCCvMyKM$1=`fAUifd7O+ z%E^Xl=ZcOu4jr0}e-=_bN`~FWE)EoAPfszc+0km@rg9s0rUBFzYAqPYzJ*>pYdcqk zwm(CW=>!|FNX3Xg2(;{ISkTsxd@dZpU0D1PNG?UBWlx64C&gIDE-faA*q?Tv| zKB(vAtvSs=x6gJ|Dyefq=Q66^mW_Xs`G=wwIYeDoQ*Z|sR)las{FR-fI+s%Gs%%_` z-byMMgWq`&{`t2$VTM4J&g(muPs{E5lbqz>*xf=5ub=V8sgD*%)j9TL^Y zscAPhh64`bfz!+ho%87bwru1Np_X>IOB9Us zDMv7%H(PBW8q#?{N=hd4zK-5&ZeNp)`~l+8juF5?%()*?iPs?I-az^yEt@Oo0XXzZ ziuC0{+cZf{u?k_8!Z)C#{4-QO(s{G``$#tO`$j$;s4XZ$D^4ecz=T25FlOm7hoy)k zUmOmf$LyhUGuA@WDW`{a;5Ehr2-1Q9C8Z+-%HoU09}{pxNAD50*JmTY2mkOHy7-1> zq$!b(U#sd*XgiYlloIni+YYJMPpoyd!!oGI5nn@>6{6X zWZ?hHKtXnEr2ww3;CH`OVO{5qRA6D?w`90=%kmHg+^QnKWMvwvrryvwgBllSBfsh1 zD_S-{GI|`K<&@6p6uc)JX?>Tr+gNMlO3YN+heIX%y!Zs&NlHrPd01C7!YpKbTgQj`ve5GDyxCi+M`EsKNMY9f-_}8OtRECJAqK7# z%;V&9OkcXQz;EiDB1NB(jriAKO2c+gi#On~qfCF&i*I_oViMTZQ^z43EuqH_YooRf|Gl9_;09XO3) zXgf?uXB`q0E+Z#zdk``KnH0<2Ca19 zH*8lXlV-C>LjCRtV>`HWB%Pg-jc8|N5~dKoi!fpQfzBFrb7wZPOKeHA*s`9`qbx}r z&$^3IK_g#S6qK1_A%#HJ`_<4T@fWH_)*2SX4iuW`K_RT}tcI>jvXQS+-l@Odbij8S z^z=zfGe|5djw|gzW4fM^uBYoth0}DUqf>RI!Bf)nlhgh6y3*cB>Hdl7_X+8Ge7YW|EA`h6HCq1T zSlyuGV^V>mQ-QVV{!!`vk?H=Lbboca{{~$la8>$!gsu>Jc=~;quCza)E9F+E`-cv- zqTnIv#tL1jcyPKNlD-A49*Vm`}2c+xk(*0$+(!p!fb^mmIjjl*$sjdr}JNMHS z2`p*p4;o&a3XG@gSh_As&qsBIu#t3sVY)6z*ZJw%OxIyuDLc^<(M!u&(o(-G|ck!F2s-x_%^GKdkF$v-^Oqi<;dJ={nNv-k+`?)OBIA zd!Mcgn%xiR+HAJE_of2x*L8lgdylRZe4no4&Fe_5} z->ob4?nw7<*A;?pOV4jj&u`I{_HOp`RKO<4Z5)7b;{Xyj4zze+AmD~^fC9z=uEqiG8wa==2e=vscy1h^ ze!_vSae(K>0j|aYRA(HZym5f5ae%9Epe0cX2qa4502Pe`G;AE;e!_vSae(Iu2fD@q zej5kq$T+}n;{fd%2WZDQKs&|(ej5jP4i4b|`~eWyXp93ikZ_=D9H4@60Np1X=o$wo zXB^AUE=`HjRU7OyT$>Y8wa>=9H2wv09WGx_xH2=LD7<=f`kLzgacjU00k2cbd3Wv zoN%CP9N@WefOd=nbdYeMYaHOYae({A0j|aYu0;26KHMx!2v_64yk?PbpfC>bJK;c) zaG*#yP#6b>nuT%T(q>^C7-<&9f%(nCIIyr;7zg-m99YyWi~|dsts((IVH|*fgad_f zU_rAm4$z=+fCh{MT#W-XY#eAd3*!LyjRX8P4$z))00NBzxQw{d{q#sPjO94L$f+&2y^ZWhJ?`Y{f`ka3^| zB?bbn#sL~I4p6{2Kn3Ff4H*ZJh;e}D#sRLz0qPqEmNX0F0OgGXT#W-TVjQ4-;{fG_ z0~-Ge0Rd23oRw_BIDiNf4iv@#ej5iMz&Jod#sTUZ2N1n+0D_DIh~7BBed7T2j04;^ z4j>V50RN|9fIygB7zbX{EQ|x(Hx6*$I6#Gj1BG#bj*J5kVjSRV9H6{$fcnM(?i&aA zZ5-fg9DtCCH2xPW6X%EON&({l4H*Xzp>co;#sQui2jI*&zyDC=w17OYL?P*e?-a9N>4tfxf1PwPk#(|+`$2h=m<3O|7F%EFwI55B2F%IxN;XubYKsn<8 z_Y)3ui~|dr9peD^jRQl?R>wfV4dVb;;{Xj92e=vsxEcrO&^SOt#sLU14$NzIj05~O z4$zKqfNR2mj&Wd7vtt}s*z6bw=Czs~0|6C{15~&vY@lNt7-@El19WU0;J0xAf{X*y zGY;_FIKb67z|}Yap~eAz8wa==2dHlxXf0`W34|h+EymMsj!WS0)=E6@d zymsO73+FBP^94H=JiK5b?^i!P|BLhAJ^$4Ci<;Y;lg+m`4;udG;T~Tq`|R*d!zT?l zhjtI`9QxqU#Y3;1_viDr&U@dy;p|)42eS2ze}W?Y@3E6_p0~DjbfGEYwb|0o#mu$b zq0~MzxPEqyi65&Q{L5_aj6!q82ePFatYToAx}E{E5XJy)4gmEvAnw5I0N1l-OG%$|)dBt$abKwx2^NxwFCQae}jpDD{vsYdUiR z6)6K!XbU=meAEdIB~^AriUb|6+s-BW9okEIMa?nJ1k$!aXS{~ym-ff!{Eo4#Y+dmh%A&sw`iQ+%s3!SCX(uTocE7+d zrlJ}L7E7sdVzv}fV-VhcY$HYvkFF{9i>}+)yH|l&Vz5rNTI)r=&84$Iy+;o&mQWU^ zKS5_Y{alMq^rFwnZpKxL#XLSMTdE|bWTKTV=;|ps&W)#gkDgkLQv`4SI2F`Oz={}P zbA{e4<%N8#TV$tWR*)t)xAdluEXJsNMYi-2>gr3}+8K>whe!*-Q&Dc?8D&=%i-MGo z!LkyiEJC_0KiP+%BDWRZ=X!3o^kMq*zA5TLtd@ARQXp%MWno0-32a?K7x%>bPP7ZQqnF{b+QJ00ygga~5emumu4_D;9BZF`p`^@FQwk$cC^; zTx#1uUe2s4np$GGC|mmB&>ZCPL@9YBBeqzOksquohG~a#4c!-aul=p5di$eA#T8=I^Rw3YFVTYiMqVSxqH~Fa9V=R(euQFd#?YHH{Ze#Lzv3UM?BFJtS>O2>mF~*M zwHiXDQVz*QDCeQbqGf*rzqG{c^=kFDw(~F***nGJe80DWZATK@rZO$(o!ohd(syU$ ztblNrPEddw)gNdHKg6Y2AeKxSl<+I>t~|Q)AQi98#=lv1(-P|>s}EZIPmNGsLXcNL z){&i$QvQl;T=*P&j0Q_0Wy<1PDtlVzBb2=_8y85)foVd64jJ`Go z&Iw319^LtH5XRyK#E4ju^kxPPy1Z;jLg3NVS)B(c{zx`nEqkEl=*!x`XALddYhd@F zDRrLYNtaXScUnHGg3VR(Ity`HL{|+tSC1p@qoHjgP7ST;+z%<2W#eBhw_D!ynb@v2 zYC)41eGct>kfLX1<9bg*@rUfrWYw^EROdd5u$f1np!Y)w$(Ec~Jk*7c?RLO-vT?fMC&n~d#P|;Hm*06*y2OqC?Bz6n^8kt@nRc2N^(;tcHU2g zJF@Yv$Qkg{&^uhL>}gO(;A%`;+OUQbJNHDbf^O6c6*w2@ z8k_h!5HkfA)};UYKg z5p3~8AaPp*EJO-m#kWR->50yJA?BQHoI`pg9(A?T3w-=g^g)>eJ9km$#B6*ALjJ1m zs1eTi(4U8Q-owN5v+?anhepF489YUg7X0{l4d{Dh=T3^@VQ?qfwo>0`g2{O`@>xcL z>A(1eV>|DrEczyX6~R(SVU|KtC84b(s?c3W{deBbxq}K9XX8&m5w4@=S%?j9(19F6 z)m&t}x1*zddEsdMaeUtaJ*A8#fdx`V;nLGm!8a0N;+Xo$_Xemk@zJFk=vSw+n>x4A zS&JP2E`n zcOBI#nTUxrVBQ0s9ido!(%W|*mXy$)RV|!!CZ*U6v*-M5N?_7Mt z_>1G88(%dx!$#<}i>BBFJu-Ub=xM7)e!w2+n-=~9d!RqR@cacYF8KC>hZekf!T9{2 zng8DA-!wnf9Ai%ZmZ4u9T0QUg=Y4qIG1(ty@5)9R+w~Rlb6d9*{>0Z=+48s0JF1rC z5e-!8PswSJU~RnhYX=sRWCP*Yi1tX}hI0-N&~!X6XRa*tQ7^Wmm zQr%>6(wY~%i8%zi8fjb zBhsbYG+E@KcIe>=u}gO5fvLpg&j(GV{EErIfth4!sV z-sjd0m2wt!Nw|j!!<2QNN5ClDYOe`SRn31CXd^eC>jL6jSLpLi?RDAmH;V#KX%MSh z6r(r@?IOBa?^LI~RuZaSb@jUr$i2*i1J3i%*7unY7v7e7SGN2dIK|<}cW9hi=^nTU zgy2xGpMad&T%axR&T$}8^&g6po}dYFtEx>{{+41xq019Vc8Gy*Kh3m{D70I3^89Ss zheVh9@Ed0ynasUflMLjn9)YAqZ}wRU9~!*`k0l7^rd>uZUpdF+JBTjs%cz?##c*7udgz1}w zBp54}P(k)j+t?t|bT`)*-Whvhw(Ns2Vrfd&o@_}xv|_60bkLN`n2}2a;i{e;#e70< zI&6v7TZKMV#0xmf?xQH*{-CWdGqib(M38njq6aEs@FGmRk78q$f1=SK*qglM&;les z0vv#jpg^vh2;sKP$CeGR?0-b#3hwpwT+>j|_p4YwOeUsU=jLyxu<9HPF>)=OA+XqWs^CzA74fl3(8 zl%;}OJOip%7w0hWT#_w&KldW!+#=QWHbklCoh19@zGd8m75b%Ki9*{F)68;R6zsM$Zb=pz(x48)|RMmc38mJNx{o zLAaV)>{ZdNYoUJ)%KU$Q@g{0P?cFL$6)QG&mbwAQ4}?;5L6n}mH_-6R;!F?4%ibFy zwPdF4MT!pehM=$vUL?#oSKnbn{n5~J=CndPW$(+D-8I;;hRWPJtZYsgk-R{UB&nIV z75YXC0sXS~P?D!`y`q1KQ7$H{n^nvaDsLTHwGzT5g+GmQK(_48QdtamF7Or0 zZUd~4)rf>%UqZ?y?DOTj)~Z4qZZFD~y&DPG7hK=cfg8OwXPuo?oJzSnvt@Tk#nQUv zXT?GR=o>+pwc@5+?LCEe$zqSUOTyIsN)kO(ZB_c*x)j%p2o#Y0qlwvS)#kl~nR6d` zT&PIYc3DWgjRrJpWf+l8s(8x-@0OrjbRcbok2B{M>!qWiY}u_0u8cmQGmo?Z(F1`< zOrf9TZNHEoInq!Gb9zB>l3E$bmfaF_EtS*2ctnqsAYo9;_tB`a@eZqi?UK z(Na4mnMZNMRC=E9gpyPV2PB3NiW0bLTtG>UC@-$Gk5W&nq6mqxJ_^UkU8B$XLfduk zYGupbh0q5vL^6hKTHYb~1$ZE3sf+)=xj0U8&a!1UCe-3CqjnN^Vwfx?cEnsmQcuYf z4768CZK7BwshvgtH^44#M*=7n!8Ld+VQ~NpY0UNK$@B7RGO;45FgC zkKhtUr46CNG4iW&nX0*!Ee$d#0DHp5H76_y)VSlNlrszM5oR>GzQoRDlp5A!xT~9& zc&RCVr^C4F7b8w9j-fut_09_5QCJd9NUUW6nd|fdw)n{r+0?_u(Gv9i*|O`z78POG zZsi{q4@o(-BWWaoPgBY=ppZg@bek|lQiY5>qC5U3(ZhLGbY)Yl@}X3h#x~tutX219 zPk#Yi)};E?zO;fG(xz8w!1Kib0%deDNg1@QltNhAPodl`}R~M^A_o{5^&&pM82q+Sxr)W5z1ReW=iQDY{zQIe@wR5V& zNa|=4Ju*GLc!QQN?$4G!)&sA2o>qX=K@36|UCAZ#I^Ef~Eaki!bHmvn$5O z#&(Wfxagl2eRk2si$+Jcjm{r=Y~=KXf4^`eA07M41>an-al!e_?0ib98Ew-3!$oPmo%5O0(9aL;}^ zUf2WW;yG>UR?OmAU_v;MRaImRHo@}>9dFHs{}rc8O!d&JMzCoP_mcIH<>TGA@H9&h zyPFpCI{DW@>@|hc)~mA>r$IpqT#7PK4024b1;{r4!B=028hde+>7CMc%eNVRH6p>V7 zn1c|=J>iScvuadDkR`HiDTt@rs)UN|wSn0LCgRGOn+lyHOm=a_$-%xfr#OocPohte zCWyd~dcALR&-kXuV)lcW)*vDqVX${fq0Bqnt%v25LKlfzQf3PFRM`pb@ms!E#Gqj{T|ZTM0T%SS6A@w1D& zky|TUah!>*KHVBvDreJloqBm$2&lroXgcoLCQmMu(!V=fv5v>^Lm)0MuA~KTU@k7( zq7e_z${V}p#f6R#m2(^$RWWGT;w|`)v~-JCOsg%v{-$J(%wh-9;Sar~g$gvFyS4R& zRtE0MRve?oBtbHUP{U)Jy*B1RnJRh6&^xoP_{KtO1s};)94$rb#Yr#nsdtGAnHS<+ zR*}**Vfl&_XlY%c<%1iu6>I60H<+jt?Pyq^ET58nPjORUbvH-l%$hFO7Q+B*Qy!`r# zeprS?iHFlFGGQR|fCMq!KH;Z7{5fBQ*PVpuS zfNT)=cwq^%Mg&YcDhGh*q=fc?g;qW$;pSQxkp!SeS|;dGJL%nfECjjBs%tCYMyxKV zGj)&ACY!8^LX7KKaq@8d=)zM0o#z34zc}fdD0wleDO7%AvJ})NAmS5)ftv-KMorl=HMj+ehz}!pu`eevY(pj!0 z0G|`X62PTFrPP6ON{$8{W0&W}vd@7hgT2%-E&d-%t&$}sA@kXRHQID{1;5D#+l|6U z%RG`Tzog%FSPz|6s|Th+>mG<`k2Q{nqfx{*Q3e*UF(`CX!=qOsd4lI+FZMCTEuqzYECWrZV07dBB*zVq9SdxG6?(d89?~0`WO{Ni%+>N zfBD^MDFy%4MOe?spIcrs_b|L*g1Q<~-Gd9s?8F4Z@*n>6$Row&;^!mT@{9WX1a;b& zpc9$`y$}F2XgiovDBQzOL$l8E*UGCFmn;VsisV_zuctN1P;iADMTv(-P2%3v=rqQD zJoblU-x&Mi*h6F2jh#65+C@KHbl0~RJ-X58Xp2U*vUk(4(@GlO(fB2l? z{f7Q>=$WBU483LOka_=m-jgihUOn%m>^HN|XE$euHU3AVqlMg6t<$=CVgJ7Dh?fx( zVH_#Y5U^Cb9r?zx6>S;V_w|qXm#RvussnopQ`NsPE6U%~Rl2WzZ8q^1J(V$RVK%%0 z85!u)0B#PeFTJ}3jT9QJ71YDSbc#%YX$7_jN>;ABo^H{yGl^`>=qht}S~hVZmTf>t zGwkSe`p~X2b7y7~7xakmWKqb9zRG%beo|L&>ED@6oS#Z4J7Ue+pOl=Mp>2~7udOm2 zU~y^Jd9&r&#CcJ?G6XdvvFsx^vJIX9Hr|>?-gXwR*NT;+f|YZ5u&XQ#%CZm)f#03g zUA?^Dx+I%;GXh0~$OHMaaRM9TANiDYNYVA(H59!on>a^OQ7_0>h3gzk&Aw`C(}hmQ zpE|j#_wet|CeBXvQj?LY#xFhHa()m|05C{~HmSL)OOlD@jft}&Dio#^JjQmzL>S{# z?&z-GwZ}f*ghUg9Xde4jnX=;K%BCXVI<~8n(e>HHnN@MZ4IRZr9?z7!ajB@>5iA_l zRkG-+Y~qd7U}&MZLL-iSfY4;FN+fHdIJK^;cPehoCeDx}QXI3Ftg2czDBagdlyc~) z!&+UvS8;AOak>RdONzQSsEVPL0leIGa96KSoRUqPR!f}H-mR=gR&@{MF~T}E>eS0F zTacMToX2StgIm=-B+4l(V&f%|J9wljjX8aGOm_tZugfM*h7vF-xiXM1el`dwuqTgz zbYFJP?CK?j2eXOw;!V&KUZcOwOPM?r_d^kgO*2SbnWF}ky@A1lXaELJqB# zy>W0(HgSA!Gt+%4OBI^e_>c+klco;r>V1O~vx(y*0(B?|6Exu-8!zbUg!)HEvWa!F zFdm}~MV+=!pJ9R615I^Ls2w6lB(zrUfM6dF#6sF0^)*!~dUmi>sm*WX9OEDI(SR_J zr-XjHtDFMiL#KqOKan$4Wrj&gI1zNwsaJlrks$41NEI9)MTPA&0c+-ogN&$SIY>Ft z%B$+g{cb9^*%pL)sVN#46m!(xKr^;q?#pxXo+Kb~J2-MX3PjN$^$-BG#YC|VH$s=7 zF?IcjGV#epjRn=2La@%le+VQt^2lVln~WCOP~E#mW<5xgKiNW^Jiyf;6%i`L@y-s* zNW;!Rai?ZNm1*>11X3u&Mi$I_qN~?TFvw$VL+TxU$#o5l&&UN1?&@6=JmeS)NAEf$ zbFhaT*44Wuz{t^75|wkrs5D*mo5bA_UApuaaDoO&lp-T$u!2S+~C2>}i_$$NROcRhH1m^eW$ z6eA#!s9pb%F0Xg6`Ez2z((^Py(qmEwZA9CrcJ)HXz1f8Nb-Gm{LTbxXEsUdl0%4GP zviAw~`AOZwy7Q@vo(~NMss$7rUEZz*j`AyqcbgPKe1}NSXb3owy+H(7dEClP9o*H+ z9W6|1g=D54)W$b>ZZePV4)OfjY~o;k8{Mi_WGYr0jgiEO;zxD$J_ZBjL3m!3Bc=dN zdQnJ~y){g19NX0!8H~dR#zJ-t;^E#VB)y*ovO({zqq^}<#>8>}AeITCS7xj&rSYdG z(NVb2I~maOdI|^l!yd+)236q%s4M_tx_5A)S2B>p0ko(6aPnj-rlj=Y#UngMny>R? zj8#Hpk1ck7R$u{N7{4HPRdkhUe|i9#<(031sZ5rh%O(-eP*L8aFq zY-&;Q5yq-dl0JT8{VrK*9~jd{29L)IYhXqWtly|r8^J??!ca&~kVz-F3=o{sxaLO% za0MlvHM;4eBvDtgNF$ae4Zq1!@%;%Wr)Mv}d>2U+rd$8B&Z;S+UP^p=gh`PDF~VvK#7?kHd; z4O51F2-fMMg`PIUNk2#cLjWP^&y`>+mbmL~EisN@^Nw=)(ZEngJC5+&4IN;pFstxb zLwSG!-YnN)evU-^VMrQ@C2GC5}|}2aR}NOx$X&+(3fU%A%(A ztt4h#>Q-lxBZZj%&oq|&acfC;$)}dwzT^!{Mi=)Me_`>v7N0!+@8f?m{`2D>96xt_ z<=8)ueSfSVJ#g{ZQDgHLm5VxyHZHn((Q#x4emMG-(T|P3ZFF?xyCd&f_}2>`TX@cb zzgh5w1!r-T^(W@9Z~jH|OU>hke=z)s;q!+6dFZ!T=AF;?u2%ECt50TEvypp-90V_8 zuRh`JhqP|$dcpFHY|X!x+hrxwKUK0ErlT7SGNTk|iH zdqgw{8#Uvh0WDC>n3#+RQ**s5yZ34~Kas8ZXN;m+^P|OW%t>IVKN;zfvig=&si%hq5*QB-v4ue|*lMKd|6C6*7cLR34A9#>-wBgfH%TL300W z&5M-^w4N$okU}&ogF7%?QkLxF0dmrWHK7CF?FpjV<6W&rP9Bi0`A4bDQglS0px?`G zAT$}0FPAOAL+-ZS77gN)o0D^Eyi_QRFqpg0ifq`*6M}?v6_hc=RHFAE#EVdws~%&E zYLzo@>G~BV4B#KQF|fqKER3A1Uo+B*3qK`5F(t&bv1oc_Q2;<{>%Q)LrO2DIHGj{L zLJ@@_?3bw!*fuCFGSnOM6av`Y0q)sdoAP)WC`TWzxv`Y-Z&Q~TDx{f#cN);tKIZT&zr1ZZ`0G~ zwR8k(YV5SG)<5sd*8DHEf_#;yo1+eoWONZU844}1>$>k2A?va=KQbWH&L0F z$hSF*0j*8ETGKtaw99t?=itw-F33}tW3X>GOiB-Bc{REe6vQaL3a zCHK~vEOC!Yl7u_0ds|5N?;y`!W?Mk`9=E+cfG1eAvKt81AMC5$Mcb<26*g=DD&4msk*xZ3bl76l=(38bV!j zs$_u|WYp9xT`!}am96<3HD$i&Q+Xs_hBtu7*D<~Rz$LB88^vhKh|h%~l|Z%$ciz$U za_TYJn!koS+>+K~=|$?-Pp(*-A|;fkk!70Q%ykA!-KidNQV^)o*D-ZDb9LA6jIGMn z{E(-5sZG9A<~FRfYfyFj2~HO?iX^Z^59&{b0J>8axm@18(cgJlk*(PcT^N~&&}^Kc zgyzFEHMt|TJs_Ht4i^zoq`+sqK3dLP)Ac)JtFtx#L$Y=)7|GS2d^WXi29S@wcV#5l zD-7@mZ$&j!zN+hG&%?7df2D?@lxClSnfjY9#!4tbW$IXXq>)h(Ge!Lg_Ry+q1a{SG z>(Z{*LH~co&I8WwqFn!(-PvqZOGtbhSYgqXw-Bj&HU7%_Syq8AZ0 zHasRF35kk|ii%zk0%Ws$TIh&~*hOsE&e{Qc@4fuL&ogs28^Ejj3D2JSz0=-#=b4#z z<~P5?!-5BMvQ$aC`{aPaq+z5@L=dQ3M&1faMr@^=!mxr|&I?Dp`c%WI%v7>n@ zIh9^R3RU-IDE!oow)?0M>B|*TmgNmKhD`~yTJwtfTqY5&Ho;K=?*ylcJZ~iLi!Qi- zbf{pn6hu~xl>~ZL1QhGMUAQGPe&-OR~@%&oX%*X0& z23}%RU7D>=L@aG|>(L`mMd-xPaR_zM3W|=bM*7D4#Qg|#jx0|_e9UNa6gTn&XNXKc z(9E5Nk?`p5<44Xw9E*8$V|Xl2HX+1j7%T-uxtKfiV=UR*G^Z0`Rp@vW1&~2rVqF8< z=@`C*L<=D#=ZriBNz^`byt14cT~_X|#=ee`WymZK9W%zK#mnd^F|3;RiX}LEWGRA6 zLdSFj+1!G zupqjRwifj+)W*;;1vMmXS{LecqC+W%Y=*Qt$aDG8ot-16q|!{L_G2kKVQ*b3o~8y~ z?2qm&jObmaO`&5F71Egt6+6A>0Ff6Ax1}NdqjuE}hgaX@1jqy4$&{jGE zD%9ExUkB;z>>D{5IeP6xVi8q(qg*z_Tsoqp#W81Is7#{JeW#4*t))`vXhSjL-#rhF zOn9@sNZZ4!*WA<(zGXM%gpm_TVtwd{;|<}FdyUrX`%8z9JdqgPp`$f7C8fm(FJUsg z|HKi!Y4nQF(L%KC;|nAg6!oLJqdGUdfBuNxKf-(ispUOn>B6%?-zAdqc+rg7fDd_0$jNjOZnyQ^PU;9J7fA!#RXu)%sS76Gr-wSs#x12W^1oS|t@_ zdXTfyQo-nkgs~khH^7+_?wL@U`KFo08K0Z6amJHo96tSb(?36bc>3_qDxm%1fp^b@I@K9w379|Dc~kcE+S ztEB`jl+w~{OrJ$;>|Iz^&sh_?KB*^!u%e~Oq4B&H<7^j3W!a<4>MbXRu20a8W8WH* z^!COzN^DVC-DGX(`Z$HgZkmUS(ntz>$}kuiG^Fn>70T)xn?l#eMwvaSqc!=JRhsNT zrSKT6aFL=*(O*`7I4^X4l-e*L(9@fPg0!RM{J3a)8k)a5;wfcyi&E(N2zA3(Ixv{sqtnPD8=(^iAbV5wPW!SpjiXx{;gWJkZzsBC9 z%jz_!?Zc$f+|AWzNfd{ZWso8;HEvNdJ9u&gs7YeFZ^Lu>>4KQNq8G}Y8|McK_sWt{@q z96J9rdP$>sXCCaWSQn4lu^~6iHYz|nsL}b4hF^y$FP#n{uRs3#yq1)8G~}Yt`3I>s zrAJ$$Glu{4lVQr%MJ^543OO3Jc@D!>(^(O+`~APquB)u0A{#>I@066vJ+?h9GhrVu zafB{F)B%08aeApdM~a1zuL+KZmpO6aq^M7B+6Us+unS`O^plTZ0wG5pUH<=;@q^qLkJXb!ptivcP zLg!BpE@F;MtK6DK-GybHM?qIV`8UPjrMDe($~u>FYUuni29uX7s-^lngPJ}Kv;Tzh zG zxiQpV)}e&+Lgx=iOZCxYO+Ar(y*4Bvt<1^R$1ubRdLMM?q_U1H(6PV&$Ptqnj#=&y zwuwQe&l0Nr7nXIB0hN94->3|YJ-hs9*-Cfle2}t@8v#s;Dy)2c^3hOCIaS1x3kP!RLg#ldzvO*3ndswPW9QSdM?{L7Ro1D9lSAjX za}%XVqA`s!42hj(9g(24-=aWc+M)rtGZ76TuUVCbV>E(#7ngNhg5LYh1GP&-XQD@S zkgT-rqdOI2VOa+#sNgpqTEU0c@3^uKQ=An#zmC0@l8*;dY_64Q78FV`ZQSBD9j$=Z zM$;e)^EDWR$Tv;78fnXxLUfdMkYai0Y>X8fDY-_JCSjOXLjSWeMuQ1cLfX3l!&L6o5Ia=fM7+Z39H-<@DlLBl}#w(!3qKFTIj zD&-^aw&v#hA~G`D4vn;F#IO0T2n+tvG{9j|TH|W#TU|bBc3kdO6m0JE4;p>P0#NFh zH`B{V+!WfV_1|ko{3bA)PJdV9&K_kLCK2o{H3o+p*}&Z5nx-OHCgD^X`@e*=bQ6h44vlD^M%qZ5rP&mDH8MRPLeT9i6>$>KHM8=>3UaTb z4*3AqO!(-8nV*{Z?wOlr9><++-<@&ij4Nksm~s5{+oms=cHgw)r%jsr;M5OIExl*z z%criKI)BPPr+jnDyQaKoO8=C}lkcB=+vLrYmrZ{Bq#sVYW77L4y`wBOnO zs`fM6pD^)f6K|b(*~I5hT-^5OwtL#jZEt8>6aNvv$ZhQ}j8Bd;T5oK9ck9brPj7vE z%kNsg+%nR#wdLHF(_7}Xw1x-6N5XYscUT{aVfut0Px$EDPCdLducDE2St!2K4CjiW zS5%L&>9$TeMRv}qXnZ_16yK4gWK68ORAbql)wZCbQ+}&M@$JbRn?C5g@KJ&!rhDATh6w@F#J75wYj%u7E$-^od+y(+#axXSrL6Q-oq8*IDL+)r1O2nH$B8WZMf2%h9`9QM#}2`LM2EVgX6ezw(eOK zjY}toqWrd6QV+|Qi5NoI+|JQmkFRJbIxZA<(f622OYc>5w(Ho6MxGU+D0i;xl3~G0 z)oS}{jTCC2+a;)>2SH85tv!DkoQ)B8dBxLLi?@yLI;x^^hbn7iOr=pFC=0b`D_~Yd zqYS0peo%&5AL{@On+zvZG}K^e9{QB9D=MQJ=DEgOlJnc)Nlm*JRW$aj4aIG_bi|Rd zsCcMNvk6Qn7jRBRLl8RMs^aMwg{tZl$q-@o?hh+#_`xE`jE46~PDTsT+4cX*uh$gr z_)sW)0&^h=G-_vz223)sbJ}f$xF1|h5;{1rcQ!WYR^%8hlHT9KYkLdps=bQQUA~BH z&j|&4ppu@m3TYJ$KdE_$OY`Y<5hBeiYrI+(3LjJIIDav`ArJC01xe`Nv1N@~D?;I; zvP8<&Y#TRsvLp+hT-Lz#>QMLyx!7?@z2$sHGl`JZiDc(`I}VxLq0-B#tYc~mHT_#qZJwz18Ssp6_qG@{CaMW^<+HL%~- zEE!Kov$L$Ba9t?ebx;8`BUXDnvJ!SZ zK(;Rw?j-5b_zvT*+IN;;o^rOoi6Y;izNq-k3v*R-R6<(&M|bs>FGTzWp>PK!%hr>} ztP~6FN!g0vKIg93Q+^q-He!EzPbiNwHsWSxI?(~SI1NlxEWGQu@&&{?D-^Kr#;_ZZ zKFb_aD$&wPcE-$t;t@2r{8FS(3k5l!Qu&93v^cd=k!a{rPx<^*l3NK*Za>vd&s<=2 zEBz_P&YiML|o)xCo($J|v zn$u66&m7X*k*-ot`9-OWH#B7L>R`=^P(Kj}LxBpNo8HvBqBPL}E>o@Z02OaaYOi_{>7Bg&f)?+t|-Iaa75 zY@)RpEDy5jmY>fvdJq?j zP?i1CN-j9j54OY3@>MTDmi4UuanlbFue{3VZwjU-i ziw~#KXL0!)$VG2&lBaK2DJ*R zZyxzbp9HuO{?kdbUB{L;kOI_7wNn?Nf2mGQ-y`j=ErMMg<>w%XRbEeFQpY8RCmZCD zyA`IgsC+hZFz9uXqpTj6&09BUeJNw2I6A3TYwYVT{}<8>8rLQp(4*N6jr>w8EkzI1 zl7x1UFF}5Ca#){o!05+hRCTQ<{8FW2C13u&CzpL7gidk|ZKcf_EzWs6SaBVR0QGQI zQu;U61ym_@V`+Zh?DDh7a!Dv$O*CzhX*s0+=u-J+u8#8mLj?W2UrkM7Y|@(;8`@k} zy;#(7EM#tZ9df6YLg6axMDu561WHL6Vto_>sgCk9Q)*YHM9>KjN#KB7PVFqE77AQ% ztrVz=O`miiZ@RvcoLkmm354F4gw$B2#*j2FDa~j3b8dNUl6tQSo0^o;p@S@>Kj)U8 zo`kMQLee0e5zz zlW`qboKZyPL==d&Z4Nld>0Oju@%{2A+1k8|s{&~zrT$F2j^A>{qj!I?z_!fpo_~Zv6Qnz*O#G&d?46G7kj|~x7)JVLd#dKYtvO{P1tn( zQchokBVDN3aYxmcc%%(2{?OzA5a+v^N0y&Tbar7%_;3i;%9o4>m{Y=4bfKpk_;)vx`0wt%Vc zVEHKsUJ$xIM^N59R(ZKHLG9#2)fCQMA+_v*9&ti>8FAN#uFtxt44-)&sVhNrP+Xm8 zr|Pa56Qw%7yp#yf3SIXSVWWpZD~V@tBQkUWWj>~?zVc~AI5%{C2D75&uuUE^qjR*T z{zXeRy|FgJ6Y%doSY7j{^;+9%{$!#@%S&!%W%X+o^ zWxZPdvR=(!)@y02`OA7>{<2=pU)Cd@zpO`k{<2;vw$=P;Ju>#E^?2Y<>+##4)&u=% zJ>>G2^=kRcdbRvzy_&zQhuqov(jMq9>mi@NtXK1w_4w^C>w*5V9u?7-_4L8onm?^a z$^2D7ina2l~@`g!{{SVE(dREq__B<}d3J?l0?s{<0qEFYA%MzpMxP%X*-{ ztOxRCJw8~8pg*k#E>jJ7ur%XoLNxN^jQYf;AD3}a#)ZQ6 zw%US>^Zk8DA|9IsI)r3&OvZURd~U`$8E0plmGS6|M`b)xNClpde;*;FVu$D7hY7KP zl0UIYhL6t=9+w3lDp;W`WEDRr3G|nFn~Dd7x?@AS3et;pPE;n+HJi0O6Sjs+k9> z=7G>wH4l)kd7y-#iGTp}0B9Z{+&sW<^8lE6plTlAw|RgJH>lt(ZB_FCXdVE~1Egaf z05cC%%>(=v50n(qpV%ZI^FY-+K*Ht$GBOYF+&qAM=7DPFfvS0c=jH)aoq3>Y9w2_^ zfokS~s(FC);Q@cJx(X4v2AKz{nFp%o0WvfX(AMSwGRizqH4pH6iQpvIP$TmI1yKyd7x?@AfkDIf|&=9HxI!g;9^kim0L(m4F%OVV z=7EZNfam4`(#t$hF%M7?^8o3?10Me?5P|w{#XJCJ9;lcH(1dw_2<8FMJV1u#0e+hY zz$HqDj57~Z%mbun9-tuR0iI_bs0`-*Unwdg31%Lsm0L=rSd4Pn?1N=4*kg$0GG!GD-d7zScpkf{% z1M>iA9stb)#4``@!Rm^MfB^FVXdXb&JV3&k2P)p={z{Iw)d7#7}CIS-7JWw_dkdS$R2<8EPn+N!vd7x|_AijBk z_~rqAZ%DROHV?FF6ZEVJcTJc%I0yc2!(w2P*#nYM6Rm^wOjKR2eGJ@w?NhfewBluu5%YRW68oH=DN zTcZ!MHM(u`#>s<|CrOVN6MsE%XyWFHPnmdV+t1tX zD^@E-8!;<0x_NXSlLy{~E4T3UP=xU?}WJ68T0?hv%F(r zMGoXOVd;~RVACM0v8hElebO=>*&`})9`}Z&rxNBBze>SL4Hfp-iu}YYXs1*3Q|k%t)lJc+WZBESKUmV65( zZAQvTl>JNERZ%4<{$Z7~iM%*0J=u+8J>-+K0~Ps*UlNv{)Y#K>I^>s(`pNza69NL}Kwm#99SL{2U z8J4EY-czF~At?|KrIbQ(FVW9rp#DuwRZ(p|43iE!>!Ul4uHZo~vE9COke_bLZV_n6s{2dog*~m}p&mD?Q9r9o=Si8s4U;e}Hp6kgvo!L;Qw7>UZMzXV~BA4^V za7yWU?$nQH069-p0FM*pSzM6|`s{GZIVy~*_z;tMSd2v#IiuHxQ=Xe+j9SMK!Kl1) zx)M|$M7X|Tx8eMXywa<}DI3s~UQ$C#mV1PB<(Vek=cpOgRiyQYT483SMHP9d5k7lN zl!9v)OCP(D`ed_MqYl_^@2JRCy*!+vkD#atlN&YFmu(w|x%tjS?!<7)dRm>NEWn>Q z&6>4YjR3WIcC(C$W#qa;qiH9hq;H(==&U>q(RJaJXEnQ+xUMlwa*&DCfT|9(!wHpD zh_4T){6CkRMyAX8T?xFwsh~cY1lf|p(CD@kDsrk4VVx84snK+{q=XeUHxT`&4v=A6 zPpPcXM)u@z$}>@eTAFgY$z%;^981RSmvfT?pDrhrl3QvFlp=L5uE@QL&775#i7UNn zwz439Y>Xn?o1AuChv!%1aYb>@km8zMOXq1(g*cf0tbJ`x%T?rfrLEQ;B#D6bD6}8w z{iWgKD)PRb6;65jL2@WtA6t};RA$`#DcSiI`Cuu)nsM3NtGrh)Z^+&04?8r5S6BRH zxF>{@WF_tr|M{sfX^TxaDRdm|Ew0EXdv-V}9Y>|*Q(Iz(Y@%X2GzEH3RI)XxSWa1D zY_olj?u5bk%T$q%nvmA`JdUh9nHU4%q^&x4r=}$vZPo9Tiq+9iO(}|~RI8LGn?n4~ zZe{Jt6HC5cH}fee#v2ZbaexAGzwefshq&^FZ56(~URgqn(uQyn8?TrgJ2Xn|n$evS zl2Y`hPyTc_9IiO?D^DWMs&LZlb1vR@(>sw?%n)Vut#P9}j;frDx*pq ztN-VItbGE#qDsf=LvndI=@n#kpbJX7(vmJi6Wtqm|2asm(IguZ z)Mhqv$0xa$93;n&3jg?GuFon+u2bVbN`CP{f;6~#Jk85qmuk~d(W^(uop-QYx#51I z!yrseRqI8_QR^2SB!^}&7@2~IYa5kEc1~p>vZrGI=Z5Yqh9hGAcSsselJJE-viHW5X zj{8ok%tLN-D8i0(9QqlXzSG_p}9N5wH4I4b3U3mhJX^{Wrv2H~iCW{@)P{6!GDIAef zcpoC-R7Ky z99iPfmmCC7oiH?E=7(mMW~48x%lwv> z@IbgDTpC`&x#;!@4^XOc{~M@^n_m)E|B*I7a6WD}N=ARFMVPgVhc9vSsLStdT;CX) zTNPiQ7FPcuoq$e8F3puRq0)&AhWZn#V(s-|_3!_$Hlcq&WuCqERK?sI!)m|i#~4D7 zrWfWlk=kWEoumo(ZAVu{<0ppIzf~D^=#6*0QsH%qok`}Rst9~-Sp6F;)~P&Xnd8c7 zQo&mKm?|tzE3f`_u5|NEUH+NHj;e~o2gAyvW#Q_Y^eoiCp(g^kB4ch<@LUb4_f6}W9Z0gV(^tmh?!6zomvgb?1}%@7FEUIYs1RJ$qEA9(|~R? z=Wm`4b1t5RHEOe}qVAK!N?jJ}Ij(A+I*g`Cjaq45Rn)yKtUQ$LnYXH2=P%2vf|}4F zHoq$3UKLh8p5l0ZE4i_&2PNsVT8-MVRZ;Ycu<~*0$5M|qNyRWTX#NtCW1ZKi9a9xi zmrf5WA1g_$m~MXHd-l3Aokee>w&jTGY@N&L4l55yg33Cnk;Z|o$r>$+=d>)>57)xZ zG1TI7P&AaRO!FuGZs?9i6u|n21oV^l=10fYSCE5#Ffiz3=D5qXWGeYP7u#r4hW#yv zR%fYr$Ay)Td1&!ay5^b95~0j%U0(H8g;H9sr*c>-m1f8U@*k5rIk;+KNEbh!y&89y z0AtHT9!WdCK5TUB^Q%W|5lQ1dTA4{{O5P^R*d#S^J&ikzGD0t@RGHVuUe<9OcjaBF zlQ++`+(h0q(Q~UuDIe_YQM9kkiz{iVs!OjHa_J#+wM`zv3IKS0da&qIfO@T2(8J&hsY;Hz_;Q-szd*67GTvfG>s>aXdVdZp}Oh&^bK_`~59hH@#RC4pH8c69E(}>B`SN(DPt@He>V#Y`w zUDZf>VputqUyMPc992c#nt$iA&8=zxg@L9Zp*(g=nJGJ|(gj;s)xfzXtel*LQtOGt z4-~G!w2i0b=T#r4W;r#SF^FYa_tNe`UB~)&;@ed#HHKs!sqg5+ODG}VUCbyT(`HRr z)iAp%oKd6+JRcl&>&Qu|%t^Df>epf0!Wn%$OXIyc{Q9vrZYs3N?D(IlGX~}x zLRFvH-`IC-^-*NJBAiiRY_NGzQuWF#n@ktk1=X1dt`2AP@<{V-kpc#IE5MXUxi){z ztIj}lSvaF7SKe2@aOgEpORy~3a_gMxbi`<~?lB!{Jxak`4xRhmI2g zvB}3LMz?lV^{&u{aK>T^<5|>oD$IU%{O@{2>~^tt8*J$pNX~-iYGgdJI*Awq;fzkIlAMdo z>$sj$V*$_SRoi)vdXA%IwUXbW%<|W^Mkuq#*C$pdB6VRnV-dfZ+&5@C%urcgLidB+ zwlx?IQx(&QOwYKDzP5jKXIHh2$S7o?ia@d1xoDIyPx^)?nU7TLu4+ti7K|^J#*`an z*kkAGY(c3eebrW?oEy%VPn3UCM(Ll9;nM7UcC|&_wL6?~EGfBF6Dis7(KC7GNioYv zRaGa;<hQk(BCm^^s zoNr>8imEeJ#Q^uPAFS6Yt$y}H#}QOqv!*qd@zwZ8{49!#tNd#v-M&r zY4ucIO~&juJ&RvbsPbh01#v}o`DiIIRGuoUB&SvUMpJ88`u`BiI^K_AU1CwuOfvyY zWcKLJ#Qn}=*8 zQwr5pc_mfZ5SBi3EH!F`##E1?R4`^R+FKwT|Bd!i{QmB7Vd+_9M3<#xXi)b)ND-Qi z<@v(6W^3y65mY@+v0asyr_wwF9jfuv;9A_tEGUf%B|Ydp3t1hdnOC`xD9ggq(pr9w zU5tz2s5oL`ITN;@S@GS~>{&jYaeP>{8Qm%Cx)D3^^e8c2etF+3*~YL+fVJSca;^sg zl=Gs>1?0RoEL}qxZfF_RM(UjFV?PVfr7Ze|Gxb>072B zKYhxyZ%w;n+B>G5Gp%!4`_yl9qk4&tX3v@On<@898JV(u%5$bHocxE$pPO8n{N~Be zoBX87hfP}h`$_jrs!jTzNlPX@s{O(C+uGmSer|hL`;>{_nfU36)roJMxM^Z>VjFIQ zYuldJR&1LbzY+JvEpbZg^{wZ(_VV$y&$qn0<@w=H;lsR5y&)``@S6$uzU|Z_OY>?P zDVK%yA2%iX-F%IY9-wbWUwW%$cVA6I;<;h{$JEetLi*2aOu*xzx(aIq*#KX>YS`ze z3-kH2r>0?XV_5%Dexb!Zz%VN1PnkTlGS2*UV}jmV-?yNqk#Tic{}CEiO-XBLUF);@ zUi(Y)@`2ZSYb}VrAgt%5Pe~~W&DT>b&n)TNd4f3>tFNX(j_Q0^!>W3g_UDxm#zX<> z(^}K>l_rg8%~vv+H=J9WAbO-|cTpm(c%jb&*`luU=uV>ZHa;I5IPgHJ!cE&WFP0%< znrV>s>-<_pvOc+3<f+?npiAu_P^Q(!&gwr4N@&~&3>ts=yw`E?dMC^3GMBvYG|ch z@38nDWrh|rTPJrC_uM!VawW^}SVtiMGDHD(-)(`qsD%C`nCu4)uMJFJ(b;8q>0 zp01N{{#X%nX?@T+ca`I+uOP}-mAoE7;iB*lG7l!rFI09IOdYU~a_$Jxb)s8^Zc3no`mfoxyM$_NP%;W6YSAwb&&}XI0}U3gfE}J4HUG z+dMl+`+=0f>fp2$AGxV&D5V$-=B}CsJyOk9rB&(v*;S3CSi0u9s)Cx1+hJ1#>2v>K zRgI^M!}?(?OjNg*Wv$wrInW_hjh`K1{k|N-8$}DM8WvZFbzk?enCvW#xtcyf3NnBp zDzSZD^@WHo3+qOcltu?SuB5J0R!OSAx(TWC!n%g)tm|AfdhSf-G{%5q9s$)u=Et~w zO!dEsd3sp)HPXY5Xg3*sstWXZ2^wdy-%))5V#~w2uOh_O(>Pr`yE3&QGSv@Do-*>Qlb3xNgUhiw)-WYyTRp-H88`gc1LZ)TC)tnb1 zQnqPS<-3jwk7bx#@hGIafdrO>bzdOA5|`V$Y3LQ(DWGg;gdx28oFwyk)M}MzN+uB~ zgdp9KUOhXBe2yq0dh85pp;7pbB>%XmVP5sW5Wyn2u!u66HS`*=*IiTxDcXPEoa%bS zP7UksJs{@!dAcJ*?&p0As?SD-#`=t#zM+{n2lWonk%H}V+ILj-S%?mXb@wQ_gcN8_B}DI`!sP%ddWN&ZEOP5QC)`wCiE#v=;`x8V>u=*a%PjX`b^|Dgms^E zp-7xUVUsAx_PLt^c`9K;RoA_}r>b*S8^bzX28%r7KSnRk7#sG+8#jCWNZo!+^%=y% z^grHE#nVm8GXAWl{MC$k)wPJB?~fs-V_EbH6_$#nr6LZ5#=aw~Pe*DXtotZB(Y7ju zWzHr+ZMp61sIK8TwP*JA@Y=(#dTW?w>#UxM9Cm&;6@W$dkm45VJ`(kB{$p(dx_3SEv?CRcwfD{`ZPq(32T4SphM0c&9@t-<6p>&=ADB6WT;S0 z=T}#y~$zkmS)M@PE8ymB>5V^8vQFR#-Ys1?652DMoj|VTM z^zzhoc6BMjOTyakN~xT z<6nIuPnU<)|D;iNI4X(xp_+0`ZdP>=xs$`{e-P{@*g1O1Y)Y+C>}1k>@ljQsg&GX2 zeL0%iuXzTG5@NI+)q!-ZX7%5)*7Cw++y;d{RDS#XYCj3B3akH`qr;Q&LNsRUnlX!>MwJ;+&8hVBLg|O_+KA%v)x@apt)*duRM@#%E_-J>yj~&YE$; zjENi-_{j7(Pd|71lcs%fT4@iLsvkdX%G3v@-Zu5psn4H!($vGId~eE~Q!bzK;wev= z{O8G6On&*~r%gU~((fmIY0}W7os*t7sc+I_+ke@9OZ#Q*8`^u@ z6y6Y)heId)i~=9@zmD43R?OyBT2%dL54r<}wBn?VF)pt{7SuGxuMV4A()=Pl#B?xW z7Dt)(WV2fr*EG(b9X5xtByp>ww1?Vw?i)lY)HLjG3Kzx0cvPB9b8`uw9!6CRfm9GZkb*Wl=)qa=|=rU5|~9Axwg%zX&!-4ud_;&@1cadqS6M@ws|$p zASgdubR>0)5>kTz8sz-Nh|z7w)HGwD_}#W$COGL(BvS!rC~o6e(Y8f3O&HdO^VSkG zI}e7C^%QfQ@^rpyq_d`p!@6+Z(+>zsC0d1XN>Wn?o9{41VSMbYX~ux~8mgPqMjX|t z^(YcwJo4r+N*k>^h;QS;P8p-YS0Syg}aG)A33=@Hkz_!*mN5 znbl4s!VAKAD;0q#51sT$(bzWqK}yO~#f3FZ64r$CR_Is{*|Y18whyGA2QyxslY-u`=@kdp#wG8oX*O_f zC~bPVdI_aK$3qN0nlIy1)XW=BP|pEec#24Q`fDc>@w~9Skug5P1y85 zwp!mSL|Tyhxb@61^0zT3t}%;XPQ2+Q4NgHkgJ=0R_z2tPY#=& zZ>5uimZEMU9mpD1XFeW@^%fqJUDIe4&3S-C^ z>ug%R&;{nzjzex)*mO>oJE%)i-4AHJGOxDCQX83=${`HgdJA z>m;f9wPTT56*g{5_0igF%yp{6b9T$BTF2B%9f+PDHfpkv<`-z(UK}`+FSDi;kEtD# z#Oxo}Tuj48b4}}SB`dk^+C0S02^-&F`*Ag!+LQN#ULaM97s$^^m-f!7%_Y*QVWZwI zp$+toLt-`!04e{D+8kt-hmE{WLWqdMj<+l)x104dzcw2=YJO3cYpxWUDQaBJlCvyX z+W5L8inhn`dNQJ>=+P_j_iN2OxECQGKHJ7WaVl$%{U61gQ|JWf={2q!^TA=> za;?Z}G%@t}c%x>njeU=;J(iT`hYj4?=P3ip5xl5&2*GQ^hW9m1?U9ZmC*s-HQF{!+ z%fp8Ej?KTc#$ww|`AKhC@R-`85j;I?xPnwQEV;6yX+}W3HI*W0cI{D!EeRXm<0ez5 zbyIVG5HnEqcN|-riO7nu;oa1g_7>I1@qz!#Jj0(z>EFWI3?ymNcOl6!E;xV83Wl*!3LT_WsRaC5P@7yr z1dF;1f#mY14XDHr+Qzw%8e=HQ>RcM4Z{rsa4xFTWnCOwUc0>ol`cEiZDx@`Ac0|({ z@(-LoVc&$AU!OTNv-Ikj>t>!Xa}wtSZkch}j7>8J`1IO6(?_OXGJW;*L#I7Btvqe@ zw4ey4ATX!xpKagba`i23 zPj8zRAB?s5uJ{^0t~R;#-q!#1_3DdSTU&nA^3j&}wrp;BTFYVKkKq$jc!%IsVbz5D zChU9LsfUz~t&8Yagv(DwL*hdzM#pBU11q^>@}W z#6lBEn+Kg3^%M~u9n1#0mXQb#=K`$?@x|0NVyq6AJ)5la)Sf>)SlLRw685X$htxH2 zbcD+|N@WZ17ry%H8Ya#Sm%gg$d>tk-{eqVaa<^~fcbro4B(DCk{e-$kjrHNu%?BcC zhOh%0pnC2@uB9xQe>;w@YwS2NT>46s12;)FsUg4e+*>~iffs~JUm-o`)hK>64)I9U zN#1`Rl}JkjGP)Sy-?|^kPnyXs2_pM@^IQ3@s&QvBSy28Ywar58w9sxygZ#$tMo5WB>O)pdS(aoGKKwC35H250X) z98(v8pB{G8-P{dfk%Lb2l(4hiyqZ-Pd-sOjZ}U82`{>Tu&J2@;Y3tB7OuPH7H0k(> zF%Dppj#`=}jOc9Vv30Taim>}Fur(7w=4R@NG!z3K(=2)Sd{CvcF8(g93%f5(d7>%` zO=EHFL~W;E;}U*iT@-#{*!^ZgwKN&mutU_BE~BwfOujQWxsoG$NO?(Jw0=?8{U%DO zF$Qo7c{G;Zm<2fjN~1#Zz5}VDw7f;vSr_F~+&BIURa#u_hJ%{V3DiaZXcQxs5h$&( zhmWg^@fU{OySy4W@SwXcdOs)Z7-_hpF>F#B8azQNyn6~2trE)EaBN*1zas2V@6~0N zXu^zZ#Y-bvdN3C}tv*!@(i(Q``^J^xQ>K)*|1mFrUSfNKn`S+tgwJ zVTXQEO4*7QG>j|CEN}jdy(F@rJ{j@VVaMJvarZQAp51r6F^a8o_V~n4sEhK~haK0a z>MK2R7~>gKiy{(5?jX6^th$K*?;w6Ft>xSh5$Uq`VtCG&U@ApL1>Jtm=RnnL8StwITrkWco`t?L*SDP7G& z6<&TECJZU1TT)8>^%jKA3tK-ts*gKTU<6FrYB!8uF*Uu?3!Zcu${~sUqdSZBK+N;Q z*1M?fzl-R07>l~9?qgJkhOHkOZ$!IGG7gRZnhQT`-c4l)U1 zk}PB>%jse3ozzP63Ac@At_W9PG>P$9soLuiTNAc^a9p;eY}XpXC0Q|fQ9IsKtX+ig z+OYKwG?UdJEsiq*8@5wEzV5N~jFVB~J+;>&zcFmRy^+nF z9TRrr`Om?z6vAz@sr*~csOgyTw&~&Z4S97a>b0uA7|pN4kM+>8_m^}{1nQ6z2>ZyK zrWy~c7`cGzHl(#5{?QUS-y-79mZbKe7AA1IuLRA)n5l`Vy0dl}sJI@vagvMVz&lYC zUlu~@>;-4H*7IvVnzbyvUI#jOn7^z6asTH&cQr8O^(LPckvhC9F{Bzd_H8-3rsG*# z2gB>X+RUgqQyD$5PErA>zo)OMcFbW>3X5U)9*hm^CxXgn8nT6Ata$2BAdT>dx|0$f zT1XN~nr{}dzoui?TTADL*MEfynWHtfWm``(ER;ysR7)f!%P7V+ z`n9M|PDLa%E%8W}3SdVZJ)2B-H?HM0%2?9!0uqgH7;%zOTi(nBby(2NqpY1$W8aHw z{Ow8l!y*?T}>3Xs_Mh= zg2wmFs=b1qb8>k7m(&QOGGeDjJLM8RvG#H#E)1`~4};WnM?+9Du&gkx37L|LPB};5 z{Nd`II*qVzOKVMEVA|RmUjIde^5AUq{1;a99~%vYpv=jF+yfbPhM@!*V?r z*Iq^~&JM5nIdwosDhb1q4svLUhLUvWK-v+Z4s`El*Dj#oychDCpCRHe+`0* z+|VS&Jx(+S{+p%-8$%6fh>xr0zNyTlG79S@9hO+Ay_E873a|MoT48BGT1v@-4c43o zTI*SpL#ClR^1Z*ic0SS039tD{(>fk9=Nq4zcEdFQ^ws`{Nau#v{J3$QdYLpl_Ql>& zEMBvK@ey;p{@P23cV5_hhzhQD)ql`2hiep#j?C!Is@Z>%*RaM3UgVlxdodB0l)~o6 zplztMxz%(7nqf6P3XdFFI}e$Gu=&wsudXN|mCl`F=lt4>c)TiXew4#>%8YKMF2n&h zDN6@uZf#uO*mp`zXX?mn=7Caj?wP{!k{^RB@iBYVPp1`h=!-h27->v&%E#AUNE*)y zn`fY!WTjM5e<0}rP~sOW8I72`7SuK&&o=FJ?2XM_LK$aJu8K$jr4T+YNnepm|G$wv zHEf=S9G%2OluLk+*7>^@*Is}Od%05|Aw3kPVS%KQiO6%Y>!{lEi7^;9Pe~l6O68#s z5|Y1mqdqRWENq@UK1ms?Jus+cbSEFZs6CHlR)o!yTzdZFY&7sePm)eM^1Ei$&Oz|x zu(>_yLoaCWL;?fIUh~e{bCFmVHcuQ)*3#h19=W5-?CeLenbB?^P@Oj*Po3MEs#bR6 zB=dAYZI~qH)t*BPsuW!^)uS0TwWqe2w#y-~Y1*eI%>2_;FMZyV!(s1y!~C1>+QK38 zf3)BF+`F&-U2CbW?yE^#+v@9eWno)e-B**wwz{q+G@_-(#bcI)>lcsguqJu(b`sDkrVLMq!Zif%k`V*zL~VW zt?rviLElVDxNjyU;e0b`{j@Can@M@@n@M@@n@PbXIsJSyY5in=mz1%uCZ%L2N`T+K znv?_v^_y_tOv>*8{U)J&Gikj~zbTM!CMCQ#Klja~#LG96)_pT6&wVo~=$lDN?|9$6 zN&=mV*xFVS7XF8|IqlhzN( zzkM?)a=w|A^z+T6b>B=%Jl{-8!KVABPz0vsh*O1V#8;D&z~mh6n@RcYn@L;S>b{v2 z^v$Fs=$lEwR>_0DnG_rH&7`1jCgm!0=f_p*zL}J(&`TGk3~Kpm(%NhDZ(mLNxVD;a zCgqZL-%R?bw%TSr2YoZ?RQ0c7+g~Qk{1Of~UFq}pVI!QiQk)e2bXwkpZLs)v)kTv!Eesor5^m3 zSejS=FEhcN&m7&}ydgvxbv(rzF;`e77+i^sMlORCuY^?b7N;+ zhh)};JD-u#Zq6=qMEMfDuxE_6{9$#Sj#(V;Tx<3eDMCZ}ktSpz_(818Lus@KO<$~%~|Qwb=KZFr(>HrkRnckBkP=7DKT!} z{-S1Syo18<}la<1{O*3_G0!v=ZIRg)Q+gHMyfa5oR)=3$924!$z5%B{b@X05^iSCO*2?| zudHN?IhTd?RW8wf?M0v;-@q~IWgQNX=Cqf@ZseHJarKo5pB47ALqR&)Rng3oEcMM* zjbv#;M?3LW`hvO+udELH--||@XH0D)asbFTu2!KajiH|UQxV-5_FtiNqyXH zC(gu9(=Q8*`li=~bsc0`6ZXG{;dn@i`Ea75GXc&?t8?&lRs9T2Y!3|;O^0qooTHKa zhvYD)H1s1W^U~0+#m7PCM*YbkjAYnCLyf&F>!&Nh$A!ub1dUCH#%w(mM&=>6UQ+J3 zl}~Dc)Zok}_jcEH7UrB#+26>v?Zt~GhkRIEtw#Nm2Hb*MC}!o@`Z6M|2>U+ABfC+Z zga0I3CYX7()m>le)c1W>!PYPd+dC_Vj+cWfWmdiyh{xA;Fy>id-@Pf07q;|p8YM-^ z;ucm~-SsD@Og{6UXL4-))Kr6eaVK&4nxCoxw1gXFwv7}Xx6vKFbsdd)LAd(w>6pVfF_NKXY4J@TBqFxEmp_Ph zbX})qP7GK7O%_JdbbgtS-o4Pui*$87hIBa(Yt54>*8!%fB~$LPHwmQJv`MPBr#tI9 zDYGtI&EeSQaz2+n*?V3~YH>`}yXrb6vmsplXS6-Oi!`U>2c256?LN8Qr*0L(m5-$| zY2Hp`%EQ9OwUkE1Q@jbGCQ2tOtv5fYQlTv;)C*+1K3sVS_18MfMydvNUq{cPADc~M zh{walx(?HvAFkBBIwD$3$+=N%cJst*URWnTq7PT8bn=&MIkMhE+<|cAqX`*{Ef=bM zM>ox{>tM~QaK-WHEFTnCD{rGvya^*y_p2|X7oSk?a;dNAY+#tw6XRC1LQ_QKlTq@Y zrt0g=7{%%0-zd zFvq`CJdXy$IOAk57cZ$FFYo9@;fjShl8WdEs)7!>ka1o?v3itOMEP08N{h4H^-fY4 z43{rYY0LF0M~#60@9U`RNY3(b`57p22Q4I~aqg*cOO1SB;e`4k1lNblPp6eGZeVH9 za?_J>V97{1w=}L#DKAVZKSkZO=^Hi!lK>^qAWsPv)fXVQHe9}pkW7UK_Lj7<;0zhu zc)D+KT?c8-4wo-QZQ@j2#O%&RgK?5)+=L^?SWwrYn$_X*(@^vHO>y!f=1vV<^1{&q zi|QSzC{K2lTJ@D^a=)%*Y4W4Vadk;rr>?_5{9sqhgqB$B>gtYn@@HG(*GAKjfFeZ*LZA2s&Hj~V;p$HCCn-K996;O~;s`=lTC#!rDQk%!&!(+=v1_vA;P zF&5&z#=iJjuqF0L^m7jCiJv$2#xEEP@rxGemB@Wr*^ki-#M~nLTgJ;SNtx2#Lyk@H}=E_jJ@$eVW3_7=L3NjK2k2TMAwAcMj-|zc-e8;vf7+Z~UXN5dUQCi+=_~?3Ygd;-H@RS7UGd zo3Rl8ZtRQyF!slP8VBMVj*=RRaf@*4a_Q$=(fjDF=#(l=YI1IM7^mWA%2Xx1>u_soHy|HR6#G0`$ z){Xsfzi}YmU@XQPjf3%~qp<(hmj14IvqQS$EykXBtFbrUW-P?pjeYS0#{PJR5q2;Z zhWuPnGMuk6vI7R=S3n!; zR~-Nw7<*#F*c(TUh4^(NY+!^9j05po#$x=o5oQ1rGdKWdFv1MR-co$fe?SdJsKE#| z7-0q@%wU8Wz{Cs=fEkQ1gAryh!VE^3!3Z-LVFn}2AS?x_fj<&8_zBctgc*!5gAryh z!VJd#_!r|q{Hw7T|7IMFe+O-+|8PKe{HL)ezTs%8yEkqz7UI^Ut^dBb%>n)KV&g#E zZY;(f#=*D~w4Gk!fbO`<*b{dfd*d68h4?08UwpH%KVE7ah;K0#<6Dh`@ok{&v=lFM zNOyd@u_wO6*c;zzEX2!=eeqq!{`hX=Kzxs}7_Tr6#`l7@)Au=`JN~b+CthjnjaL~9 z@%_fWc(t$;`r|bY7>Ij}#dxi8FkT0?whVT~>mAS?_ZoZRkg+%JGZx~ou`iAo`(xQS z5G%%FtQrSn4Qy#;e2sO7bjSVN-b#z~#2bvg@kV1I-el~HHyiuoEyjU(tFahwGY-bv zL3fxBIG{V;VeE+?H1@_jjfMCjV_&?>*dIS^94N)R{YNo=#5fo~3cAO9%mLl;HK zUo!T`FB=EqSB%B@RpVg%8t4wwa6oq)HTJ}>8++q7jD`43V_*E1u|Iy>I1s;MEXMC9 z`yY(=6W|{6fS+{72aP@Pd&b`QePbd1z}OdmXzY(aG7iKa8;kKL#=-bg&>iMy4(N_Q zH}=F|7<=O{jfGPDmH+6Azc%*A-xvqtZ;i$HJL6#dJ!oeCg9EzbAB{cnPsZN(XJaA$ z#n>1BYV41HGY-VR8;kKD#=-bcu(c#+f5R*pVRzhO?1@{!kfxm5a?r)b-niXZh&zmZ zai_6AUSb@GyNt!S+c+5C2--g19dg0|1g9nc-$W$cOXHulE%7z^u@J8{_QmUr{qcI^K-_CA#v$Wi+y~k|haJ!zM~pqOZ0wB{V&m8AK; zSaU#stQ!a7eq%A-U>uA$g0|0_9MBzaHul6@jJ@$zV`{M_U1Mv=HF@DfE z81DpSpQZRAhjhogj6Lzg#@=|hu@FCE?28{Y_Q#JI2ja(##rO&1VEiO#`}~vxSV9g>I?1^7C_QtOm3-PPQzW6m` ze{2{B;;6A0zYd1zb<#H+)DypH?2V;w`Hw>Uwy`gM$JiggYaEF88;kJ)<6wLcv_XH* z0p0QY#-8{CV{iPSu@HY`?2A7(_Q#(X2jWkS#rQMh34`(H{9%Lsg+sdIFO5C%SH|A> zYhxk)W>(9sYwo^!Qp@|E9%6gTwAmA8JZjplkAB&-nc=RtvMcyY?+PB;TDs~vY)IdA z3ag{RuAaX5TgUH@zcUWR-y4hZ55~dxN6_~FCkJ%LKO1}EUyQx+uf{_Bo3Stc-Pj-h zVH}A6G#29i}p%EiAVuVJF(1;NlF+w9oXauzVzsUj6 zh!Gkw_QtmuVG)oq1sZVxG-8BCjL?V?8UanvmpcF&F+w9oXv7GO7@-j(G-8BCjL?V? z76EPl?=Lw78Zkm6MrgzcjToU3a6%i4US`hMJFF+}HTK3KV&MWGuv+E#k$~ zEm`E&EOJ{Gx!uUp&&bkGNSl8~4kEf+tYQPEq?(O_`q%wOPuy?pjW-wz@kV1`yvf)f zZ#E9ZTa3kct8p;i2AYy?cR+XifUzguVeE|`G#28W#=iI=V}HC$Si)=dVFwiB-NwQA z5zrj-Q3rI#j~RR7$Bn)56UIXPq_Ho4%Ge)2Z5)XA7>n^U#=&?mXo~r)18_qbaYM~6 ziG}b(IRHPDu`k|d?2lhE4#Y1Ti}5SQ!T43s9P>2?bjOCVCyp9>T1MxS;V*IUfF#Zno82ft%bjLp! zd*UCBz41@RLj1F_Fa8A#$w&BC2ld3i8GGa3joAM`{6}B>r?Ef2VUAj4AZ{@h<5uHf z+y>fkFLpq8+-~fNJB+#{Rh5I1t}xEXFq(2jiQ;)|L{Ez)Ky{9p7T? ziElOb#Hv6Rmy5p~mJ@MDZ-uN41A^z6b7k_8$kH0q##6K8|@sGyA z_$Sa!_Gbs+r#9l#{;U7MQEkLgZNyP+#8GX;Q9W18fTP+t7`K9MvTY8)QEkLgZNyJ) z#7}L+Pi@3cZNyIvGXKL-?EoCrpxf-t4!}`uWb$HU@?vE2V#HBxWbR^Q?qX!_Vr1?D zy2;+<0Ol@6{M1JL)FuCcquPk0+K8jth@;wwquPk08uT!6wFA22HAWoOMjX{f{M1JL z)JFW&M*P%9{M1I~-^2Xjabm+dKy`J8d-W8S$P^+c^X-H8d-UQHthQxz{=Ce%G1co z)5yxx$jZ~m%2TNM&!_`fdKw4gH$WTqHyzL&zh&%+-!}Hf?-&d5yT-nFzp+0)U>t}K z8jJCJ#=-b~(02U;2Xx0D8hhf8=H~g&j~!5mKQZ>jpBnq)&x`}{=f-0Eg>f+c60~9e z$^qT+*T$as8)I+$t+5b)XY7l=H}=Or7zg4Xjm&@klo zjlJ<|Ve3y_Qacvz42yaA>LB*AARvwV}HEOI1q0)7UKtugYgc~ z3j3e~*xxp?ziniH+gOMnHulB4!MxM-5eN0ej~aX9$Bc#eanStp2?zAYPZ|f}r;Nq; zY2#qL2ef`ZCVzVZyt@7iS+{x@tY|G2Hb;+zkU=z5Vg~jxi8_U@XQTg4yHy zBM0@w9~*n)PmG25Q)6HJnXy0q+&B<_VJya98VBRAKr8mw4(N`*0cnz+_*)0`#@`tW z@%P5Q_y=Qu{G)Lo{>fO3e>M)rzkt^4U;jU8?;U5?aou^=d#`T2_xim?2V@c;*#Jn8 zVvv9a8r_zIBqw=g*>d)H)|R}Z$@cEp9^11!Gy4!a0AK=h6hTlV&}f84697RlXEA_D zV(2DCkzmeIOk&#a?{_aK?d<-spU-|i{kgx}b?>ck>eQ)I=bWnNgeCD~641z;WV##4-$zf#4VPwf+WXWM<$zf#4 zVPwexy1A!0ffa|56^D@(hfqthGo8Sa!^o1u$dbdzk^>AGmIX`;=Q#~ylo4Z;5o44Q zW0VnNlo4Z;5o44QV-)CGUFrmxM0~R}US`B3V#F9_#2977rftNgZN#Q+#HJ0p6ULl? zP1}e~+lWouh)vswN!y4?Tc{~4jo7q7v-ch+VAD2Y(>7w$He%B@V$(Ka(>7w$ zHe%B@V$%jaCEnl!Oxi{ii@g8QES!L5VMMb4i^c41#k;o~u}2uOM;Nh37_mnfu}2uO zM;Nh3fTsApPQV^v#2z7R$sXYZ>=8!n5k~A0M(hzr>=8!n5uhpls1vY97_mnfu}2uO zM;Nh37_mnfu}2vD=8!n5k~A0M(hzr>=8!n5k~A0 zpeg=}6R<}Zu}2uOM;I{)yx|+{5k~A0M(hzr>=8!n5ulZjcb$Me!iYV>h&{rHJ;I1R z!q^u-GOmap8~fuY#)0@Lh)IADi_f?#R=+EcrcaPatBk#IwQ*ToV_Y5wjeT**xFQZ4 z`{RglAg%@7+3TFJB(66ujVBv><0-~v@l@mT*gDNOeerbTinzhpAI~rj#4|y6_E}C? z63;d+jprD9=8!n5k~A0M(hzr>=8!n5k~A0pjEPa zxp9}==O@@BjMyWL*dvVCBaGN1jMyWL*dvVCBaGN1K=;U`6R<}Zu}2uOM;I|jw0wg- z!iYV>h&{rHJ;I1R0?h3ZPQV^v#2#V99$~}}Zp066#1C%74{pQ{Zp04`vbJq~DZc6y z9N|VB;b5@1Ivg5%ZLNZhy%-r0}FKLf8YchSVkOJMjTjq`|)5o z0S}fD50()RmJtuuF%k>|aTVy!U+o0kDMs8WM%*bz+$l!fDMs8WM%*bz+$lzu{<%&-XE35O7||Jw=nSA$;)|Ss z(qKesFrqXVQ5ryZ{-sX9fn`KzFrqUU(HV^B3}CUo+w3I#S4R9-M*LSsTtZ{M!GUGO zfn~&jWyFaAx(~;luq5s>;=?fF!!Y8*Fyg~7;=?fF!!Y8*Fyg}i-G%$Pq5U{9`~)Y4 z5hsQbCx#Ixh7l)*5g&#TABJ%t-U7M{Z*>Ab3?n`aBRYc-oxzCCU_@s)#`-5pgA-61 zj3^DD`|w^Tpfng!8jR=+Msx-vI)f3N!HCXaL}xIfGl1^GN1cGqU|iaYQ@%lIFrqXV zQ5uXW4Mvm(BT9o2odL{!7*0TEFrqUU(HV^B3`TSYBRYc-ok7^bBKMLLP#VD8iQxp4 z1|v#?5v9S155tJgU_@syqB9uL8Ngz>{8J|_jqe(J<9o(s@qHun?+3o=iysIaKe&!rg3RJ%h(&wHZF_j z7?;O$jeYUF#l`%4z7zW61;&ARA!zoy$O%j0#m1%ad&b^)iE&xnXj~pIHTK0##uf20 zV}JaN(%9PK8{~o!xnRVvZA2^>5er7df)TL*n*GL|fLJgh7L14m zBVxgbSTG_MjEDte3%TGMk!5euLL5ASsXV!?=5Fd`O=hy^2J!H8G@3j@hR zPFflt2HQiGtpwp0i~*d6AY;IC9M8S+5x=@DK5ASZCyjk^%D5svX6%oT8wcW#L64Ou zoUkPR#JDs*Y3z+p8JER_#^v#8V_$s6NX+50zUhz883*F?pa;tfPFNCOG-8A>VuUba zgfL=+Fk*x-VuUbagfL=+01JD@TTa4?X~c@T*uoeyrV}t@8Zl!UF=HD0;|E5pn4p{c zGbb#G9~tp`8u5D?m&H$wxIK-yJ&%>h!0l>|#50UbW!TprIc z_Qi9JE8=;^{&>D|AYK5v))zWqNxaCoG+u1%jo&jaix3Aj0oxH*itIgGeDz+x?VP4UXL#VgkpuUv0Ld>9cQM#M)>f8>V~ zkRPB6z10cG53pDk-R>mT97fh0M%Elg)*MFG97fh0M%ElgT%e%yzRw9PIlxw-c0b^> zrSU;yZ~UQgS^SZ4d3?y&7aumRh>sZieumS- zh||M})5D0<<2cJ7ydF-#>tVzlYQ!B1y4{BQ{GTHcKNmOCvT*Bl7HO_ZtV|1E8DvpcB~T zYg`(CWbBO(8JER}jcoEY_QlqtzF83`js0=TI1nEL-OR_Guq6K2xHLXt?2SJ$E{jhZ zm&d1!ees}iMSR-WAD;mW4eD7ZEsf6^d*kz8@M3sbe8CCJyr&eA(C^Uoj5E zS3x)PH76{IuN#-fH;gC`MwAC5%7YQ*!HDu;M0qfxJZSv6neTH`=wcuEAqs@CH~!4H zEPiBM9zQnr#ZQbY;-|*`_?dAat~|b|{@MH8rK+mG@igPIc)D?U++ggBXBbz+GmZW6EaN~t8}#6A zo#T`x@m%B5c%HF0o^Mv0(x|Bbi$H&sc~uCWbBQX z8JETH8<)q+jjg_Tg>P2G&Bp$?#W)a0K}+JTPFNDhj7#G-V{hDUTo!j2m&cvPzBmpR z8tg77VWBW$p)g{hFk+!-`34h(5fgx zmKa7>7)DkYpu6%`ZbE2nD<7#7XTw`1o2aU_)kg+cg8&|{; zV}D$09Ej^c7qs39OXA7KrSTMFZ#-2fXZUGOSRPL|_Qegx74ZyXe>~GT5YGb5G-o?u zNj%56G@fhhjprGc#q*8J;|0dPc%gAcyvW!eFE$Ru@0|eun`tg_3QJTYp*BWBZH%l? zjjT|OtWb@tP>rlmjjT{X^UNqWg%Z5g&sd}yS)>{Xwh^}2p0dLUMB5mNwlNZIVMB5mNwlNZIVhUBHb#PNKzGe`Cs_X`+Qv_awlNZIVkp@pjN%bB7a_#2*-!#ygF@@h;=Cc(-wRyvKZ~_i7 zBMvd4f)PG)0uC`F4lyGRF)(DDHiE|anbX)cexjrUyT*;n;%XzC#*J(mH?nEm$fj{4 zo5n$teytPOG;YKhvEDcA8aJ|Q+{mtRBfG|p>>4++Yuw1LanPhc(+O-EH?nEm$fj{4 zo5qc78aJ|O+{mVJp_~yHI)Po|phojTsRSM#O^=>0m@U7?BP}q=OOZ0Or!c2}lPc(!q#y zFd`j{NCzX*L8$fTekULvjEDy?7Y|NAIv9}-Mx=ug>0m@U7?BP}q=OOZU_?5Ag@x=6 zCvnV-kv*4p`es?Y%eXw=ZS0Hp7+1st#{PJ(aUk9Yy1Dl|VM%(N<;sZV%82F4 zh~>(N<;sZV%Gknm#Z93gPB{&qxe=ea5udpcm$?y_xe=GS5tq3UmpSOlJ?#Ws=0;rR zMqK7bT;@hx<}KggGdJQhH{vrl;xh-`7q2=2m$?y_xsko&#%1wMBU{IfY#leUb==6- zaU)yDK{NS#PH3@r+&AnUH?nu!$lh@yd&iCJ9R~{y^%Ey8jh`BO<7dWYapg&p4$I>z zV_#ftToKn8`{ST-AP#}1({RfvOX7%eX#BIdHZN$WF#KdjH#BIdHZN$V4nob*> zfQj3PiQCv4&oW}+He%rhQPR9W@?58(dm7O_jp&|6bWbC?rxD%Li0)}b_XORXjZQ%K zG@^SN(LIgmoFd`z1hzKL`;kWq)83CGxcQ^qJ!iWZ8M1wG*K^V~>jD7Kd5e34C0%1gf01FN5 z0Vg3MjED#$BI2ZC{qv9$kP${?gb^8GL_~nD+>{frZX2;~8?kO1v2Gi&ZX5gJlSYi& zMvU9Wf%r7&{%t+ulqK<5BWqJ5Yf~djQzJ`LBTG}TP={W262>bd#w#PnD?S4ON?MyyvxJm*G?S4NCipqurH6EI#GFncCLdL#*l##W5i1*xx_uPp0+=%zwi1*xx z_uPp0+=%xabi2>^Vxj+@=_k0)jkwQ^xX+Ea&yBdxjVt2$M!e@nyyu`Bei1i?>F8oV z!+~zZfo{ZsZp48ul>Kv)6Y!uL@t_;=po6aF6;8l|ZX~eAhy&e-1Ko%N9W3VAZB8O0 z*GNRJk%(L)5xK^`IDMCIh{!b(k^4m-EKvRbaIgSp_UmAQ0Zx7+f|u`I**(sP2wo!* zyhb8;jYRMoiQqL7!D}Rf*GL4fkqBPUB)!22MDT*eJblv_J#evk`587ZBQ`H1HZLPK zFC#WDBQ`H17IDzEy~_z$#KB^6yT?gO;{juDyw|ua-e+7M?>AyNc)&L+;)BNi_(QN* z_y5RA>=81uN65$?AtQT)jO-CIvPHBWn{QYZD`D6C-OABWn{Qr&AbNnh4os{=<#m<`)-k{2Es1KSNf*Xv)HW-O*FcRBfB(}jwY=e>52GE21UMCRSU?jG|NNj_V*ajo94Mt)c za>ErdPfj4_$w_X zo`fyz#?LqbzoZerq!GWQ5x=AnzoZerq!GU)Xx4tk3HT+A_$7@*JsF95G7|MJ3nL*GU}#QW#$la0YnRiL(urBWve96;JUsrUywZ4iu^uigl@1Hl(nxyo*ixxI)XI}vd^?j{Ui#4@mGrB+#3m<& zaA{X&va{EqOJ`!a%Om&SF#YJ&OJ?U9Ivb^)t&jV#7Q;M>6;UcUEEc}Z|Ooc zI1xBKMm0FGx5CdFr@iy_pigDZ4LmnMB&(HHQA%H&ej zzbS82yV4evN=uZMrXk1k)(rCMQNHQ!yJN2ikGG;$eE4<4icbB<5Xz#<;5{JueY zaAB#`6|xGy7L*=3Ne}qBm-UQ{vA@_tcUHsCD0x<1e}6YqIYHH0XzJ||SB!$DtNsP& zQpGG>%l|R_N8Mh?OVuz}z5LJmzZNxEPq?h7$g2_->291aAV^Es>AI{kg>DqxAlu0D zjntqT&hl%&q0&5%X=_m~^r?r%`q$m|3ZFOlaDDTfqm;!OQmYVg>tZ#{BPoV!o%{3T z*%AI$HC{{k08Nac)c$=A2_Izijaj^GbXk=uzg)oYDeX!ckqEaszjMqn&_KCLf@TVP2hFB9BPtAVH0un7jOhpWlD!jMOn zu0$w6w`}udi{s7HWmE+jnP?bi#$>Xb{M`mE4ZD$Nf(i(!rgIYa0O#0T{63Kvteb_k`T%n>eW zk13y3_@RyT@Ug19r@mq6iM+4#tG>)mnus@;;Ef7rfTyVwRO==aN zzPT6LR>Hs6C6=dFr%AUTQmkvGXCP6eaQ%7~$LXi@AO@vW1}CpccacY%I9RoMhA~GI z={}~CbOVp-A)h_dFqQ1)5wA^-c3sA!7=B;btToNsAYgOEjoM>*qR-GBHXTL#nYY~f z()x54MC3H@%v!+w^XTkKkSwo+L3*|legLypH1rt^n$FYa8`xjGySB%Xu68WS#ZVAH$f&`kE~uC zkk1F(OvGxPj@BFw!;e!U^L-8emnJft&Zb~UUSqsiuSsW+&>gDPF<#LNH@8gTjOl48 zy$cnOp?^h1|44KCfY-%|>zeO#7sEl=oW98rd$fLt-tR>Fu(pCoyNu%JhOAvbJ#$DG z8cMg53Q0;6X^=eTgxKcVXq<0JAGf~f-bauQb+6>jEPS6bvT$?sv{c_dxtY9F+8&;& z>@E5gj9h7;<;t32muwg=6p22yd4Qx!g);!k5g&=uSs|$h6!QPNv8i;TlTo z4jag=D;(s$mZ`evpLEzf+B23`GvJul%*yG0@~>GfP0D2^l6biB42e0dllc`E-@D^mAO)rj4j!urH%Z{Xc27hUM z$-Cq=EetUeWZ_?_Em!cS6?@O`RW&1pn-0}9nHoo(LAf)-)jSgmaAB^l>(#nCksh|Z zY;IVj*KQz3CVf}^y&e}_Y4Hnyy&8I%b-O63RSD}UrqFZFbh$T>`B9-mSw2q2=sV@9 zk@PC0l%7Xpn__{s@CxrS<}>~9`AB+iu`8naU@Uz|RZ-`v9y?Pb%^}#X8Co}yDHO=A z(u9+)f~im!BqLW*=UK3u!~w*-(Y>9O2Gi?gM9XTQ^VUYX$c&(2@`n3_0X;dMCV2BO zn2z7@OE;r$s2r&qo6-ggYu1FnaInCaIar|QuYdcn^S<((@|@PjukjO*H==m(TL(K> z+|V9!-vrq@Lu#8UT0zo<9zK-{KY5sT)5o6TR`t99J!1HC5e2f>)F)YZo$IXd3<({K z4w_Xk)l+;|5BWUusU-qGL&x+>mgg)`7FlUm^Za8(z+35>WicpoI_;GOOBB^>MFPfDc)VUP;M@B-Izc+g#4i@d)k-N!o(cxWiK#91B1 zAWd(PYthk-(|BOb*9~1a?;s7G5q?D?V^1qc8jm5^1xi?rMFn#N3p|)vhC%mymT;{UVI!+0zzj>|{()7nrQfoFiuHxnTpk zMGBS1xVR)=G^WRBhR887YXc;T;r~>xwCV0Xw>GQ#=Z>K6k6`y)I6nsorC(5mHNSD+ zaV`CKo8SI>-Tu(GT%vSp4SFtL)8*gj@;~d+qYDD7B3;AOS>MzoSP%P2h53+vYt!Sd z5_7uN+4oVt2YI{HpFuZvFVv~FczbwiUH5)?JB1tIo5RCOXbKe=zw6S)W(HWBMmzwE zpbb^^+nYP+MQK3Ol;3Jk98XcNX-uV4BWb&v_vsR`XGF&-;|oQ0~mWug|0X z403VBP`Z*fXq8?3FwV?p;80G1{QUC!x+DLg1zN>d!Zk2fCH%BxBIxoOd~rx?uJ9A0 zN8H1h$--G=Q3+@B_F*YJiCAfb-z=3Dndw?n>#HjvPIucOoo_F-Xu>r5iS(^$kd`pb zieAbj&tK52xzDVu&l2PxD8a)rXx z-Lea4^u39ka8C|4*DTVz6aE5qa4e}pXkfC0tmV5|;U9}C!N&i~2qjj$D5``}w-cgy znOMZ&kIReHl!%zwk_1(<7Ap`xerdQ>OWMu`fWVA{e?`Tv-l-LLf0(PpuXU&s}`sE8ym zm73h%xQ>CGX(`rWEpP6%)TN$<8_Bv7K9F8s3pZ5s4-{Twq2F%py0MiaQdp)J8npFI z4HrLY>Pfp9PlJsLr(qOSW_A=2{eqA>UP@97Z$M2newcAnbCi;+;VDw8Vkpt<{-Y_s zft|xYOVlmVIWN3V)v7@YMB9{52%4`!{aW~p6*cQ+I8&PI-Oz(^g%Z7;{|@pWtMsaW zsSP6BO%-9x8Veo$mf%>rm-@igOdtEw6O==LpenxE;yoBoOCnY1bh1IBdFp=0ZIU>p zJgpdR;9aSB6X^mTkvqCndK7+x3Uz+954A@|Idwf1tGYKB6wi3rS4%_97s!2jc)hM> zc%`#jdQrs#k^Il3mt-1KMb6u%Rw?u;ct}t zl-c4D-b-OM*IaB<9VyuE8_gL~6jAlz5Qzsgz|urhE8;efdbG(JP0$ZvGYjwnb{O%Q zq%&-QU1}^{L=Mc}`RYOMZOLg`S;M+enW9@%^?Sb}%m+OhH|m7v+#Ok6n#O)h^i32i zMNkXJ(XP!9svd5wsp5ZUGAG`Xn7Wd}=>d(a)=;{S94m#U`6&0|X#{*akA71Z_;;&@ zO2d|=d|3%sLW~9BcT{USW>)yIiNmGcE6~Csg(1sEpe!sA1)*!ECGiXydzzZPV+vjL z|BQgjRMm2?q3lw4omXwXtAvl7^GXt|CZaB=@f2ZZLh6t zJWRuW6`hBMk8n?qSX@KPlaFQmw?VM0JfA&I>1zD`sa5Gosv}9x&tJmW>gVU_h^lla z?tj94(`$*@`z(Opuc-0wx}K07wK3^K%8=}{eyq;B)#R&G_0!p`SJLQw+CSa0J?(`_ zrHfo+=A1%JQ4KJl63!&G{OVVzVDti<&>Blv;Sf z3P_e|F1)h28qUkY1LQFc-W*LYES4(fH5}||gAuG$8t?jy2W1Vfve#d=mQ|t2O|ws> zW}cX0IE@0UL32&6Yre=g5?vFMSEU`iCWS#1XpHbrCHxE1t+EeB827+Ar2DT)*XJ~? zTuq-nK`yg3V?dq0OBr&tFkGSi9pQT=<#00By7lL2-Qj+!uvi*cGyD@RtA%%|O>T`C zOMBcAnvktw9uJ!!rgFJ|)Q!~Xuy85gx69I1SUnbKUDQ+s8a8O?tvO;Qe#Gw!6A-j2 zU9PS#7JqK-t;=B8(7&_7Mm|R5h{Yul7EmUAf#wA}HdlFQzQV^$0XQB;(vM(eWH3tB zxSbF%7Q%B^V3K4Ix83GO!;XV$DhVnS zwvhI?@D!8tKdPcP)ADInMPe8+(Vj`l1*gR-8&1&a)1-cSeysVL^zW*PMP=*yZ8{bs zkX42)+18N#jZ+YF9{kpiu<7xPVbCu9Oh1`A>S6XVRV|C*{6mK6HnUmQj#{bFqXjus zxh|*5AqBI!VIk$0UFF=O)+1H05pHGFl*7v~1l9Wd0(tp2+s!Z4O{CNEtm^w&Ie3kh zu2_^m*Z7@C7m|^UaBI>59%%A{Xuo_hM5_flU#kqrcQ)gn_vrg_n4o}eDQ$BkTD8}d zcCKyH?KW4XTH4XL8(CQif1wM^Ff>o%ry6c@-8Mmb8-UB<4_p@(r(4o+PMI=40t&s! z%xrPfO_0^fIl09vCcaDQaGG zf@*RqUCz`(Ke!{M5HlBN*pZp&R`E)qHrxn(AWFR@mDs9sx^tmupN|hoO-V8eYHo&p zjc^$i(*z84N7GXm?76DrAJCV1e~H?a#IYZ-F&A?tI7GQd60T~MjvcE0&%nO zU!564E4T1o&+{4Kk_~@YI;Qn^tn>Rq4c@m-Ht(l=&=$U|%n@5z*hiIX;rrEmSnY*p)OY1ueC@Wr3a<{(+v( z!V45p4c~V`o4G;+8>2P9$z&byy$ z)w0xF$%T*k8hTnJGRKj!wMRs1Au7<$EJHtk*+a||SQ%Xdcnyb3)y72mJSpu70c6WKSWEx22@MpTf<`mOHnA&055%?b` zbgA^ku!`Gq_>}vYznj&uXVCSMLT59^S*ccc6yi|-Gj6dQkD|dHq`n^EtGb_bKV9C? z>so`dW=Z5BSJc(8yxDdw9>^r0xzQy<=;pK1*^eSr-@61KW`{dvSaXHLr>5-dDVp&s5 zIsCdVbZ1lFAg}%SLSIQxd;k|3R1WmiAwfw8mNn84x4O9C-_)JIla_Ji@ybm)YrfpQ zfLVtKq$(^*_iZmFtG(=^3BdkwfOy;Yp^Tcw;g*PEW3d1CbrG~CA%Qn$bl?^is zf2?P{y8Cm_G$t4s6twE*uAy2*=?3kpTG`r2hiYwIgn3DclwS@##f!h#CFGdtX>~OW!IT*@9P#(&^pe)w5|QJzHGR zK2`kbB-&?d*84?^4KEfIJDGRda>MS)bf0XmjE6Pw9&98l$B$tOk$7q(okc1U8_0U( zN7_Skv{>DGtZWeVa0g{J!f7t^*F`1nx+No$D-sbdm^2x~X%~GdNrc}dSI(L*ER?*w z%eBs}p!bl{m9Oe=<92!&r&q;&;~KA;XV5%L6Tkirc^w|^rz`|A*BNqe*VVz%W_<*& z>LLN#6^6Llrcw=`&_TB1VPB%f3tu9insbPvMmY>ZjY_zPo;p6fL&YQj#+uJbGm7Cc zGO_`L<}{z}(MY+DH}%fQd>OYY;U&JWhh5aVQ2~jbG0$^-dXVbL^tXFzO{=+;S5kP6h9QNLL?Z9Xe<1VN9_GO9 zX^PI_eY|C;YI?&VDktV@_L`>F=Eqqo(JL4L&Z0t{_El;^9iAnxeL0L#Lcu1qK#L`2 z*P&KgZ~2%ND{DF2C?$^wd5RCGhd1G`x%rZ;`u1X4_6w%C>xyZJ_T`=5C6CIq@FL`? z<%ZK-)p(m^EIeA*bw9F5gxE$mWTAB~l!bld!0D3dvn^2j6b`^@Oj@Pkr}^bu-NFdf zfS6Ajb2Sadx_Y9T`(eML92w{q!3K68LlrwD8>Y*+M+6rCHzmraD=>t0qnbcsQV2>VuQ}xO;B*!-{F17|dksyXbCqpP z^J{EZJCe3~b0rHmNiC*Wdde{!SWBq7BVt@jCPE(j7AKZ7;r%F+N=~JF$5idchiU>8UUE|m$ z)=YOMsr?p~%O3m09Oyf134dGMmBVkSvQ)=R5j7#F@V_Zn3aNxYqWrShMLNdhmcP1; z(H`?kK!_}S!k3oBj0?R41@k#Eh6{L9QNPFV_oBrqZu1YJL1wvG3lDQ&k=9ub+kJ~{ z8cmlosBltXUqnSsEg3xL9*V(A*L^{sAd@9oHLiQ1!`PocO)=((O1Oo3cgQ15=#4Tu zTK}%2>_*t{%0A?JpgCmuoTo|O>uZAaTnjh+1;0zg*3{77tyQBv|JT%>nW0A+52$5- zqw0K%I~%*pZdixZZ?BH*n~dQ)@~VU_Tr+)D75~mG47dMULi}H-jyJ*N1=X|4wyAZE zdts)ImYglP&t$Y`VY;RHG~}5RHj$Z(5d^MuE8H;)d!T;`A9B^gCl^p@q(_fxH7k?S z+8#JTh2;L+GmpvdC3`n5bSVmZ`I_WO3Vc})FO=#3q_LcZofO$Fu884!Zj(YGYZ5zt zH-;aR*cP;M!;z-B^kMSEV_*;Z)JVE|v5I5uL)R@n2a*zDVNyH&627j7hmyvM>-Ebb zR1^A-bSpNgQY^i8w7b$sA9x4 zi3Y^*2)r%Zrsg}S{08LnT6<{rDb&AeBVaweLdBxiR?=~p&*xo87auMQYzk|sX$)U7 z`z7WdXLEBG9j8|@(SjMrs_ps-u0f#Y^3Jpb47F5E{Ir1ne=9d|x0 zsfV*Fx<1Kw)$p0W{*aVnDtRbVre5uw2Nfdgh0?n2m+6JJaFCbF;S=7_?6Q8oJVxz# zoXNe^B|0*p=~z3p8CiBsk65PSB?v(~=|ft`S}q$`=caqeRo;Vj&G#5`ZQ+8Zs#GQ3 zMo;Bwnj5|>d-4glK%NwK(#?+NlG?(Ti1TK+#I3naQ-o3pAXMyk_)ZAlId92#%75D0 zIREcsxZzj5k%SP7G5(+_K;yEtn&Q}zBVIcXsGcF-Poqs z!)87z&N)sW2gu}57gpWQS`rnxrancpzSyOm6!Yo97< zX`B@2l4*TCH(z$rs&YV?nI<7NO%p{^587pUE^B7B($kjY6UdZ(S-L>Y3_3?bf4&Ib zt{JpVvzTKy+hrGUz-Sik<(sZn^!#Yeye_uRr1Ms#DOe$}0$z8fSw5~^ujP8@kJLH~ zywxN+gtx58B065EP#b<<6^n;L<}a6cZ9W0@%Ha;iq87XuFh>tRi&AX{PI)V6bB6=w zq)CjWXX!>vNQ_&}GHKlN1oJGD!$JkJwudw6i*B{1%muT4LKaq< zV%SKcbu|ngg-m&fu96xul1`@SC`8rmm?k8+Wm=US*fF)Pc@4dg!tHJPoIJbbl&aXH z!RUhh4^1GmUSY&k!U4)*@T=+;?OA<1_gj$dRn(fcwb~~px2YWY1!>ui1?qxAJiqE( za?|n+RB#T92zhpy6nOP^a$s?i;YmjPOIq_BZ#ix>@|PJ7tq9*QCjYm1I2}R`&e6P* zhZbjHE4|k0^vv(|1RE0el-_mU$h=Vvf2oV<3TIWNSGC!Tez+XIdHi`@?DvbSSvsy$ zd@64r4`4hq#^IU-x-41C;oA~Xbt=*kT4+QJk$c}S+* z0eA)fcc~=XXgb0{zHZaFrIGA3k&M2eM}^l*yVKq7RYg_~r}s&fv1EN?!-<~2ekJD= z=ER|egFJM^JF9XEsE55=+rmZAz8*f}UIKKu`~F4x=6NVo3AdA)+Y`ij5 zy3L?Rc2ADZlDVrKdUV7#|>0=S-9H#bw)S{S7{HPi*N3f*w`z~ z!VDGB>?5;=ed^HahU3%^MCj_#8-8RCnyo1@3+pMjqRkGK@F-O9dVSvO6qd6J;T9^R_5Dcm+y%;0a|hz6g=TPfX%CHx;dw|>4Lf(_ln z%vQpWoZAZBDlW>C+fcd@vhgW)lhL#d?o~Yf6_bjw*n)6cKQ(mH9)yX=Ap_1o=>_>S zJHyv>D^Ga}KY$LkBf<{znB%dqbv6!=n~6Y`S%whx4UgeOZ0yj)K;|;PoJyJ<4(pQo ziIoUqG50h}Oe*eSr|LpbNQ=l&y5Bx?_3;+^m@wTo%`9?z4#GYk6#@t1YS+OER zyvz`41cHx^j)X7ve;JI2!ATxXHu_eQm+@`g?GBX*ZO)& z%b>;cs>&H~VMP{?s8ApjBc3_hfOtXWF-v(+Seu5I7xSIrxAh6_BydP~w6k4?oVr36 z{4c~76=1}wO^=HaWkq-Je~&W51`uUhcJU@;G1RE2?Zh4( zW!0;kTS309ml^*>%ozPt5tV;&gn9tEO|&to%@r-`D_Y&S4D=7=L^&5%ZbXF@-%xt< zB&8}+YD}Ys1<>PkJQ}p(O!)8meDu`C!%dRE@rCqQY zj~v)O=KPw^xN48WFmfOaru+CG(+&)_H$4OWO)eX`yEWyv2u!8n-a~}h4j(TTIugvl z#k+8uH4Iw}XXQ`H-%sl@C$;6>LOU}le^xmBwD92YO5u!w4a;K9f}>P^)c@Xoo z-6iuDN}n>Mq_TL_jvgDS9J8=M1_l-xxm+JzQ;JLpu>I5D1=EZM?HC zK1-aA!-Z?~>n_W#OUHP0<`t``v&=t?csper?NHWth%MEf@}-aF6sd~yiC@;ghwEZe zk&D@^u)p$hR+lAGu5JFY84x89mBI$X!}XOdmu)IydR>Fs^s%|+NU1?dhBx>~HQ?=( zo17>-7PX`s-DPgF^aiLopcfHSQIf1H9Ntpm398(3B_6F!AAH9uRFgT`%Ck`FuG$sZ zl$by}7P(Dx9xc$QwM1ZDZ&gke=RMm?ZET&EZ;M@2w7{jXIw9dC=^4avd#st0I!k)ls9-VO}mf%SCw$x@Kbg zvX)a`i*=f8_ z0F$}oZHQZl&RZB5 z9?-j0XXYnes!~m3t{68|I7u?w6)|0RwrO-}Qt4K64$~LydQg#$mlSb=L}$?b;?@ry zd)4jyyVPVeoIA^8sA^)Jo*T%&s=T0&dAXP|m@J}fPcFnWrDx8HEjg8d?$yj&i;y5p z7LG2Kxf+V%V+&UcC?0FnQFjY1EQbYwI@KssvS4u0M&Cx6AQVc;zjVDhvL61OXaQZ_ zM^*7_Mg3+u(5i{$ZH&;mh&#TsMp4VfG-<^o$=p}V-98Y~1y^$6OolQ1wJwM;7hDPd zQvV*UZ~sykS-9>rt6t5=weSE}hbOdVgiRCy4YYdNoqj;eWPzJn^9`-aW7tE*WB3(a zJOs}7mo(U9Yf6dpn9J;CK3@G>crJKnW*B8;Ag)<)eob-ZUc6clDHPXM_!$jGjFL|+ zyw1HnMs~=wOUl_d(&hcL+Onku&Wr9|M%5pUxwdLjy{XaS(LGIEleCuhDtVe{ZKdRh zEQMEjw;BGsE(-s_B)CNS>Uwt%9hJK|(`{sdXs6nXRc*U++QM7#T2=c{DvqpbNHjNf z%m5tDHgT%<82t}7YJsgyLep~NxEu!aZ6`*7c;2`996UcIb2f18n1;)F@rF^U3@VQ)g*jrPe-nPkeAC;)5K= zo-aIj6NyOs3qnDJVzNXe6~$2Jf|l@#-zy$4&@4{vNV2QcVnf@NG@(PwdHF2wRx2Ag zMBmVMq7aM0pKszW`jSd}g;Eqly9%|-Ws${P(Zp@Bee~y)upU28`M1pynWveK9QYu< zpx{6#}?JtRh3nj@+%)|4i@SOFlqm_*-D27r)kg>Ya5EUE77~~-7vdZwcDR;;7c@0O15*`pA^c6M}((ZW=6NdA3J8w zM!qN)bU{fNt4-7`><}JEZj)^}CYlu5eikDlEFDnspctG=JmHCyQEcFZ5HI zVwNjwQDn<;S;NV*b773|E3P`tbGO=`oR^#v5@Au+4C>V;x|8{zk}4A1WlfKTuIDmg zIQOkNvFK&j(#><}t}n`U5w)eI%ll=8)f`ID9qlpcJRZHAw6cZvNBMP)Ov|I9ECi=l zjb3{&M)a?l={I#pr};z+FRlw)<6=Gd0~JEWJk6HF2G`NEI(LOS^BcTq)|P!yOme9G zK7XSsEwfOtO~wWKSKTD7v4{nA-~1b;(R9zY1%H^OOp|}s(<62HTa`TQn!pyWnQ7qZ zTXqHo3x7*_^NyvHm)#9%NJXRnt?rzIFN_3txox{;X*y-QjlAGTVn+<*Te_ZRV$8y4 z)U_$jl&NiZ=K)%0^{kq&PZV*}!enY!DsqlV|LY&S=eF|UG)?+PiC4VZm=>O){NhBF zO*DO$hM6Z^!Wp2^@iv&0!*%98O@X~H(vuD&D0}{{g#F}O*E-8J`?@6UipB+tn++>S6@}ao&J1?Yr(oJefxrwGie@8MVfXwfKU5ar`9(v>Jf#%oA=$w z`m(lFdv)r?QJ2KbJkhZ!CD5xBuS6XtY2~_A)xevkd3nA%bGp_qrs_;J&w8(?mN|N_ zO{!}{7q+cQLNe!hRc*$f8NQ-sQzh?YwK|yOBg0KxQestv|0UH>PFH+NntvVa(Vnl* z7bumt$}%lxvXT`;a*^)#I1`il_w)|`{@ldl;b0Xcg&}fBVL=ou(B_$PSWS&94jL&d zYPu}fcpe16>3?FC^GnZ~S3K9b7A~8r*nH-wmAV&YbJVSs{JfMetCEnQkxMlAu`DgH z)8WVtj!C%_{yJCn+iE#v>y+G_C*`spS~vQUbCMgO94;rJnA)fV@`h%lJKPMk=*=bB z{ff3Ty1g(pk9+E?hySFn7pwiSLLo8_(+K=k6-VBlGY8fhQbI*KQ({GZ5G~wQcRq6>}h!8F#e`9`No@C z3O4&_o6(f~94jB$9oM`uDK#042(mjSKFQFX&dg zs;JXQL*e6|Ay|g8>!9Z@YLUXjkRgT#7~yq=9b0>AYL((m=J~dLGRgNv?w|+hBK9`w zfR8~sk@w?Ni^|VdyuLNpa<~flnyUG_mbMSuuP=|ui0iB)fH(@I=UvE1h@~rV4b^tE zURArbt6F*?0`lOCi=l)(Q*QKX{=tuCm;Q+%@W;0OS(|=$w(S7l`p?(_7O`m`kDLz8 z5m`@=Gpnlg3@Z)_Gmllm-Q2couFk?8QLOM=x+uhQbvn&`?H$x)aE#nTnVk)`tT__R z@oX_1px#M~BiWl<>wH|qQJ7J;;dLIvjyYUav}>lNq5ex+2*GfT?bL6A-ECx_PNPYz zcNCN4gCm-+P~l2(00f~gBibm#d&3mNx9no(U>c`9O>U?a?K_p*)G&NtR{aTs|MQMt zoz{^ZuC7#gm#;C|vBfIS^(JQ}8=quLd^(H##5_EBA9~Fd(Rs@!%2Z?wCzKtsF{$RA zaW#TMGHZX5hbd{(m~(xEHnZ|0vI$1k7FZ@m47c#i#T1VG$ac}H(RA zZJuQ9j~7+7(?)(RSg(ScpN5+i_Q{@?^oct&uUvWxzHeu>us*ehzNq}X^=dc?FKqxID0CjMcB=1q8#fAqDwXjfW8f&5q3W)$bwybe;; z!XA&dI{Wshe6yupD%EfS1Dkn*;M?sCJ+(@ncQ)YFy$jgY+3T^1uMro&qOD{2317h` z`ROp;rP6&qb*Oopd@XFJBuG)$9u?g2%%krk8_VHZM0W>6Ya$)U4-?7j-}w+!FGS@x z)Tvceg8k`EiiJ~kB1UtF+&S2QvC4TSoQt7~zD*h?6@=9McW4Vcu`9Z2KIar8YG@VL zwheYdLKK~dw+9c{_b0EM9BZW!N<|?gZVVqnQiKKUQP*xzn4exyc!9&Y26GwK+;0wk zgvP!_0LZxnI>JvlX*>cw?vdY(y-#A;O0qVcbHv=gD^6OXr_Gibp>?h zgeHr%FI`A!gl6-lBc?MgJ$a~$`E~~s_mdc2^-I<4O6b0|xer2cv{0rZp^kQUtxn_6 zo)0-UfRhM)T#!N+IA#&1z!IDv8Q1kBm5XXoJ!~USf}mSCOrK?(W2)oSG)>-gpCCG6#Qs~;ESGU2? z9025gs8+Yj6Fj-C)xF(P;VyDWVK0@U71bRK4KkX2s&g-^Z=?h^y%s>)5)P_Pw&)|e zqBx;OHZI0HM_Wo1j;9MeCE-n*S~q(TqIHLL2`d-P34nD`*c%MXI%y##fD0-k}v%$ZRagy12%H2C`5>XM1Q$pkEQhQ+wo28c}w92pBKu(;TI%TPf z;bq&6u`jActndoUR;T>m@l(Ch486UnNZ2eq1YN{m1j>jzo2S4S99hb{ zs0uo&XHX)$xs$SzH?xQ}VN^Kk5=emU(Z}PhWtLDRgZL=5ZgM6}rwo9HlJ`#3na^|+-6}FjnW3pp{aFqp7gL<>Be7B(VJ`O4xFFbPSU)Z z`b7&lg0k50Kj?w^Dyi^um8r<>Kb7{g?Lh}{XlwM=G-er7t+!V5{#xiZnBGUAOKNya zX?g)tKm|M6pP*aOX7f?cSw|*d73uAMI9efHUamFdc$zC_@z^;^k@Z>F0FO9q3Gvf> z+dcUnyia$q^6+7{>(jx-`lg1_srXrX|9ZJD&+iQ`4kp>Xrhev0H}1sy#~vv%^K+Sz#M>Z+cS~ zvkM!inRF+|o2QXuJv`Up?xJ|4|5RT0(qL1&%HcBdW!)OZkA#@E^q*o(#kuQF9Pv1P z42mWRj9Pe=rW9_ryJ#TZo~oYxqb?@aH>J~Y9;I8)(!`OrRqq$}Vz;JP*M>GQ%?}Tn zdl8t$xwfmz@P92lMH$ht-0*~iK0W!&arqD$w?>8}SMdHg&9jcahH3b8{z>5+=z!4W zc+XL~`6)APRUuE!hoM*&KBCBcMssXa^&q@UFfT$RkK|~jE9q<&j#|oW*EneENS33* z%Y3L!N(57VLHy?SNoY(BUthWdE{@(f?S)kgm-5)wUT>Q3n5Lepd9O-EvE^*+qm6~N z_DT1EzQ9ZX{q4>!R(MWi4)8o;c8}pg=M9ymQE`+?`)nT1{0%fr%O%7PLRl zFy@vMo)(5`9ctk<|AKMppB?7CX<-XhbXdkh8A~{nh^ylNa1YWUr<*ES)zk0fW=m9! ziG5RpU6=Q$P+sT^A5&S+nFO0Lr!uZqrt#IX^o2nHyR2L3qA?~iZZbWyo= z;h+3t40}(K6#%}3m7&))-RmtZAEB&9_z01y!+OU%*V1USMy5y#DCFZ+$GQ$uu)S85 za5FF4{GdUubi%Po(u^X?>W~`-g@@X3+IozwQFQ{y_jp#QvpBwmQGpBGCyBEbc)?9+ za7NNQly09TAt&{GU8<9kHq#}599+axS>5aiF{LJhCbas)GUUTF!a1YPIhE? z%sCjN!%t$kmFL!EJSOvrsMC^dA{?yA21Vr0 zYD=WybQ^pxku_uuW6axNj)K@S0ZhQIThk*Jj?8$&X%8=JqSvMnI@^bZ)IvKZxUETq z)Y`*CHhqsC(!pg@gTJQDVVyp!gH!i2m;Z($(bjVPbc2v>uR1ls7+{(RX%(Y^SZb{P^UUSRvX6mz01;!UImFbm#i9_ZAllhm_a77$Q}fxv-7>p6bhjqIq4yg( zXt7)FwWh4LUSmav9%0LnFhQEx<7_*1&3iRC5yC9U7;8<>wJq)4hsYTnKr_xYQALfR z?|ek@6>CaWxo5hHVs5e$(H26v6>dv?JaqR zyc?k^ZMz5@IU73G^IZcka@+7Z{c%A!ojO;FNI{(tE^o+H9x#`CYOAwC#n{bW{|hL% z8D6IIJGF6*iAVGA%$Ll-6x^Yx{r7;8<&a$ZGc2EtO227n|Md~ML%8a2XwC|UV)xQ72&HUa zi+_pg=bKz>;IPY0JhOvT6YG^aYm<7!aIOiVH8^5`EYuX{eOaT(LF|%;PbD42F3yqp zd9)!`E_`r|QLJd`5k%=Bt!b*Y+Iw%rpX2FVGST8nUv5g;R7&W-ntwf?NQIGYNoIU4 zR>c~-(_|SF)Cr;(!`w0_&9}i(5M<-s;U0((RV1^oV~k6?gRJQ`^UC4QSm`H z$N2KWRyXo0x=kU*`x`%n)Q*Xf$|o!FMJ_=GB~8lcO=d8rD6hBw>kr*FtpeL+NU4h3 zJUg;X6;+XJK2T+v*e+qi)X{OgSQGO#rG3}E+ubUCo2*IAJb>#(D%La`W(#DJYSTh2 zv&EZTyF7&AY>b|0o`G0x4>Aj?`{23%_7ruhhBtYK9ob@L?&xgg@q3i-X|y4yF@`ID zrj05Mi8`#D%2@kNd?=ND&fe142+z69Q+yrg1y6SN*twJ}oJ}ILDb5r0!U4J8lBWhL zs<-o{-{UQsTv#{1pu1c7zst4#SqlW;aD z5nejmk>D+_F4$>i7N1Pv*{5&}e?z%2uDfJHcL1hCgNnUUi0*QLO#Gw@JXw_}_rAH} z@zYehI9{D$o8pBW8!!h!gQ%pC~ZZp9lqoGr`$>3t_S2-xX@uiuW|DX91T_E zMME3by{#qG!b+%74x9X`#}(_}@-M{6{JsA5JG8LaBtjnY@lnLrbjQ&8xq{;#Qz4Xw z#aAgD8}t{>n|W~ObbDj};XM_1KH0`ZE2a}km3y(Xw+vjXke4Aom+T@WS zuz^bYgpqDlXr`K5jI-ZV5Ad%xbMdz7=jNG5ieNTO2CbSRcA&Y7fR=>`AgTiJOTi1= z`D-hl<^`J;99Zo5$Fd_t3KnpekVEm4L1bMlL`_mUBL76yW1kO`rp*b($F!rkqh79k z@o!z8d$P8nn!_-uH78sYc^8_l8$fyONs5|}1a*K{ROplCoVRckIl>`CMU#>Lci>hr zgj@+OmRGiJSkAcH*%U&>O)+0Ao!ndQ9!C#Wyw##0o@y%kx)k$V;UI((Sfll1(dSuy zDZg_GTq<@?76ZW@_4Bg7EGA>5d2>xUd_^TtR$I|Yr&50hL)w_`PV00?uZzq(vEIr% z{!npw(~7tFp82xK)psU>8H|O9=|<(5qI5BfkdG-*Ffac)4%^@PQe-2N9HGu@K`GuU z!b|AlqN(mk_kuG=;8~fq%qoVh>&!s&n0L2&8$z+$!=wL?CDxc=I}?#(+LBqsQFil{ zP3WwH@o*HKZbl^6f*VnvTp36DWI1`wRBp5D=9fKx%q|AJmfaJRm!ku1Y3@Zkir$DU z(=G~Su7;eTRBeHlQqp>qe_>u654rtHW(_uBb3O?!E!3Tp7s-fFwMYLdL}orQrKj_S z63b%g?egx=k;XRs9Zh)VX&>+5?I_J~zpMP;G*(P(sOAMSKP{p7to*3&vR!`d;XCRy z-Y~&s0OI*RGVr(9y0tnu@lzyJ^bZ)!Qs|*K5X}g?$-S=lqr<%UzAdccJFUc6SEEjS zLR*kyXjWaP9ii|t#elOsIZS2g5Cakuz|3Wu_Zqx_DgA;%_ol5`&#>;i)z;`ivM7`>x;Bs+&Ai# z*l_xU;ze1V602wxth386x@{O z*%M5s=Br$ZspRXopHN$!^B_I@09udCFRUG=29Ld)%=uLt`qW!NIjn_OyVQwGZBgY+ z2S4LzSG23~St8I}<)6YD8faxiR;Y1X9&U2W3s)@7+T%e)A7dE2@(9{*lg-4eeb8`r z_yMfN`8);pGX>VzVh#aWpDS3+XM*cA9~lLQY~d;#L2|UI&+PrX&4*joq~bJWWjbW( z`9sz2*G0NNQeL#8)h+{$kMQvJBa_WXOx43BJTC;JCwn*AwRc&+r+oiFms5E%SL#-q zS4$4ZbnZ#dD+=_C+NI)MJl^3~E`Gh{m)-wgQq{k%=hJmrq04u5;S?sVcgJoozio{K zo_2_+Yc)q^0c+J2?-0)NeY%LqO6XCJ^qQ3h@1wKNp{A-7>foQ60xq8yi@A|Dcs8?D zx!7I_gL%gCyzjldKF!7|sHK>*_5|QY7bJrw-MMLyGCds^6V;KT6D>)yrR3h=D7Cuq zss4ki!02ig$25{pF?0D2)@Vo+)g>a6R=C!Rt-X7ntak=@?dCRNqpGNR5AwB^i2HqB zYBM}c5iDF=T3QdK3t>_n!o>0Dhs!n4A=kj{@KqItK%ZVL&Ln;|b)VQ}fc2QwjJSV!g^%Mkdgy&hA=Dja#z9Hv0r5%h}8iw^aezJ(k)yPPzdv6j|~e7ld^L_YbU1|8ui5Rp@BX=2%4 zkE(q6mGBk`h!dTkWY_aEh;j^jj#K5wDroSj2vM@b4k01@CH>TC!_!}zch_eQ=3?3- zqxNtqrKs#utT}LkWkkcjQjptRl77``Mqv?A#M68_awi1Zc%m9rua4)!wRsi|nr{DK zVV2?N%HV&wkN0Dz-MGsUR6`K<`~ z_zmSpt&8oXUxRKm7Qj zmh`iBQ3o5bQaF>WX6eW}gdU+>tD0BR+KRohG6^tgq_>e(x+fSn?X%pnu-^&aXdc1* zdg9wHc(xJVpp%c1DWGbxKRcX(NNCqdUmY?-w1r{BvKSOIt+k@(8L#3LS%&v@=?M6m z4*;&{XoWe4O!r$CzC`vlo2XORLY|#6FXUSdS9p%Z4k@GXm3dW5L+R87dV%Q9@pM0O z5jIjqk5J4~4BDj|!zmD*gF>KO7M>=BHlhUVpaXAOq$gSZD!6LU72Qll3G}kH?0)VU zRaJhXfog9`55e?A>8x*nB9psj!DCN+cI3|&aLYH*mb#v?uuax zgn=tK2ulp~s5_2mPR~wHdRA4mDO0_?RA|ro-8HdK^Ev+S2$#zbB^_@fopPLNnl-L= z-3cMY0jcAFj|dl0#&O|%T0JZ0gWsFt<4my@UMcv9P;cn6$~adC%Q`r@O_Awy!yA0* zb2=S{G)FV_T)RH7P{IF-!oZ3Ji@yeE5T&H>3EAb*!yN!JIoAD@|NR)AYnVe&ZA!z_ zb;bxkhoezyYjYd4Z|F4FQIuz8com(=>IXexkO}-&OF1jfQol8*F3~}Kh~XODWe>!^ zyvQt$+{(hYb@{$C|BBkqofj)J)vONJqdgA|A!9q>>tQo(%{04rho^Wyv+8Zry~eTX zbT8wUErc=##6Jt=_-Fh6;O0%=jIHyI`Dd8wTcK0*M<}9S6g;$!D&fTS*Ox@04t4E! z)%5?Q0o|{EKY;n>g-NpD#N=_tlje$v$vum&pp}diCAcTFUxJ@n;Ene1oF&%3%ZKKb z%pEIfbQTUEpKY}rN;ktsvf8enyz-oq7Alp{qh5ZMf~s-?cn7r?u(PH1SX1G^EtsW4 z!g0D3R3F#gCUabyu3-0RWfr zm~qJ2OB0Rz;l*MxTWxtsGLg1hj1ojJwJJ>M)?NPEp&;xT`{#y<{q9XIs#qLicwKyS4 zlEY7?h5I3bIp7$`JIat!{H|M|`EfiwOwY67N->Z_>D5Ihn|{#c#1F`$E%#y_a803W z)G(SBKH>i??2sTjya?5Eq}-;Yomn!_uWwH9d56zzbX?BRmp4L-x;m#LJYnAWwq(&9 zvB0-P`ETg1q4M?Hvh{Ep#4!J|l(vdK?z3DZ=%?1DlY0=vQYF`0{7du69XuCN3`brx z95_(uMnCdstY>??a(locTk>O)V0ni3hrcjbZ}L5}6ytC-T@O7R5!N_JmvOdPsdTJn zDoq9xovqvcFVfyS&d%$+?>uwMx%bX}r`$mq5PeYCL{ZI=tz8vUc4W(v?R*p`aU465 zw01u0tg~5foP4}?;+Ggq0fq!S*bEXJ0K^mm91cJb1Uo^BU>7@Q#0G*Cd+(k5{r%1h zGQ0NvwIn~o_rB$n=RD^*Pk%sT`#CPq4k0fCben_cRHi;JYzIhl%-0;@!eW`j-E}xY zIa|sMKGy{cGog11h#CLtIzA!75VT8vCSIW zB;vBHr1E;Pi$U}Ll<*7`RmX4+pjE+Tjd3AMY^U)Tm=3@~1i+bHWv!8kE4r z3vwFjqmMna14li{>QO|!ENG50%SyNa{5Mx__w2&1uE>djCAqxr=LI>UEeKm!8{#?` zc)PNfkq1=ut;B4MMge<1Jz%_ysz{>|uU)c*zUtu$dck20P-cogYuH6k_Kao*K4@lu5h2i5u@-U^JmUogTZd+nrT={vN5{ap!jq;K$bYdFZe!!j z3l}nslowfy7LowNQEpxhjiGeE;x(dq9}|VCV|Pl&sY)rkz%8+T>+QYKu%{fgB<1XD-%J3ddSM*cg+Ii= z3e8FCwRjSj_&D3WmOU-|uoz%GXL===)Wh9i4I`zkqHIg8Dlufb`z8+%>9{3EmlGAF zn6OF=mcKNc4}9@N8gTB5THz@(#;iMyPEIn>o-+2U+VC8PKltTwmSchJKz2r0WJYU& zMB8pr`!2_D_DBh_AjLE=<+z8P2=+kgZUR1uBjj|M$tV7$EUV5aP#kvM!i^411hU_> zNHjO)&)dnsR6UrxywqjTeL+$K^p-~rYK996V33WK&-R`5nYlpj4C_J{gL%I}&i z>5gGtDTHjvBc@Y_M2`=RM!1auc7*>`E&O(_CH|%+E2lkC)^S9b^G$D9`SnZgUZMBi za=TmT)w%Oi`W_g3YXR~4W<|Ueuz@cSMVTLdMswXZFC7tnp=C+CX=eP?HhFjyn(_zf zpD)k%U;b2+p^q294n2X%aDIfUR1VI#U@ei;Fjd1D{tiNDjRfXQ@J~1b47qX+Hd_HY zv`C!}gC@y_JPU23L0v)rI`_;EMmemDu8lW8IZELA6}>0cY`l4{sTg;?vTX3|RR9yi zqwH9_)cH6&PCB{9k>M#O-xE$K1O;mw{63#k zmNV_LAq?+Va+}prN1x8>qs1hG@#Ul8p&7g~f<+xaZtk#uz2NxSS@ zh3=Xb4Y|DH0lKAb4c}uhx_*ugb!RRp%=Ua~PYjVrBObWRHD&45WJ~Q{rU|waF0E-B zA~EeL-Je$&!-W4!+<4la)QINk=Uo=43QJy;@Zc$Ywk87=bylQk2HBJEmcWpTn;Q6@ zV%;fojFfgia1X_N7+T(^9!tDbwLg|M5!rRT3`n+^@PLw@6uLZe~k?8l&j-Q$MIOS3S|6O2Uoh>5jP= zBbRZwmUh3t_j{>jL(`L#PsyVw1`Q4s zx&|KCGXvjW+Rebr(DZK)+A#3@&@%e>@67ERcZxzB(39f4VZer#XMux=M+a#9+X&Xt zif+YA)(*ko6)zEP5WLt)c~Qn#u6cE%v9*+NoIoeu_23d6^46MTto7zCo8a03q?w$B zXfVp0T$rr*+)_NEpsqkpckgygP94(dQ5y>r%%b?b<7!!+)gAS>x~3a7@xgv8?J$|T zaEJaYb@s=4{CC2-Rr+}XS9Tq-n?{ktz_f`!UCEHf9x+&935q#7f5&*-h;(L#o*Gc= zmaDl&+d2m;ptdvDB5rsZzLMQ08Sh_TNL!}b?J0YLSvKM{HQaCM(G$R-9B%O6`TCCW zmcxXWsUA)i^hKe~kG40Ztnw6aZBU|(t+3PUTi3(cU^Zo}p1lO>ZSXEbwqTN`&~E2V z@lwVfTXS#%nAAr0;6pUn44fm3XIA-Gy_get9`4A z$|??wUov!uF&&v)nZBWxlRjN(;zw7G_KE1uGOHpf=}u36Gv8IijSNKcuQGfL#cSa3 z;JBqT{+Ja8+^O0$ruFzHqi3o_5M6>U<&tN>kKzD!^|H4G~Fb6dS-kE*KUS#AwXv7Q&h=NmVdZRJ5c3{ z3f=ElFXT>#JSf}4MtZF$S6^g8vO~L@Z>yWLeEA{$NODiXkrJ!BL#GGX+`_oqm1zQ> zbZM@gI3TKMJ3@CuUe)*{%;wnj005wBVoics-fe)r0RxSxv9S)rw!sijq`B3W)^Pyf z3s`-e?xls^9X~uujW!Ka&GFz$fiwkajvz}Mb`EYSReCj?4|i!ci`d;<3hM<^D|BZa zQ|fXe^|EdBtXQ2vCNc0Fa7Z>N^w}Qo26j7ICSt-TXRd^a-*BX6nxbmP?dFSR7>$#S z24C1@3}lC!uP$ugv_PP>u%;naxFei`e5@XREiQ*%Pnaj8WI8t)MVQh9@obxYnF8)&IJ`^?kaYknMB5E zVo6KoeBbQ)cmsO^Dazx;3^xFZN;cB06*CM`CC31UhWK5jZ)Q&dSB&#Y+xluJ3G58Ya3{^8GURA%56ZMt z3~uQ&>a;ob12(-iUd?6H!{erZJi7vaB zWvPdU5;iICiTvWdRMP#?KNhw@Di--b=Ogf{c(=XsONu-5FcR47B%cQnQytYqj zzglAtRx3M!WhVlls*4wjo)wuvqS6u|bxZf8W;c3WHWb|oa`M*K5KumReO#_hUCto3 zGV}wXS{UVTw~PTtNel2#672=IGI4w|AThHh#>k{*+{Q9M3CK$_h@32;S0yC0hlYzc z_7{jEiyMCFv7c7;yug4wp25UWwABcRj7jGJAWB8 zTl1Ry$^b~(Z9BB>lM9ndaEO&9>>X4vlM2>@akOvQ72tOELek7dHplEr(HSI>8A4LkS^b?@+sF%0oW z3NfP%<~|h_cs@_d$4uE;9}19ZYoVQSNX7KtwhMTvB|*cys%3QR$XAMqu*leyZ=s9M z`juX>o>l~)Mqn1!jk%g4J$wDxryb#a2GmTcD>~Isw+t^QdX(Q1Tu=AqCjOQcN|go1 z;Z(R?3S269#_tF`Y|DlAMy!SbcWvs5SITKlBUmK1mRhSTk>t zkHgu98^V=YEwDy0d7$v=5D4>Pg9ZQv>f zgQ?l|NB;uYIxl@a{G7fl|8$3EnAyUx0m@emr}LLlGD)2>eR?N_)XTQ9Ng7oSfT%Zr zM)&w?Y0hyHSW7301{J53DH|qi1@0DuOBF`#bU9_cWz2cT^TYz=C4L`J!W=klM!#bz z#X>0m*X40%0fdx`XKU`wA?v5&9=EfAhl49GrD5g|ucI-fU@DpZ_hHkUFhP0V5{22b zsW{1}NP0r&Ngo=;!cN8WQx~Xrpd?4>C1c=IG6QkNAH zA3ZsP$!1BDPo&{^hJ)Q7&}LN1Lx&;brY3$K>|Syoa6>dOIL29pN_ZD6wk80G;5zi; zXE-`L^u_1oQBe!m1O7-sOqw$yJKorGY77tQuH-e%oqh5bmwE0qT8Lb9;k8`eTZLUCJ$Bk1NdY*I^grk`4Zx6pFS0=S(d^0DW5`u(|Iu#H?7bNRJ7Cic`TKn zEH_4Ew|n_y2;vpkl>y-iZRGDjLtu_Y1IC(XvT1!(;Gy3@s?eTckn)^I;Fc@m6&|(x z??p{ugBap19-?Yi?upNUT)0IPrhbaOzbkx4KZM()T#YB=q~9(#h5_u96F$cK7=uo3 z64!ipbG6?y0eg~a?=y+zkYpQS!G(`z znl?90E!`cHe`yoPtGsRmbv&XWJGpDdJf=U1EvzZ~x7 zMvpR>R>O;+53!~+2U_AZBq15~XvbE@=t`%v!vObo=m0uz(rh4y#gADobm*xZmtwdX zVq3nuXFuO|ggvy_9^U3Js%#Ic`92S<8IoB^?VSbK@wmIxx}9^VeKrmS+_M1bYfNxvjXjSgg~jeEIv9KvuWXZ;wS z@Cd;^H98Pg`;!D+X*yTXI>r93!gaT}U{Gfi;R>CYxMr0Y2-CoTf+#}w;s)OZ>78&@K@6+3tx|DN@y7)Ikh|tG2%7AZ)H|ri9cY;C z8W?K8VG{4jFN z5K}a+TUr)vwRx<-G*n(`CrL&{PtX!1P>Do)j1yH(btMcqU_?&@fxU7OGZfhw?7fy| z9P$SL0Fsu&qoBKh#m;9#i-{Cc4bA2dE8Zpff+;u$+L{#t(|z;`x-_ zE6!@+b_R@G-q;Fd6e(I0#ffmHV5CO;+sk~>X0V)+)kSGEGdZ0sWgZu!XBspwBgL#r z_(;o9fa)|uN6IK%Rzb2!I>lC1R!Y$q87G1Ua?AT%=`JEuya>W(eG*$A_`J;SaFPWl$8VMGuyfIfC(4N$>K#NsAAjMu)LRzqeVq{7I>Y7 zQ;{BKZCiKqwCc=#)$k{}l$98F`M4EeAur;jw$@i0n7g$lLot2=;A(R3td=(YsI6M< zM1$9T{hBqu-}Xsu?~iXBt;c-xPyR1##NSI^pGZgjGjiWyiL(uiZCN;vS>bI&M$^Mi z6Y?l7v_U4kYl&Vu0ALDa(q6=s_=-Q)>7!Ln-K z49skeI=DrRE{;kXCy`!<6DhEM^$-N0qSRVWgzm!AO{v7nvOgp9F!`!|5$oYfMqUoD zFg7)X973ZeD{EIDB}T|5r*6IYeSIUdv8Ivsg{Z5>zsrcN?TMC^RAO87z&O_zDIT?_+}2)aaPHJf4B* zAf3Xx*%0PGnj8hb6$1aA^R*wBzLIbL^}hiLK;{{U15`{okEIeAGEgn&fUF9V(jZX3 zfX9!+&lG&{XuJs8Le@s9=(ZZnNJ9cwJQ-$I(wbAvX6Lfu>)|98i4ZdieD*g#;B8_+ zxx-sd+)I8bTEB-~0*-&?46aGtKqtr$IzVQW$Yitzm zK4K`22kVdrY4uhB$*`(SaP)K5{qS$b%56`kJ}5u zrC2ORcZq?Yt7x_;yA-ZGxpY!>0h1@?&{uqC6$j%l{C4M9Wf8-4Mg7XRZXjO7yO6i$ z;Eb&~#=T7}IcEvUo2Xc50`Q7A@%fz^zLZ%hW>m;vcjAG3=Hht3OVTJ?fL2(o2BtD;=^+0lPa7Fu(&jGO z05D2BXAn$)gZScef?#QDrd}~X zV=?&O?hv45;n_AFcZ&YWBpAC|g%$*ZL)>dR!b}Hd*Y>;*Oxx`)-tB6>3|Mj|LmSKn zz+5uJ(+G@F9SS_|VO(b5q(akIan0(9P|S?x4v`?2fNYQPQ?C{QKohQ9EvwRa?^gP* zgaP&^k-aora4v+Ztj(tQpvzJK=PNkca|ZjZ5Gu~_RTjmgDsTqm<@uUFQJ+sY|QOKJ7DaPV^j7zdKa>XS5 z1BZ_d;TmbNNc_#Dg-eP;K8-i!Gqg9}0h|#&q^20-1Lfn9@W2PNL%sKcevP2$?z+4< z8)3A~qKi;HieaG4dLyyoLXjuHP=Y25>T<3`j;(bbtWvpGl_LpdM2Vr?#*oeZORjr@ zb{yjv&j#L%za6G%Y-ancCU66jM?G6NyU(!^pZ1MvhjdR${xj*=Ug0o0z zA$r0c%&%d4`Re3*4<+doWyqu#BE*0hE_h%252Zm(r_wL@+YGz-4$2PGb}bmd3M!zD z<3!W^02pXJrnA!kSR=f~yK@hdNtgtXj}Mv(X+xhe%jBI-PRo9I9EFf4z-BMAnpJ1Z zZ_io9NpI0Jj22Z&0IXxa`gXgi4zm;nK586+e8JUoIT>1a6x;qwN=rlXpbc?gS-k>EJhjGyQ}MLQ(xy z0TuO&9eSLE+b)Uo6)){{0FJJQKiAp;1*gkiVeYBJwGW7EQEPu!6m)T^`?*Pta68a| z|DsDP3>ylt8(#7@7-O$46xF+Uqt`CV?cvULy(iK%UlOX-rH+7F8P4wL z(By&eu-Ww{)}XPYkG6A&iOG5pl+#|CG|LT=a8C7{&O&vDv)$khKqGFX*bvhm&ZVr1 z9H!Q5x&Tq+XDqvp5wYORpRbAB{*?(H){=)m(__zQoZV^vOTmP!$F!wbzt)=deffUb1(t7ZEkni3@Hl7 zgVr64FB6+xvmOcJDiDun7r74^HW;FgvesLWJF64R;ZAT_-c)PjDJyhUsi4N*9p9%9 zwJRzrv0S|UF!0<&^NPU*ppWprR!-RL!a{SUI~Lu4-az!}+VdK#Uk}@WHFZT9viTg0 z(j3o+Xje&X2EKVKy*0vf48`FGnUi)BILz8C5-hvIexB6>6FffrFS%}TcY$ynMi9YW z5RD4XK_+0g##?!l6Rd6U_>;VCcGEuz=4hl-?(~adc$~JojF66T?p2a|2_^$?wF#N+ z5nfA$kUM?EXtI0*O`-piHyheSnypKVTvi}iOPFdCrK=!g^Wp=teCiLwaN^05lC>6o z!@t&sJC$EyU`N>b&fcTWHRK|54vnqI%325p!n0hdC`%>+C8JhDnyQlRwpQ6&(ORVL zVT9$Pg2Dn_LLgmcuyAl@4PB}ZMnMaSR|j|Gq<59WLUAF`E_<9^CRPSxclKJ}UuYXl z15rq}yk0=%wedZ^17X^(%A`XmhY#7KXcr*+*)2!u(OPckNC1hHU%;FR2NSK2gN?R3 z=)BaAauFQxr4YV!&R2e1TAgqHy}w1`UrsINaoE}Qzj3?4sq?xdA#jPzx(%;s!t9xSP-vy)%B7N<8wdpN|RED@nxC|eM&vDlhjpUsdh zoGQDl=NHNuZ|hoM#^Q9Mi-n)gJ8B8IH-(gf2$`rtL&kNNS+?2SB`WQ;4(BSoWqhv8 zU|(-JV&5dAls^U8xrRovhU~!&p83qt)Pi zkA0~+T%afkh&&`6BBh4dTsC~PLMIq+=~|2oJBTmIYsp`ycd1BLYA8y*C=JeXm*sd+ zby$d6M5AQP)M1Z_0YifT% zGtXNoI;$fC4=qKAbi%7T!34+W&TObpJNrFmf+hh?K8nog_)C}?x`4&m!3ldEEl}}2 zxf^X*u48OJ(}BpL=1K>L^6bm5CBxU6JQK=Nha7xb%^vh zok5`)Y?Sf+RsPjBwBT+a&~BssvT!$WNDh+{xmx717tKCV~LWIMq<*WHQu&wi}~9PE6M~&HaV8P2=>?P9YAY#W*A{u5)@H% z#Y<=gej}Oty(APNFx|j|CQ*D-ZK(zZ;}(`otw>Oi3WMvmk>ivRB+6D+cOiwEg9IxH zOKH9{tOs)t3`ALVWH6U0Kq@616hI~5oZtjX`iDL7npoNi&=&5Z)7zIrQ|J=y|E&5lHK4mPJAXyYhsB2kaoVQVdgYD zWF2E$RCOp-oG@9~0gt;<8f@~>f=N~T$5g}pe0gMe1K^p^!fn7j{`R%JSJ#Tx!~ZUT zL)9AL0baEO(Gtj5-gcawJ?h~@rn^9BMPUgpvv9E+JBMp^JAzx?1>g>v7G)lW{gF8W zmC{N-$6zzeuQxm}@h2!-ctTp3{`tMeRtGK(~hST#e|H*2f3tM;>6iJyFyZrE%> z-}k`eE*)(n@xZYfHj+$yjV1NXweeE=lQC{MyOV7~){3V7D(|t9TL>$!$b{fMPDfSn zRHN|&UZ(II60)-n%?|fmzzTJS8zJP0Xi8=p`YQ>u_SbTShgy-B=Pv{Wfa=7w#*KOk z8aNeL*?X~5?$oG8)tYjfJNccl)j@Id#j`krWOp8ZQ>@C%tVw&g#}N0#pJtBI%gjtJ z8Uc4%y6>Lm{>Uasvp_5oTG!#4X#MyAs6ivcozi;pOe=X+BFue=Uz%`DkN^ zxb=r=L)I3!uT^8bfQ#1iTnK>gBko)d3 zb{IZWw1Y;Z#u+bq9zSIHWtE(c&vOL_fN_-l8DHQ#v7U$x=)rBo9Q20Syi2kxUK?Ba$}pkWzd6X5NgZUW@pf0jg3mZUeDY6kx(T5DR9$^Yql@=}8T*BsO zK%zM(Lkc*no%}(%l*#x8-E&NlJwS8cK?kH|mDYEF*9VNQWoy01LxAFigQ@&~Lt%%<>{ zAyHG&=OOwOncJ5=#aA)h2so4a!AYI4aElx~%tnEtxnzqBWZgmxxWRa44%L-@BrV!Y zthaO*ZWa zRh3{A*-2Q%FZo1anopRm4rJH?oFJ8wzX^xr2aoTOd>`>;_b9II)6!O}w!db#va&eB zWuAjZ-M7YT0HuM}3ur7~IjrHbj9JsKf#lL4u8+?$Su{RX8yMonWOw3A~V7G#Ln1C{2$$+sBvLiu!RKgief1Yr!%x>A9%rXdWY%V82dxr>}_zCk?{}@7v z3S)G;XwicF+IYS9rF=O>bZjVL#7d+hDneZl16w%ix;9iYqG3Ifq$dI+H#QiAhJ7kN z+i(jD`F}+S{Kr4%)b3qAC*S;U{u@ew|A`Vz_+h4U#R!V>0GemZmiB?lCM2X>kd74& zPB6gnY7D;h4119he(m~O7BnFvP_|b+e9i&zVZu;OR4%^F$9idqLY(H~WSA3u7irZF zkwr-s)Cm@l=u}QX8EJ{x0ho|D0x&*DtdzbY2;g(Kq_0|330?~DW(Pduz^d0B=I8ES;xsNA@t z{V`p{&0XU+OxQ=+nG#G!vZonmd-w!Ox0(?VFA+}zS&CBb3Y?N!cXsGFF^SQ!njK=7 zIwa+hdK|TX%f%w>phXreMxVH__$gz?=0&VNF=U*EnQID*yFyBl{FVPRfJi z*vL6*uwCTwmt~G`z088(AVcf+Y}Qdld}M7u14N=q9+9RjVYzBm?zbEBwaeb6#w* z$x9w(ovKnGSHroySatarvQ7gBRC)NvYRf892|OS)O#VD$j^Y$~kb**+;#uIr5S@T~ z23Xh{$XHjLO6#t9+Ayn-ghvQHgXxsar?6U4vv@mSl}J${WR{PldEGTx{yHsFm&t=C zE13asAKOExn1FF5&&Aou8HnH6b~?<$bIe5AjpVj!+5`RjdZw$_+*HyW@~cG+|e$KV-!3=H8ye2P5G2!RizWyEGs zWoPn1pvJ1m3Z4~T008I z{2pe7491~)2p*{xo}(Y&4X31amw`;g5iBqOPXvG~p%A-gwsHr|$;J$o3oB|>&Rqn+ zc=d!!DL!&H^QH|*<77c+P6OD8%F^4(?dV2!FCP8rn7o%BP{))0Q;BVEXAz*BGBF=M zUU%^}X_uVY=3}NTBL=kFa;KI=(VCLL6N)m2&Ge>I#5ArcR&S-0`_g;ti}`uHYKea$ zZuG%t_H=!GfP-FCMD@)!c(t!)9qQo>uST)k&!TC44559Vk2MrI1>FHm)FV`jz4az2 zAQ~!;LCdZZJ>uF(K8)Lik@{`-d=H>!2AF||na_E&oe6V>ZSgh-Um)vV*3idLVx3=M zvjbqf2KL&tg`1V?y;G92)imVd0QjUC|$5N;<}R1_5qY!B;-aaL|{a zgf-}S@l@tzRisG9LB@wC^Wnt4afb(`O*l|YSh1@@h=a7hT0~X6{rw59O2H~<+LzOybUu!dX zS9q4+%~xKh;rMdm_?%uA9c6x`Ueee&NL8IT?XGFFEiyA1Pc_o!yTGPf3UxI21W$-4 zNRShIj#5Lj8;oLKS!?9^TUk;}KF-7{XKo0KC$m)SL)uKY@g*f-l#uLp5LrDdXDfa6 zNb}kr?q}JEnPu=qGH|4<7-hTLenla4?#>NA)@xfq$)2#e?v2YflT#;+#J6Z)#Bc|o zUh8D1W`cq0i!7uzIq?~ihc0Jk02y{bUY|AO1+-V*VllXc{5DaG>I;a=r1*2W@8m= zuT@iX!b`MZ&0-;eCg&xsvvMce1N-g}1nXS?YS4&w-FsCNUy(5Gd&NbE% znAIG`Ogdwar{h$B9bqTSsg18{(dnc=t1s7k;ENqen&BxLG-EQ7-Od`-i?VJd9bsd@ zZ!&(wOd;?XTkNA99|O-sq2~v9b}Y^s;gI z!bke!%Xt}eRPJv7zKNbuLMEfbi6EL#P#!^26DS{JLc#6yDVte;ywvllbV4^L6V}l( z)z5fAybIQH59%X+Ta>|O&1GVz*;9iwOS;{%o_@fZeMi(f#|cmpZmme|Va3<8z*I1q zF2bP>GExy{m~F9zkTdI>w2B(Q1mn^GhLm#>pqI4|n$up8P)^B#G!~xuu71gN4NG3@#H^}V;Esu6r!W|ujDR7}@AAQ9z3_{Nd2kAhjD3WKgelbjOmxj&m z44ZT(hM*-XXo;p8E^Aj)Wzapbj~B2U;{2}U5WLdY(9(XN|JFZAmA1C$(-m5m15B$F z&Sr`_6b6fcKSkJ3%cwdm#Y4QSK!f#hT{>^26f<&m!w?QaDh60~c-#te8;V~SKSuk&`&aIg_PvwhX~ttn%M`U` zwVBb+MzLszx#o?u4_hYMw3Cl3C;{>iW-f~_GYa{2?TP2}3QI(&hls>sVPO^2!2C>n zhao{kyti)i-Z~s3zP<7{1VVXM-ef- z0@?AZdz=dKRl<{?D+f&XI5l;cDRm9s;%Mm4@`23DQDS@rHx(Q6{5yNk-ro zRFHysqe>lM;8y{KqA2TFa|?t0iZVQLNXUAP;rLFLzZ`Z!pGmOf)wl(yihWnxV|+m1 zQ``F1U4Y5fab#RblQ z^$V_MSCaQhNm())GDOF~Q>W@8^Aq4r$LqSK?~RyRhfWcx-{EJKe{|p6OG&&$ zNiHPLCf^W4QVsbZ=_U(OJgKQaPy+1HYZ;#1oIlSblW2az%@lV6=en4 z%_Ug`OY+evn)G{>x=twgoe8ggXZVQm0C0!aOxnnrh5Z%H5>9nEZUX77#35>9nyTw2o!&cIpNK|BXX*KN6qi zOS_L|ilxG)%8medqNqyYR5zI!{Fe$LB1hh7pv>X&Rez?o;hnAXw+~Yh5sZE<3LBj0 zHA_+1tYJz^)+_%C%qldrn2|x$tngQt07H2+s7TBiG1fiG$jCgBYgGT6aQ?8NPIXyC zX&?yDcPS9{41)*19`Q32szcp(g366lRSFs{&J9daKwx6-N28dnKEQ$}cx1xl>Z9a8A@DAlfU8l+%@l1xTN$r+nf84fAU6f^f`L2#5 zHk3P&dk9@xx5;>pw<*eq zJYHI%#87yhSDNvLk6Ru(9TVQSQ&ZWcE9oJf-brz-zbBO3JNx zEh|@&>|3$Puc~cYmQCF8$8LO%4iDG9yB)qfCiJ3+Jgx4L&TPs(AOzpE7Z;800&Iq)0X zp`RV%FVUs5{13VTvD}wE1c9S`B6mO^E0cxncNs<72zeKCHj&9pMukTvst|C;GsO>% z(LGIV2m&RV#P*L58KdEPXE-4Gy^M%Bp14%gSb-L8zI_T#hrb(g4BDfH>yKNqyY+>$&- zs`5YXQdY&f{ez=un=3w>VQz(==#y{jp3*mU<>3Um#RI%dE8SBBiJQfA7k7FDZ35|0 z>Z9!3QsYfoz`DYp`gjGmCgRhKQj?j8*E1}XF;*83W;gJw>6A}MUYsAV7~l#`9Gls$ zO7FKqWNXpA2*8vWsJNRo^u?*D z?4KZK%A6xX8pEM=Xwu8d+RTxn6nz$_ere3eCHhUq3;2=D7tY`Zmfi|}LvbUAqBsv} ziKT&4jZsa14vg|HY&;7KiJSH#>uS1_WbjvwH z&{tKtspLuxr4+Z|W7%8pVV1`J?T9W+xMT-0*_s&BE5JleD}rXfw~?~SBpt`CtVE$o zIt}cS5_fOX?`7j2>MrOX6UZIOFR}Un@L`Wg{>1Hy=5H|stYp-sya9K|tJoLFh-ei) z0KObi2^&Tk>}Rqi!{ThWIuVV!>fcm_g+ju9(gHl0(gG|Gf&b2#`+sJ0^UZ(qcOb#H zK>`9H5i-Z(&%s8_-8vYjqYf=7GB6zPSW8b0%Qs)-uM?w_dag` z{-RuL=~VX5zH^d*p9f&FF^|j+H}faJ9}WZEVReR@FEU>a5`&l05T^jlvZO>!ODqfD zzrZZfL6v3+f25Gb;|Tix%2Z zZmzYI+JAKLw;Gt3tZ(sqqW5Vg+Gz)z`QZ`9=bT(pMc}YbllL(Xu#`P99Y6N;%Zm8S z6a-jyl~oNHd{+W9dAP~xLiuBukh~Phe&oEd>zLKme8W+ql6`8s^(tL2bb2)xJ3~js z&KZjju~1r$E8_*sfLH_B4n}6KY+b?6dU%?lyEq=cK*HIB+?Bqv|GOs|6fg4}{U}F^ zlXtnGJp~N8YXQ19v}vH*{r4m8Rz+Et6~1bm5|d3a|2Xt%g5|O}$OpO64EF=RuW7R6 zb%I_apJN zBp-`m05)slC{X7_09WZcH9Kr7)w)}yFC zubKAT1JJ$@_5qrzb+#->Z3uFLbmF2oIN2}&w)2sit>+_N0QeVY%03!MXNo*5t@=T( zVq9WrMu6q@o}~AnrYTE>(q4##JDqDCJeRCiqANS4H^h`;u3$=w#O;Fc`ok597Pk5S zd@;>QrOa%cg2%_Y1^~)Jf~zSzOSe8!wX8-sK~E-_pumn|;4)Lqq;X6lDcU2a;Moq} zKVf$qp2DzPocK}b)piD}y(vFHk%UK&qkr2kV2-jgneD=$x?02!*cLHnAyY9x6H7*U zN_N}2;P>bHb15&Di`wCkVzF0`@s6b2lmzGn`jyy;+mLwVlq>+!R++hizTfdm$Uj8L zAUmMW6I}13*7C^0N2#S?Z1l}~dLPTLlBV?ZgDqa9Hlf$=sn4q!jH|~}T9%i4#N@1& zSX~PzXw&ef(-batbWvA0pSEm2!#)Vc^>^RK%GDJ7T2`nZlRzt;bf0IlQm{Po&t;$@ zbBsqtv}dNjdZagQih;sCs{V5=qm9ue;9@R7~l-(t#WiSmV`)nV#A zd!7!O;VnSg!``?YeZ{Rj7OO6)T?zy5u#s#pJDe&>O|6otp;Q(cs=}_61Ova@oGy|0 zq4MC5yvxkxi7AjyLf)28ws;F`pp8gk@$7XFr|L*S$rcKM*I95E+$Td|U9>w7(Q`t* zC=D?rr#T|*7ZiAW^RXIOsRDt>C*wxu>6b^0!0Jaj5v2;yU;yfxDA-8inq2+?5bRJq zUXOhRnnEoXm}pNWmza$F#0R4siR4W>+_McVX%C8^=(aIBMK84ad7++%A#ZPhB=NI< ztWEr&O9?umEvj+8vqH018u6;6GN;9fQUO;2ZCgZ7=>==y->a`H8I%zjk0^b;jo%d= z=p|vKqYINwkhDa+P?dvcMdjX#sL^-88(YJzdv=b_aw_dWLnXZ^ZUwsgFoVV8iy;yR z4(5nIn_M9Gn09-B;Y14}SHj~o$T4h3yLdN`p<+^{-I;^?R;vvjvjd`E4wt)!bsjLA zxGTKEWjQ>+hOO9nNIVQw_q#W^>s}$|Nq&WctQ-!|Q8|1|e+GS*4Oy0gyBdDUNH9zI zg&*irfyV?%WT%5I#DMq#I-N||7Q<{^AK_m)=vlUhpK}}|ySUX(9k0IxLm5rmaO;4- zxUEZH@H}Z)y4UyV`2A0U#c%2f5gX0T-0MzN1$-ptz4BVjoL;3FP6UvJu~s&UHS7^7 zoM3X${Y;xOD!Jco?!OCg=4(re@XDzo9dTF0lh}ZqCXn48InGuctkNDvSRF|b1JHu8 z+2ME+$iz92Befo)3$~?Lx1Wx95pMP*9^h}M+$S@gW(yLMcsZF)Ib30YJ;h613No{t z>M&F)lPH$tyH1-|PI*39X7tDEQh4+AiIuwKCW?cv`7T{G!#x#UW5Q@iM<3;{Q5CR$ zD~z!IK9woie587Z=Mi|GSfz#u2ahM4xU^9qU@Ii)W~L`50GbGKBlR*J;`t1vvCMQf z025<8PX4+csm?oGUauK0V>)7LclwaJp-*4Qk3R`+j~nNbV&O@8kj-|wrEKPv@EG98 zv?pbmBtcU1TDmkkm_fruFdO#Le842l?Dxm%<8xihpXU)~h1nz>hle<2q+wbIxu2SM zChNn}=}o}uKwC{s*&?M@c?Hr(7gC!_IrJc7)M=731`RR;`|b`n$_$0Ej}SZZmMODA z|7uIe4Aik_5R7S=@xng9IMIHi36LD61Rx6AR*&K+q~?I$_Xv5SL+@jF9S~S0J=!go zfQ}SK$e5KKc)LmOR)Eq2B9K{=B|ve@ctA*_qa_mM$(E)W+$8SnGG4Nz2ia~)dumgc z7Muon3y83ZNfY}CGF246L)kchCw6Qy>L#3*_s$)3jOTso?EzzX89I`W#Vc7A!GM_& zKZ_?R@Z`5W6er>a(270RHuTrpsC8juMTEF4;{k*XeBqSRNPemXglJebX@{eR^w3RV zaaW4x;^msmbTvgfEEJr@e|=M5%hO~8JQddfHp{L_A~g|+n688+91l+ay?Xis_LNFV0|KmH7O!vFA;^Qx(^9Y<*wTIZ&u zG7i83e}=kMT(Uc{Ta3coKi|1NEJcxFRYeO&;&UJbh872N3Uf)vYoHupKowUcFS~%Z z>0E`h97s}Mxt(o{s@$(2n^eOy44h4_KY+cUiQmxGBYp{VPf;@)8KpSKVK)5~%PQ+H zNtJyBpAII7A04wj;YEJ&CKlT@=4@yOsU>LyR)(T+Opxq^9&{(|LgDy}?F+UE87>U<|9-yd=*~9TX;~Kh*!43&ZF}%UlszkLw z2eWH=NQ_!TE6AqEEm$2LN=gi+l8bDEWOh%fj#jl-RD8*jmP8B0$%4NS5gNqh59 zpXOtGSNzb#Mze|cGh;L}C_4w^$)1^y^t(Y5*Z@mLvhd3hWH`GNxRJ|)KJ-k`n{}^p z3|1~QR-6&$-!KX(R#OKOK^FFEu1ezYnYMOaSqXLL=+KDaBRA(Q;XQnmh!^+7Nuhw_ z!FxOCeMuUC`TDFBa^ArwR&DcQRcE)Y*qst3n(1R+bm_z* z7e<%9qojG*>Q{w@{nZ#gz)Jd#9!kXhpzyz-KkJ+>q6y231iu|NH2`3JFY% z#eC*y7BJhI6d!iV5aUc8jkNk+2sUbAPveTfKmFv3IA8NQR$Ix;pwk{#6q;Iff{xgVWm6qfkcM9A@!B2wQ6z`j25{8{8mS3L2&xxrUWN@QFG{na_iaRzDM&>QVG~Gd#h# zl0Svc^AYDXLEdN^`NmqFa~l3D23VxJ215g0_JTXIHQcumtMvj@}CcBr{$;c6BbGS4blWs!1M1Q=Ur4=b( z>k~w&^^}fbC_cj+xR2YVlmx@^On-q9X=mGRAXHT*gi`o-`t#59CeW?O4p$Z*PzY6j zAoTxrArXV?)Y0FWJ7Ga4OA$HmS$u$?_y3S9(~;hAxtsbIy1RkzDzY23B;dH}a8o3{ zB6i%q8KyJty-@Kz$tNXwiPW8!j}w+Xc1P(%MkRZ;xRGzG0an#jF`CJY$~wBLp^GLu zeQ>w!?ABA-A*rro+FPq4+FTJ6-*QTXx@H3|>1Ln)a8|*|>mF2s0*CbN+Ef((&h@-Q zf6z@94M9bUEc&-FcrnWiHB%cvtT65Z*^1)iT}Q<*{+c3^%E|9-g&y#&^>0ohUy%S` zc0q-@`nOvV3F7GA_~_R`SH0v__?F%=Sek|3`P28O5uOkZFSC1FCE+)FJ+yl*U5Fkd`%@&5=5Xu1KEZpFk zrQm&Swd84A95Ws=JEs`@Klao2ia$z{`T6hv*LR;k`2TZN6=N{e_KN8-J-BRkIU(E_ z^9KN?5C2hwfS*3Z%Gv>~M0~kd)uA;{%(bifl)_JRZ-^(NUFf^ z=oyd7DdSz$u3AE-;dO0Fb>zR?)_Hrkk3$wyIntcfTLL~|7-1AEv0)EX|UNWbPsoENC zzXT<`pWhE|1d+aqfZB3 z!PwtiHX~`MF6g)d(t9C9tn)4PkY=tt|Zg zv?6ZSb>WAqPSVrvDgUv`0dh0P5YE+$xsRMep#GKMS>0*Pq{Hx92Heb&H&msOAj#K z1z{5}cZV6aQ%%~ylJnY7fnGXYX8VYt)My)CzGhWHxeMLbdWL54ryxsZOiQ>`bl(fq z@IOmnW{73HdvYE$@9_g@2zK|H>jk0V6c4&cE_fsp2W#YmGSc%tSHvvS#!=xdpu-1> zhr={uYllBt{yF-n4bs*{6o=F3^kqm8gS0Qc#AYf5{>epVbWY$Y$ta`+N|AM{UdS%B zg+tb8dE-&&EI!1vokK-J@b&yCwtBpt4Fm}``_H2>x@ON46FFu~-Uebru<<$9hk`IO zYV5)qPvDVI{X@S1_V9*(e5Ua{i1x>6ooFkJeI=U76q|@d>k0q z(?OcieT#{U7>5Wu^spsfs~%ZHp(6Z;S@>HsM}=$Cz?BvWuo?XMzHEfH5k`TtiNjN*=3M(Xm28WUW ztB^!sME*Hec09hveBn6KPGoImpiXEiE&?M3*8a(OsrLqZYkj;CEGt0c$_H{h7-~N8 z#awd+1R_n^DWrJ>u|#aAS3LqA#4ujbD_eM{)0xj}_LN;79%oJ>09Z=0HyBP=!RE56 zIv!Mvgmsk5yiMrxKO14CZuI?|`56D7@)Y#1CbK9>Gv$49DsvIb3!9{>GL}|}@PLQee=^ac_ z>Mbfi&%Y2tjq3x8W3`O;S)sonM1q+p9SyX;d9jasSOzUInq?YR5*ivNkPT}%6PKe8 z@OKF;O-lzllUJZ2By1o)!a&Vq4}Mh)VM#l=loTFNoI(frAdqiaBSZo?mbV_x_Cs+L z9glh`v~Ma9EjX#e$5O5=2ehqYZ^D!S*rwtGi(jBG=U%UbgEVegx)EMr$|Q5qLB;|= zI$+Rc8Kg!TvldBQYX)qThWQ1XeKBCw`N=C{*G0Fr+&ZxFhBB0*BtrSc%y0-cFXAm) z)t@p)tyt9|FdSo@Om5KDUi~C+z$*-as~*nj25X_@O-5ntOkQ*5ChvgL_(Xw#VhGoJ z6YQpkob)xcVpD432v6gqS!!X*yI3*5PU zA6VCdhgNK&j`V$*;+4C@OW>P0PClLe0{G9gp!!a(4=>L6CR$1tH!o>;2RSLAvgD9c z8l!IKXO_K_!!3IgfLFr%rn>Udd!yP`v2j3Lq=Xb|$XqHMj?(p54n1ij7gR`9f zV_F4Jg6dq5IsxhtLyhUdw9Q-!ddcTG!Mx)N^kwr|rVZmCRLRQhP%9mu1S z&WT`;(DJDOgCU!?h{a%L*zUF)ySR7&{a=8Ug8jZ;V! z-IvmSQ|EX~8fImrpXj;EZJoq`^0Jj))Ix*h6QjutI7yW7OoR@*2f`V(^u%RE8Nk7X zqD0#*%#wvLRbKCDs*Oub1{JP4FuOk90T&GnnZ&r)UP+YMf7G8W_?o=6&g;7kG&3P% z-z64$peeU06zcA!$myqW&=nwqyoa=h1xJ#TI4{aje1L^vCt%lA)!92 zPJr1N{)6Cenc)O2iyUOK@D{Uj)MKHS{GNWIL5lLh#2p3rSJFof-TbfmQXGi&(_a}2%8B}vvz zxWwj)|2kwaIflVOVmOd;qE1_cG2NW#mXnCBS7j;JM5rd?^D8t58quD(8p0#&+}TuW z#l_Nr>-dCmrvEpMlBPJ~{CrH|Y5!e#w48TO1`nMMw!>DbImvFf69_ho98|lwRtW%b zn1RT0gMyDaxUSjQ;*EW3-~|Jlb+DUXENrJFy;^+;NReR}V0H`o#`Kj&SjAr9 zpOJgLD+7JL9pspx4Nf5#$j2E2-NSNJR}#g0Vc z5oaSX$Tdz5k))J~DxA$pErFbMjH1T~NDA7Jf5k)`6Z{!Q5ilf&m5y*#-7s~PpwT!8 z@+7qbO!Z!Vp#6h?1+JVE<|)a-!uU!1AS$PB^hD~cwTtBy&>pS@hOUO$k|WI$tC{1pwp-cf!;vA|7mn?r`e`6p`vi~c<}I;te5KN+7?#Rc=OqJTRX3Wx$<%1ke3rm|!7 z$9@R8ZUbBjFtW3oVU$r6Z<0|2c(WAHboDH4pqK%rSGo&0NE{LWp>vLcadj<{_s)$B zk+sbg*v3|pJ0fI}@hi8)nY3cA7(h2w>dZN-5Ku}WHMD_$% zmGB9R{!3{H;a;ZL03ukDy2T4mnKO^%zz6@WI+GN2Do>LX`x=z6&~%8CMsp!)1)Uko zU-&ajMHQ4RkZb_Zi-QL|OD1Rqw{I=W$-urdT+QF^FwULiP2Sv2lk>u#2&8hR*JhGf zAgZRdyVB8QTHxu~_35IfgtcapjsV?1LQ40Fwd4zX`G zZsZGepf7O5a82fEyKqCr0jhQazge129jnCg_~wcH)%F=@-QGGj(Mp9q4Q2Q9eKow& zrN553TfZVbhSk+Pr^$h`0XTOd zf~`q&$&`lX*ouejvWe zG__@=xIIn@Q|sYTx^5;X$qtSFAe~lZ?q8WZJ+UoZq)tn_OiY7Sz3ZY%M#xKAus#QJ zO1mXWUaBafB;sk)GUy^f1fpMhMf*^cZIGEF#wSkYtOO!*U_Qd&lWESJG0vn4eD{lN zzMcy-H(Q&C;xU$TAn?HMHIh?z;-vimBd>+MJjNfFsaO#^mR$lBXoTB1tt5ab_t@Ha z2b4srI;yBK6*CI`J^dU8<**ez!kKacTvzvo;>YX>xlT<)rQVjYZZhs+vL2sixQ*2n zbEP3Yw9>1j<1#+NY!7=>MMY_&sFc9eqK+%=>|(4C@H8?pJbSPq zc2V$jm`GhZS1Vf4*g(6|soRuqSC<~CV97*0$V?!a2Ka6CBwGqWrLK5s-|s~A@lkZ& z^0HoUZ6bLrz#Nk8G7rTLChB=<+y=r5dZb6<$qY+lFCUzC_$k|i*5E4eO*s||zJy}q zUS3Csj2&?oO_al-cEMn?yOr9idKfQWV1*NvS1Ja?w=xy)!C)N_EjgLn$)y^UV^#0% zj5`fZg|e&i=x+LA9l?s(;rIeruQO|Xb~j_IT125mpTXmd_-?w8Mt#zWzood6`c8~N zI*n)Z!R(hbpM{rMA+ZruHX^yOBA6WFK~K7Rie7dzqKvl0T5pNZ+|ZjZE10Y*8?P36x_QVb%9PBVjb-8&7kl-67>>pr zz>X!R@T(ZxQux%vVkM{o&DhdDh$<@V=?P)q=0)jq1uew~!R(c|lx+z0I1$Y-TMzog zV$nrQ;`Mx?WBn=$Jdo9zUwF8{zOOjb8{swj%)*p+4Jh0U_X3YEhdiN!mgonk8+!1X z3#?GrEh^Ecyq6bk9bR4CX-85tM7O&!g={Vho)iCl4b2w7Q!SJO%|8ZUV%5W;*Li_D`B>X$97)5FM$LVnM1II z+VVWOhT$oXnq=w06)&*hB`YiJ+XCGMn=8o^`dVO){E0|B9M1-Uy{Dv6R4^f)vqd`A zw>%DZSnrL%m})?7L6uC+@_-tmBkbU)^Hjo#(zafd76IbV136h*=_eAWew<7OTUYWDMn%>UQ%2p6`yeNGw@VNBBl zyh`4w{}no*UwXz2V4(H_+penfe9)B@8evCWiaU?#uTh03Ih3>5j(k`f z<#maU$=({`yQg`SEF`mxLfqYf^7*PR7;JMI(O$MG%zWRyhhO6OXIxOywnFnFxH)^$)KdP!0*~7C29-Wr z#U0i#J`bDtUnt1oYBo{HQ+g~FraycC_{PysoeKt(+!e*o6tlhGxFeZv+&UzPy)vgj z42baS^Rb@PCh=^xjp!dwn@@ltkOg6q_L1GiwbO@|$eq3HG@fN;P^yLz=Ghf)0vB=O zLf6$&hUd=k3Lk3`y_bNKvMMLb1yj_)uoCo5CTQ^sE;9T^n=+0#l&#`i#`dOMq=U}l z51z=3Ymov`+XEn6o}@)Oaa=^^Tu9JPz4Lv&)6f7H>JRGxPU{}nZQ0JkWJ4l?MM&33 zazV*1r%aMlGQHA5Od$*Rv;0dWv@Q%>_2{uXu+yT8_R&8(+TPH1SFTD(>{31*J5q9l z3Ush3oemXmR;6!9?B#EZTWGjeSK79V_3|Q(_~>vcmsRrz)g%vsN!6md*@Zl7mzJB- z3~)io^Dg$Y^$OWdiJ7{X#>lcyfPtNCVN7+!)TBVCTiS!LU|PEYw5k@_%d~GOodZ&! zK*AY`10bGzlzvHzFf+FJ9fnMlEdy|MJ#@yozTVFbp~~g)(PQ+8YH@MYuS$ra5xKLN zeOlw3J1RQI-FNB8{GMJpP;Ai=cJ9tT0Ze&NC}2rDC@vs#7Jx`m;ogPayOunsO5d$; zHHg<9-ZuI@z~d$2nB*0K=hq9gYZ~p6GvWpzTHC-WKtgQmboLG~YS_;tIm+yg`#95m zn)SzBj6~|(0iW&U0%=>nSP6HUoNIJP5YO)#SVFMx`c>E}5u%6bR@8brp2#d^IahuX zCFQRak*W}SQNB+&8<3mePE1~$iE2L3-(!yQJ%dQ@okO!D@dgiDQ=g2_8ZKzz6qG(1 z@1z+{Cz9KVb)7Kq%~nsw9iC3v2e8Q3BfMH5%`Td^R%m2)@Vk2<8%aN5C){=?ugg?* zxG5fWt_1LfauDwcSAtE^j=8z%B`5$ibQ%HimzX++F}*yet?gG+=sOY-pp>GhpYe{j zPGf{AiQjyMiW8b+mQR>4>BGrv=$aLuk1vYCrA2j+7&=o3Fu9-}HRgj+6PXw(1d?`5 z1P0(w4;Vf_~CqRe#FTG&$3^i|d)%EON~)iRwIN(j}kT^E&MN zV+&46(F+8z8JAy%yr~l5WJ^W;DA+}dCDAHXjsYL0*#TJH>|6>0j`OCy zh@Gq}##LB?7_8_3_jq8lU|Fhe$0(TFa0BTT8@DKvq@K`1ku|nO|Kuvv4d5ht#H<(| z>#RS%D(MQsvm(96l_6ada?DMcTp=3Hf-_f?+Vsi=-R-csP!quQlQE)&)?DbR$X0*? zkdh;`i1C|k-+~=fOEgl`0^1v|;`9a%C=pJyHBdP6U3VG{w7t`=tR|2Sc6I+wt6|k! z5%?%HAmJAS5TS!QF)hu5`S%HQP8*UXgDMlNC}yGn>?&P>Dm9JK3G(b?TXq~}S`^UK z2_@|!XX5N$Jb}6T%sMNN4Cik_f{CI}rhnJTZ0Mb8cQIgwW=Oi28FWUv+VND7MB!tD2wi&!`P@RTN@U5$*LM16x~M zIW-`pS7TtHE;`9srJt--Ic<}#>SgX#(&_C<19dOj;b2am>wMy{HP_Y7OQBCqFlRgI zxa4j{XJ)apdFm7tYG{P~zMcGfq@wkDMsYNV3A`HBu%*g|6uiAD zs_kzy0rC5ta{gE$dR39d+59A#wE3{2zzr|f!k`M_q8ZXcDTSN%>foP>8Ewepv1x&1 zspFT;t;`cto7UVZA9iX6Q?b^Zk+p|+Y0yfurh5Idtiivgjp7Um_PuWj0Lpld7m>K; zrQyxU{O1Udm)K6Dq7@uJ|2JK40;Okl-g(~omisO5TdP(Ufl2}dF*u7SV<+8NoTMF3 zd)is1yU(#>A1@iFyHAqInV!i^Iz63FB~^VTl`$A&5IYDYRjDK)l_a#;AY^vi>|mDw zvxv=rF^dgm6U^`bzqcfJ_i2vKOMUNhm*+mubD#UHpPVok`;tbrfgWsNZ#mj5I$v(f zF=n~q?r&*gcNv5XA*GS2No#FBc%bJpvnXFc#GC~fY)dg`ofNTcmi1AA6+Csw$8wQG1NS9@<|DI-e6bAPJwf3OVee z1>Mbar_si6;|Y9|8|H3F&im5&jJ@s@Hnu4Ar>OpA>KTBxwWu|z>MfJj!y9~F54X4} zkf0h~0(GGKYMPi>WUaNb9I4dTNIqnHdhBTQv+t`_BB2bw_oyjr!c>DrsaMiZ8Hq3U!W9) z$W@V!BjwC8YTD7N;R{0hLRv1mww}OTC91_D-Gei`RJNzk66mYpLMEP_CtxkaJ}DQp z1D$HxqAh4wCHFgMAQb`)_&gV_rvP)W)9JS{Y5n+F!fj-HK zWZlWTyy5&4<;{>yGdxM#X-VzyFF`(K0nZwW%V8-ps*c5g@6vH2Wyq%5yZ6ry zt&bJlM`HEEAzVGU{)C<-7yN0nbM>c3h;u!-6yE`&3FKEbJ9*Agm|nw~x|i7B-Kr^0 zXM%`aDSNHt@D_O%(_0%ivAiepynD1q)q06bVk(2)l^TjOJ5e0f=JK}wvU4p*JMLxml1-OSLm-#3T$ARHwmIvX{$^wX|R#H zbBXxOXV0fB1u;X3Cmk>g3bCXzq%&Ys7+(uec0~bA4jFT%`dU~;yA|nP^Firv^&;z3 zGwu0&sYT^Pq|m}fcn)~w`t9s~jhr?DY&g%+a|5AUrGLv9bp^YWVt}5oqgZWvcGOv0 zBPqf)^zW+gb%AU!*#X%b2NIfpMx*<8YT*zswgnYOt(r}@^s_EaWzBliKXRQP!o6<- zs=~R$tA{RLre40~k0+sLT=T@ZSNzrCsB=Rm%Zm_D)tYp{I`e#y1<<&URtsXj;6{vf*t#A#9;NguedCEvB zKenm?Zo!X&)nJ57662rVWI`5{h^zIGR{G_w{M{=&`jBwuRPAgw_ZDc5I-13Kyg-?G z&*fi;DgLQOE(v~-3QHG)ZzFsa0jL)4^{jx`Sju(W8z$U|Sww#VyFQ+IxKoKAJbm0OsWyRCEwO5vt6JP$ahl23JT1CMIq zYiR)m09E0nGy$Wlh26f*N_EeAkN#4SdkR5qx`sRH_~z!rbDQ_{6JR4hhU}9S&gIcOq^P(hS2oExX=`T z0O2)*d^cgcoR8JoPdvyfHY3@pHl3p(SL@hfI3OSDK=Lxd*&OGA(C{jH{o$`@1(Sn{ ziV*3rj-O5Ar@}(%PxaFM>;{r|v5V5I9Peej{2E}&OCz_aWhmNkTK8C9;SpeNP#DjO zx371W3*P|d+W4I3S;gMCIlfBoY>i}_n;N`%Sk2wUoLtEpxzu)>_%Qc5?mGY6depaD zS!Rwv$s0;4n;U3EyE>Wn0ReQDeA@M1QUK3<>c=V$G9;m>8FtjvC-Ye1U|zddoaCHR zXdbjHgQ|;!prSUy-9YSlI;iw}v-`9WtdtMS{IHVsm#RkE->-w6jz`>Qw@W^@_4^7s zVo062#Cz*JducKD*(^8A8Y!r3b5tmippUM@6(8~bX$m5;Txgo>7!s#SX(!)ho)YQT zMtKzo(b0C6l7H)A17p&${P1{uDg$Oqe7>_nO#!3N$gN?{3K$#F8Z5;Sr42=S=i zQWrwhFZ`7l;_R>{n_N8h7T;yIO))ZgkMDvqWfdl-l|`^!0KH*+A1*`kwK^$7cK{Iv zH8{;%^TL9-nb< z4U@DMYJz$5vb2WrVP>Qcm0fbmP07C;Wxoy9_@aZVa6k?J0zN}Yg1?l&yBBaZ!Yb(M zG+xF=iIVFqumPX(~REoqK5_UKqFK0Y3 zU(x;k_+5qqabU{C4pP4FU-x!u9&zsh*3jOLZs`iXLnJD8i)qaoU*alg%X)jVl-r~O zgD=csc|iy+K8tAFgnhzl>HR`T+$zP<@_j$sTj&jhZVYW+&Nfv81m0Pk$S1N%HFvIo*b%Qop-NaXoTn%S8mx}m)5E?vSYY>QdK_?w$ zidx$_tfo>%y|kM=u-G3-N>EJ}}ubhA;{Ng4{pKgQ%fy z13wPHe)YH9V=nNFv_&fp;5Y7OwT7~b{hkW&UlCR^|5Kz^m@fg<;(dQO2;7}%MD{CW z5{0vz6yAe=-7gf2)kw9{m{?ShA|sSJ=~VIft}9HV53Oz_O|h^^>YUlh$sSoU=SlPF z69LIj`vgA_ppo<%ta(eii|B;m6{#F5;p4i%%;sz1t@mT zhx9k0eeP^8SDoOyYdp<`;m5R;@`vXvtW36|Ba3x*l@r6d4sY~pzO8-^0e^UEeB0>tYv3CP1}Lzl9g9B4v6`M)m_wLr_0?&9GqCtRu1v3RlL zv%=mO4}D0WRGSpSQV9{y-)#D z=fsn}m>OH^L9FU7Zx5-!ipsR318Hq@ty{78J6SiqTtjZc+LSL_7Eoh(Bo;t0Ay2P(A9pK1A<}9rRgFz0cUXSOF z_B*oD;aIezkfY$d@NvJKN^O^N<5&`A!H_oLSEERpa$`R|G&>TSErFEsRH-;G1r4=U z9iH2fR?-b;Gs;$_}{1j?K^Sq-B-!lB!1AOO}Jpz|)}265+YEg=aEHcIHy^Z3Yn<2+aPcV%g&k zTGft6U2lGO<2RTR?9!!cW`%K$3ESW>Gjj%Fg4xB%V$AG`pK_(Yv{Pd9J zT=WntB_fa`g&Zaj_!CArI(`AC5J}~~Tpm~(pW-`})o#)?A5T&c{btv^M#dugZ+v$1 z@J=9Ga}JbT^h>_+xke8AL81{>KyfR$5O~cFGqRLx6~Oe4S|0Df*`d4Xs63dQi^WQi zR^lTDU%`1shhVUrHB}K#DoKD3{Rm*nnTwN@KfWD=VVZ?p+?cBG-R65RzE$xDCjXW= zkQ3LU7b0*HG#a|5W9(I$lzNbJDMEXpcm_uzFf7F)sAVEkW}!p3th@-i#~w9nvo!C? zFwNSR<{}7`e(@dOY+#lhkNfF}H7-aRp;h+thvl^?VmrV*(k=Nq1BNy&?2>a0P2b^( zv|5g`^__R!ZmYHC7yw5783nU>J`=y;N$EUAAo3Db$Yx^$%L{FH{XXti!X;+Yoq8|T zWD%{qLLL@OmRNwGGOz$!i6Jd*c6lT#y3Uy0co05@%-7~i30`{h7QPcRlF#s4EEyPy zJj`lHI(eS~G0*zAUsYD$rIy)t9`5K{m8G5smQrCV-fvFWAPCt)El;n{d&9ojnZb*U z7EGw$DgTpX_e*takPUF@QpDtY>>^l9s9>*ojm4+=NZOC7+3OBZZ0q>|Tx=pGbTqI} z=>q=d?fdv&AjB^}@X!C@<-hstfB4>i^#K0!U;X~?e(qu2#7B_kSc0ztI_ab~&F&gL z;N<{w3!S{39GQY%Jr+y&=nM#X61>{)WmQo~4%PCyh%fOW3UZ$>JBP=kN|&%YXwXLi zQac>wYr-`ohJ2ILhI&_dp`>Tgaby4NPWFM-@gW+ras@<<_prw39bjUdv;lfI9)M1y z8PbtX(ZcwX$f?0i=}@d=T}NbStPs!+?x3T*MbZpGCgldha|?(B|CD4e;@E2RhmVch8PiF@m#eyAEl9~bFTBrk z)op^wIvQoO#v+1Gn;72xj95+qWAQ0qW@(L>Ia*tV$c??uT8?-%3k{gin2Zj-V|@64 zW-F;|L6Uc(w9LYjwedyTgfJYsx!&mtWG_+-c0~k*c&9qxZz9Y~1xhRq4*_^H zd1iZfyg2+J_+Kk!u9aq^l(HcecvME;Zd}KR6J|<4B&lpozm1tQ=6DXneQiuCXK>0--p(W{0t*ZgJstN)qlbG@lKQv~}z=tQIvN~wf zXQV^>+)X-{9X0e&r4h8g@D>)N9X`|1tD9T8f_#uVo7L)4K3bKq*BGGK-EBa@3H*JA zULu9zLfZrS!-F83YwA_PjCT|{%3TZOBmjvh*}|BZBI0FVgbba0US_8QgOjJYf0{rE z;ja~7G40;#;L~*cfWP7L>F^=rq13wD;*$)>NdiS7Z$V`68QkrZ$KQLx+~WU?SxQa4 zC%(eB{DrtS?Fw817eXRI_kVjM-`iut7rUj{3(}@lVwnWn9s%HHt~M6CeqPi8x*E=9 z^o{JG_>_RrmyEMl@v^-vQ{t>zxSbmUuwq`c%b9a_kQAci1dUDRh$|Se>{i?2_l^9l zJ~*{^So5t~G@0Ac6iuqVVEf^5M_S;!A~jOtD;M&CwmZo!>m6kUk4uM;!zN?tqPH!& zQCi@N#DYrL>Segg%aF3K#P6Ff#CwXH7*|bh03amQbEc>vGZU{!nAX}Mlrqo1A6SBW z;J_Y)U@2QpMX)`VW1^Vg3umAP{yGwL;`b8Xk&#iLkZ`LWRP=#GR9-^bBfHR%A-feGy;8EKNlRsqr)1qI{WJ;Ty^P@{& zffY`6iA9PL*Qf#6qug%U7MqejO>(+bKn+-h+o?{2cshf;`{Nw|0&+}TuRI@IZ8+mx zGLZooc{gdDvU-&L_z0gglOmwDF7R*fpC4L(Zmo0iS4K#(y6w|J0{mV$Jc@g;Pno49 z!Hk3J@3NqYND(e+hSVI5eW0Zd#Jd?4%-Q)5Ir;k@CJyGIF>lPvhg3wHudqO316yUa z0hG+3LF&b6HkNKw(Kd@UaABh^%R_OgogU4&XNWN7c9e8mFj*Ua#>k+N8U!?Bfdm~= z1h2F!Y6}qq94%jA6e&59IDv;KkxK?DV%Mx| zS7oyvmg!qgDXhZV0Gks~@@M^;B@NkYE`S*UeHX%EGKviKw%wVXI!wxlJa>gG3C|@j z6SZ%|=mKzI4VqEAUgL$B^(IPAWMA@8hx69Lqh?v6c-uk64_C);nlrj^VRuM706sV7 z^p7)H;E#=bdf|6j^13S6SHgFo#jNE{GRDx&{^;(8FjkSV5EfK86W_^nu>(f+tY@mW zT(tfe1Xi)`Xs)cj_yr4&A{nYLzvmTve88t*RAf3vXjQ}sX5w0ajv}KCVnJiZpRWU< zlYq+ZtQL_$*V~@uZFItKy2aa!mzOoYKXT|~Q;fd=^ddC^`)R5huVkq9AR76|bMl&} z*fPsG-zBOl&dBehn7y&LS{ zx8z;Zn|4|q(prkRKQU#4|9|MeVi#foe^4d9Dsug5$$z$@l*87KTj>sM3-*7jdoLJW z|4n|!l}i2gG4sR@sAjd5mf#ND33U>%L@ta^FdFs@oKE8UCBR=tkkP~ALsxQtP87J6 z`9c9bV~bRGtrCth&!KR@onjJ@{(5%u^Gf;9=fWqI;77^B;kWgLe_!q^z!e2?(d_+a zJDS-O$Itwo&fui*d(665I#v6~>b347Wjh-R_kgaa%NMgZJPioIj3!57gd-O7qG5d? zqsv%a$1A2W^jb`4HA8Q^zDnKviq#^RZEAKCpKawc1vx17@Q}7u+*|3T?KD#o^wu~? z(+Uul`|L-JT?ajtYdnkB`P-W#s4g7p)i}~r*;P3RJL8?x?7+Rs_27Ob^BUBBKBF*9 zVi&AEOzER>EptIsmJ(p!Y-gra;O5-gK=GK%oOMhh2xPgfM>78+ zX-XRMv`RTimukMwY;4c$hDsmU7qltFT_88DRtf_FI5suePdpdHV@^?iXT4#X^=CH5 z4pSPJT(RMg^->%1>=klrMUFV8b#$T$vK zDIOQVJ~XUBG&%@wJ~Dtm@*qGoZ(HJA1%h!>W|Sg??msB^dozrCg=wfMd~LhXKq@nA z3%(-@qDZd4_6UDNf7W#2C$^wrLFi)|+b8tbU+D7B^iz(=)+%!# zE#*qlw!KGO2aN#yC&z#s;!oS0$4Z5iw!#SefX)Y)F`efH#B4Figbd{v=+d;%26yWq zMd&=t8nmXQXwt3|e*`2j2ulwn9|SRcbk6ZUuoDPY(Z|INenn#F*w6+jtv}q$oNBHs z4-h2>T(8#b_bJ;5(vh+C01YbV5AIl43OWaYa4kD^H(Ky(Z+l}laUjmInx7CC=yfjb zw)jJzO?8Xq5Q)H{%O(J2B(orCTV+5M!=5 zafA!MH^plk*^YKDZ`qKC6AVb;bhG(4++w*hL!{W(w$s(t!v(xm$k1^7fICr+uvCrk zNU1qHQ%nbHyMS-yFphpbzR3&XeOt>Lxl3vaa1G5(8(L^-qOVI26Ylz-LL$6GZ!8$!opJMpA}7tdVk^s|Qx4 z61Mva%MYO^ETt~^;D0O^b*}#02x&};d2NCGU>F0P z_sW~q+=q=x3(P%GFn?p0>$=#IgDPyFbo`&qyxFeqrno?k@FKDa>1_s><1qY_$99AV z_8ArI6Kjum$6b<^OJHj2vqfqJ60sZEpp#%(#&*0;IV7B$ry(9gA!fmcowlQf@?_}8 zai|V-LT}3e@FLc*3W21C6P=o&EfS*({(N9`c6^NMUAGHH(O7VNFsQ+$LuGR zH%hL#G-9<*&5fd0*L>*6{K}C}b-kK~9vvm>keLKjPt~duca;SaKCgMI4nLabKRqFj z%;hV=1Ua#L>YkXHWQGq@G!M1Jg#(}jD8PYJA}w2a;{Y1^V6J_S?G<{-pfLQ}4lHT8w`Hk4hOt6p zu`je|0?*4Z!@#Lw(3oAY4JTIz=~Z2Byh3Q4(+~S7u?XA%GF%N9U(#Jin|Z39zX9pL ztg>>{lD8w1i!;G^{5b>&XgHqB074>!!YGba;+yhO-T`|5D@{W}!N2vB*X!PpYo=y&{~x^Iza$ zM1O`ONxbI_-K$eZK$Xt@SXY4Qho3!pkM3^38e$PycZ>-f_bvko)YA~dYWd? z1-p~JrCWZFJ--N70q$NU5g2o|NA_XPSj7ahS*SI!sfpo^(isYQB_T{&B?UWyb_jSn z+Ng)8n9&L0Nx)4qV%Y-Xn@kv9W8XMV3=Ue0;XCx`0L&O3V(`Mw&iJI|{Xy^4b*<$I zI^Qb56%0pPJfxtLrYtvYJ#{jEZxlv&OZkg+PUu%aqXVcMfx_$s9{8{8XAKFZ8g`;G z4|F-EM))!7ua(gz&gcD(xM-!Y0G`yG;;m{0EqojJyk1egaBF~K+Kug~<+4Uq(@FNS z|1DwW0oow&$5P#g1Xn=R(8gdMFCHZN>*uA0ao&7^@JGOZf;`9^;qAzU9H|N2<@jKa zj7>mi5dI_QRWBj=b+oJBr>Xe6FW zb95_fqYYtmxs|2{9ECIIMHH|36%CMlS8@O8`UD$>=pL##%6iS^-;<*ml|MT^hf_F) zpX{w93&GKZ6XXu_kkwF$8o&U;D5i%L3X-uZOg8kl&MTKyRHn)smV+?OxEtw&=Q+`e zbsT2!Qsnl;K9Lq_ox%Ss9y43ZuvwMj_3($s|MJh@6#9Qu00i-pv!%CvFF{pQUNqlR1ug_55IiGSlLaU=jFY6Z z%G!{BRKuRJB>*W+tt6cu{D#-^@tWvdpBjJS*f9M&7XW9DcrJ`i3k<(nU0DM}GbD}! zVOaNkQud(!NI(BtkT?GL-j_v25VY~GVL#@k!z8_vxn6^ku`@$B69KQlf3Z%17Nl}8 z-ubjtrfrg!3|EU?8O%(=(m1YwYGRbPEf!@OfIth;aK&lNqWE4*y1r7wIz{wb55J?w z+#@v2;PMoI;t3q^^$M=qQ^(>XEC+w#G=QcUKF^-ZIgTI7E2MG}Q<&dS^u;RyAF??3 zcu|jXCN~t|wLqN4a(p|j)35|-=I>;h&G4JdpNLy}Td4T2MPe0xZgq!!gk52;E^g>q z))VLWwg<>O@a#}GTlalDwUtc!dOjjHo{8x{8#FRHLRP#mge!AZC!cewUtf8 z?aQ^vRnapkl4lZDpZLP!yHC?PE2ydJrOrJzdhf!=8<;Yo%~_D5fP)e^gq8!A;^*AeiRl`T6j z?1&{;Img=E;a;QETTNl;DQbxQwBN7yfe$?tk-tfe$zViEdP$sNHy%D zRTvGMM0^I!q2yw+k8EF|`?3f`WxK*P7;ON>0D$A>q4$Ob>jylF%j+78a+Ix(_W)m= z$9Bh^9tn6v5FDR3!vuKY0O0Xr8^AULRiqmZb@8X5K7uI8QHt9cYfW+tztcdZB7vyu zIHhUP0;IjgZoawv+GBkU_A*8@D9P%!Jz|MqyfM=Ix!?QxQ6AlRTz(>?_J6eA_kd~|n3 z+N_G`G@J3C1yq^qcrSn-ogBF5_5JU-Cl4BuOWGDEtVwGMkLH?a=r5#%91Z~SWM&Td zeRQ{cP{6*JN=gu8(A8kbY0{+b4YrNP9>h|+L1))^5X*l@d}Yz zQ_AIE9==i3@A)$0t7m-trm^-(uqI0!^e(j;7lM_i$~#%Lg-%3;v?)`_ox@^+R3gFw zl?7AX9u}{>gZGBQ1N4X@Mjkg)OUAj?2oKPKBbFO3#?o)itPPAv76U&6M*TV=EdWpX zOGW3MG)@k&tYRsTTq1gTILIAD^gojV5vTB{XxC?M=Ye(%dZy8)I)r!hIumbmzD{o^ zid>h98PC^Jw(Q-!G~8!=Qb^!;*j2!{l-BSQN9g0!@Lx2Up8~3S*vxgQGKy8hKk0Il z_8&VHTHRJx`VTLp)u|2VL6Ti;taui}&+!|zHnq~^k1G$Kr=T3fW(+NRf!1XFK5)%h zH}`VBQ`ZWQ-;KZ&Fp$hhjBN;Pg@%D zf6@po)3NOlVn(xbA%XE5{6HyICzCp7IEjB8J=qbL$?FSu5c6%oNVo{|R^{amnc0+j z#EfEcNQIWF;m16zg}dp_{6eB*_bffIqgA}kjU{I~2{sz~*tI?9a@(4Kw(Hr{Bz8Hw zh2;;+O$M?VrKK=_LVp%Ls?N!R+mw)k4Zx^o&5AYCb?5(cKnvmwsPEd-R#HW2cby}^ z+iLD?lsVZYX`yz!MmWmCrxVnt6+T>*+|&*$YxLjL4$+W#!-27-jvX#}+N&MU0$_JkIrif0*vvK82lE*Q@W<;$=cIl%*l_XlsnAA@Bp;ViG_9k zq`vfSmVx@mdZNv!qHWT?TuqA+)oirwyv{@K|0t$Dtyg};{Lv>c$afhkVJ-H>A|2WbmNb zfzWvurVSeb03SWXE2XpIMUkkpAmFLO0Na{&DkRz~*~$BV#Z%DM-Yt)M-ba8xJe%6> zcx{;k?nB5(TlY4bwivCL8f9!CXDFrGkuCp6u!&)d&imH_|NTLd}sxkd;xdzD9 z!h}~^!G^e?Bp!$7FmmDaYWdvE1A(|Et=2drwAmckdyyyz5Q z)x;u@uWRYv-)KFNd8x$sN$pt!d^A6ahk&T5bDhI2t3^8ULAHM1yr`w9K&QT{D-Tdp za=Z78rj9*UeLL0HGO?7bv}W1J6bi{(QZ;cVKF|@aRUKBBZ5E)rt9CEapi$}2$O3J6 zt^rWh$--&ctda7WA(J|<)FnM+B?s8#Q>;}pW~A0uw$)TsJh2|`V?2b$(P7f2Z z&Wggmi)43>5_2h0boI%>>`9m+Q4yXKk7_}?O~vr7Zge{S zy7asa9W4zSAKN@bn;^<$O=T*JwB&6Luu9_96iv~LQzQ)P=VJdJRF;pE!{^l^e!f}pCvaC$o31AG^Cwe6&o&zAeQGP@zeWGh@khosZ*&|Fg|V;uo# zQ#R~we8U))N~&%m=<4BNLHJ#-L2@iUWGa*k@f$1DTxM(fAu)Y>{HkHVL1m#tKbt`N z!xsgx*C1V;Z6v@9b{^pi3VkpPy!`Z{*YPC~7OBRr15^Kts$bS^(5!^VdDGhT0jH#@ zH6${%!X)2X<<^^(mDfQ?#^`KtVY*`T)OnT^zc3u-2BNS+Q)|v5+6ZeQ1y&;+94=n2 z2Js|Y_&C5o4$&pKuF)0R458aayp%X4#b9~O3Jl@J)WR{4%`&u8y;6%C@UpEnk6D#< zu2_Wrp|J%(OwrhD-V(^$Id~7?CK0kgu%GcIn4Gp3Y>we+=1*xq!)qlYX#^Ei>fsVz<{VR}93c#{dz!#rtY?zNf5zRqQTJc0;x1OBIYB0G_;YXKKwFJm(IIWyc2WhkN= zefq6%TiAUcp|+Mph`cyNCy2=N4GK&H^h$?T-6#0<|HBpvhL@Yy6fm3K;E)d-_3 z6!!a`>5rVpT;a>~TnTUS^6@>HKN*DWiZ~F z%2&`1ca~mRrMiZ!kAjn|v&HlAs1O+R)qrDA;kC`M63%0pRbHQzFC7~eVod=D7CaGn z@m=MTaiTi$3byOtP#{xp_?}msb*Y6rSj>9hTPG2;DYe(x59;Ag;LEbtSxz53;P2 zYD&xbV>hR!>A1_-gefUSd#+n1uuJhUiLPZT-T@U8gi_yUQRGc#A=hYUvv4Y56jsXR zJrWfZBXXi**x`xz6PiI2fTMf-zyQMFCMl4T07=?_swL$uvmL5T5&q_M3c;4Q;ml$= zJhj4oFq(doJrQPrUaWPNgVx}T*XX}=w5+!S=wqssYgtuQAR3Qq!S(XS8v3&zsY)1r zwdE5lV5`!K7cM&GR8eJJ=7xNn0KVRLOvE*5Tzr7Yasikmi**M*;4I*Ep$(g`ogxcp zr{_HA(^H0?w|2kGQ!9u1lL3XNIoT!{Q9CX8oqy6zz!5XoI?YQmL{w{rVm*oLxL zLW---4k+P;#g}t5$)?igCH1^>(3VN7#9&FO&q6qjxAQ+#z5jwiFrBqM=Yatgsd6ge zOS%l`f+(65QXSynEggHU{`$-Oq$)F>eT^y-q$`@NcbEJfQ>@rbY-L$RE}yv>&@3C_ z!9s7AdDmd4NUWA@!1kCfN6s?*#a=j}^lU!kWoWvt+-Xa#o^DAgWRBM_uvEP(6K;!` zv<31-NKL|pio9!6+!n7nLy#b`T)7&g=8(;3ur>pUAr+*@P|OWB1FqTw509lN9$6$n z(M8{YZC1klJVnn9P_xDmh0)L@d|tD`w_@d)6>*cle~_iGn*lgcpAA++qPj)lePBey zhAyD*fbgm%ZDUIAVV4~gmi6kE!=nqHXL0)_AZPHNsdqVd;x0=EIBB3GF792fwgA>? zU|4rZX7{$u?qzr%PAEXlYg|zO4G*0uB*~H>24m1Co{*(&W zoGGh|8q%VeAK0!7?0KQ-tYM9g-rjEQI1RT4V1a zN{J4@GCPv_h{gWHOfyl_fyvM%Z0qtbKS@~En^>yG*OG0-#3v+xNCuP+BE_6?i~ypX-x!(4jZmzE{aCq;#g2BR z6=_VwTR}prl}7%Z)RK*GCF81gr24M8&`BfNiN-zhySX;>3_T?1ujrnhG?$j#hriHjEYpi^NuG>Du{=QhD-r{=UfapwaBz$RP}M5hE1NtL&iQ0Q?WHjhFEb^C+$-id9qbG^F`PRMG)q zZTxzAzkw4jFO1kyrta{ZppRD#Tc)@|Asks_{B`@O#3w0tD%9&3Bxy81pq*Sq1 zf{+Ru5^1MOBRaNm>UE$L-P}YA$By%3a6oA(IjR5NdRkf(7I(DA&)0=LxePzz>qRa| zVW)@_r6}!H*3ltJ1M?(z{HmmZbcQWO$ft%;<|yMGa8{tEN?$3Exf`e1P?9ERSh+$g zc2ln>eHWZH84Rd|NUJI`#&8dL?)3(U1l-ea@%v5j0@~s)QX&wk$Kq!V(X&8KXg{TO z1b7D+fV9g*e=U@onmrL4GG~)r9VUrmw9K9I;ctW+>OEf;WNYB^`gkD_k(0n$*AJ&r zC4yXZh`a1>eA;yNNbBcPCdDO6u7=Z#444_%JW~~T}!$|+s;y5l(30q_Tn;`dD^M$Gwid# zLhVF0$5=(^f#*K&$-N%o|qk+;%7FTZ{g-wpe0w@?wkLR#%84DP)%s+v7SP@xgo# z#$Ia{GVStZF#A$k?6$|W>kr#Ob)kj{Rq12XH=yzw*_O0Kb6%ncAfiCZ;0*QHNsiij zHM9&f9Kj#%>&zt_|2)!wa4KbL<6gpl6AR1+=%Zj~Ra80tH@f3vB+_ zCr>*_<)Q&V4DS@?Ya@2u-oCzA32toeUu5h^G7m$~kD2UZP0T)nPAlhB$?Qv-6T_)W za4;VFfR)cbAcviQT}z^_(@Uizc9y?0M2$tmc|vyG_Hn`xjndUy0EgA&o-(aYFeIP% zya-W;aB`&S9S;eX@dQa0%7A{`<*6R6IwzTOe5rBmNL4Vd)ae)51$F_gMhx9|%F zB)Rc>Jh%F(>nL0A2NUrc-js9g1n*3{&~;RRJi>18G&U#6K5M+`Nd*R}$xkCCj@-=+ z`fF{#qGQQ)@Kx?0(NslOtRtw}U^C$LHStwo$IDE0KalM@-92`LCR{d+?#I9e>rBc! zI}yidO1nl%TGx%ru_9PI8w1dN_be@!^3f&Rku<986A=}u1SQplgt)wQ|D2ZDWRO|9 z@v2|b{haUbYJRAVT}VZvJiO-D;C~!5ai3gch`kQPIQ61;O%seqQd21>8z5Ci82DCe z54eJ-Gc-=!pO6PmYz~^&|A=EKtAS&)= zzSPoym4T#Qg7a7{j6Kd_HZm(!UpL+hKLsQ3TAfJiKv>sIhowF0rA1ij#$8YttW0Ta zzQdoja0&b%h7DZlPR`~-+fUcumoUSsHK*LiiBQ4Ra!l@8Jcr8@8!c&gB98p3TBst_ zL$pBXCD`>qxE7WxfOM3Nfbyk)n0_T`u8NQI11>{y;Ys7g_IM3rLAj>%-ZJunr#;BP zbScFGlkU-lLl%%+bSa2$`LpfPWifQ&v(jaZ$0@#$Obp0w0NzEoz6t#UCnYVOrF*fUi=&zJMVFW}%h zY2saUlh;sliBA|AG{H68Oj2O+KC5lE&vvGle!xp&R>~BiD!s@~B>h`ObOJQ7&ucBU zIp+-fpW&gzmW*vx$atF(yy_%@@Zmyv7_v!?oa|Z5oCd|Mnk7aYniIR6j?bDcz_GzV z3G%c44G6X9@AE~+R*r_^G3O)iD5ET4ZM+@~V(8>D!F{baciUP(pasCMivCm{M$Jsjt}n(tYEVTb8_ns#wa%U31UrB*Z%zEMNu;W{ z4*fXlBG95el7si|Jt}Z(6dUlmmY@bX4kJc?mPbOD>wP&qUP&k<7m)${7ZrR@kA> zW#aX4(0rf)rRV_BACy{x$Pozx7-F=PKI&5pqzMznLP$?nuvQeBg{@5dE7gX^e}J(M zNUcQvEvb{&0~lNSAI;fs3k5L1*TW)-WwerP?@J|Ud% zX}{#FBJlCuFbXRx=;2N`PBIMkdFki1KW!RTlNgwPcO_%ORz|T*S4Kf&relp!+F3BMFL6oHqj>@oqV+9?XBUeGW|05AbKQ1Pvv&Lp~5A+1p+? z5DRWF_%r{IU8^cTzjP+2ErmDeR5Nz^DAPPKsptPrE4-2sTjHHBDN*^cxd{f1mv@5t zweVvmAoDVfTq|Xxc4P6oG5aWsX|4X;Mn+dlx{W@yb{F6?+kWYxWYAG+EV&g z?Qh}e6Q2x-B=J{IF&;~wtc>2myKoY_?v${iGx&_jaT{Y=A~m;D*mR2@EElSM#OycO z=;L(^iG$x9GXC5@A-J>d=dV7O`KgAh+#`p|dbl1Gnja2wt%MPGoQ}V5hMqUW+<$vN zduaXBJ)JA!NV^vHafSp7pxD(_ReiO?MUDpgLyi1*bum8tLngxhq!1j?4C%=TX(N#h zulZz4=Z;Ho`IGD)!|BIWRQq+#CPv4%8&aS8BZnS1ZwM~@`kZ;$18~_L<`h?t&wJ6(`Jj!DiJGE6+vMo;=%~=>7>15s&N|3= z(P<`T(&=o8kEKECv@-BbHf1BvZ!;DCl7lM(4|~@5X&p->jjhmxMzqT_B0^G`t9W7S zu!K9rj#Y&m^IX6U<5~f`LL{E>H&T)-Ve0i3^0qUGYWO)ANk@3dAu%oZfH4CUUD~>^ z7#!;5gE!jgnF8HQmuKsf1C|T(IcrC+^?6N2(Dca~)2F{0;Y+k^!=D|H%GBc5SlQ%N zmev?3SA+H|s|*0CNQ!YfaKxqQj0~7OuHq$~G-rq&<8W2H$&8(1StDc>;zn+09|rsg zilgNZm>`8&X%U@ETE|gd8DMMP&jM@z^o~aI=y)Hzntg9BOPj!DiIQTqe2+GP@(sup z@oE~dK^u^t&o^=roS?tG2Jrg~TqC$aI6)WR;Wx6O%T#axrcFsgwJ_GJG1ZG)5?)Uh z$pE$3xpL~^`5yK1FT9gF#~24ACr%wYY%F^ns*ZLw0jtPxi`Y`$3Wx8{eygZtC5BBg z*fM+-Dq$1QNrFM|Ho$cPSN1TzTp!P4lsd3!L&RsAaSNB(hHE--d&(TpJH=|yTB>|T zyZ9a67zppUX%$Z*?+IgO`EY1VXJ6hb-ZLh4yulcixw%~D7qTUJHRaZdbruj;r75q4 z?{kCgBjp}9OywAZz|-m^Os9n?F&6jumQuGnKqCHvJ6a0OG2MCAi{EG|UGZu+v=V6W zl@`ap_rn16jr11jo5SWRNFSm(b@c5Ordvj{W42?u`@M|qX6vAmegctv zVNzCD<;%AWp9UO8*tr@g-(^6^aIp#!R)0VApe22=62ous+;RIcOejZ#3lR>5DY~7P z@aTAZE56Q4cIl|g zwH5YWr~=X&R8BfQJ;uPB_T@oEBdfYIMTt9YwKgUR<{MWqfA-Qk>J||7yNniIjP3&8 zXm%vsdmieM?={)-^}A5AH>Vh|D=a%uTtDn>uVw9&zZu~jCRIpdCB2E-c?2;LLBdss z^m{D>gf(|u4Shq}wc+vXjABj(yI27ju?SSUwtwRQs}4eBU& z;urSntn(9zih&7T1)mZ~*AJDuRtaTPm&P8RrG z`MC5t#301&eo4-$13~88d5)7Is8~bccP=66zrG5Y5FV^bbHfwo)+&~)0chX*+h=+^lnoCA;3 zz;}$DN+-A@-pNR`N1|whF-rmQL+*foRM+ViuYn*wC}46&aAC*V8u!vE`bw#PWc?(I z19{S;)JXC_oS%du)7__hKU*|M9MU9^rz#q?OsiwNj zULiTPNas(~8T{GCxVES;pR@i(1N^)mz?rM&sSXaP%F)HP##;FI8kGrI-afx8wqcyF zi+W2hWXmBzt*{QPx8HNk(OA`RF*h)B9W@{rr3B=u;x2Qd^b@u6Wc((W$0BoT>vJDe{4uecGYUCwkYc3n+GvG6pyoKt+gSL#E;6>>d5uYfjSX+9A=l?56H~ zRLzGx7DXXJ&>SsP=geV@+s;WjaE+hNJ|k+E?Etj!y4$PE1}|^m&Zyx~NBb|aOB`Dh z0B$qhA_2l_Fr0zHVXd}r7Gw)!({>ZCWIs<(@XxkRi4~z?#AL5iBY?9YQx)FX>K47= zeIhw?uN>zO@h=XnqUEJJ)$aW_@RS0S1^H+C+>3-HQVU;D7eWBr>RRD{*W*tMA9Mm6 zuseMvxo|Jk*uY?qxS`t8{VHdJ4>{d7@MP;iM9i`gHUm@mo-V+-mi!d?X|o^^8p{lf}xamlb~|*60+e6o{<-ik>a-v~}*Ebovh%zD+Q90e+Fi#AlUETEn6D zzt$IKZ-4K#1!?Tb+7eFXJ(6Lz%yLTUzuLl#x>nWhtY_E{y*J#;Thd*&XOEc-<1~AO z6`c0xVmMdOu{Jx!2|fSL=MilZuKOKh2TjAX-A5|BV@ z&aE`Yc%3PqBgN1&9hz)8w_(4_-9R=AFBpiZXmW|K4_W)^ia5KO zm(snHPkSawm5f{d|H^%`C+=iUj57jL?3)V}zDb>G0A;7OrZ^d@N?C{(Jq-ay`Rm}y z3k+-F8&C>*6s!jClJb>tGYOoAPNgYnJOh(kOm0L?$ALK(;F47=bK#7 zo%8)%YFo&Rv$l@Q(X&tsA`2-&(HoLEdV1`&n6}wOZXG zRDOl;+`Q#8-}BT`Iz%rCwp^AZn@nH?`Hb-qO$X@V>;e4k-PO$MRoXNRz0{thSJ{1g zF?$UIz~?0Zc|6T3SuOzsgsx7wdxF|G# z$av~$(M0;Ksd%u)Ma!nyyQ@^&YyGw_oWyb>P3YVs$!>Vd^Z@ecpO;+Lc-!M9vyN9$ zU~3ZUr@`3_v3=c$i!uH|JIXwGkY8jkrhWF5Wj zpBWDj&O8$9E^5+`D%+a0rrx&zjD@qTP*vMnFyoomJ-2njvC;5~8_cBSad3EC|J%=d zD_g_YE$8@%;Yr?YFH>{~=M)Fo+@a$eE9P_#Fbcr#6DgB(BUW_F$; zykX*@*P6~D4$4V(BC=$`rixGV2_?;uubdD zv{Q_JRHlHoBr+dYD=B#AuSDBV^I^&;mID$cwe z6Df0}6@JJhOoO!`E87Z*no?n{9M;I4$|WinpZLr8&~^M(4`X~Y7~ZD)x`A(<@F*Q;PsOr=B}OlBne{HTq-kCzcfK9m2!%&qSLxn*X*dM1+(VX z1=JehzLth$NoMfrWx`4bpU7Xv!FJ(rpu^o^c~+u*RA$eHr{tZnFX?ChLQ|H%(RRD~ zD=KCiT2}PdapDX=L(Zp=rm(2ovqBSm&(oBHMmOFFi6Vw2gW`kWFBC}s4H_C3FHpB&&OeL1fb4$DgZ9$FP(T~7m}l=oPNgb zTZ9gnG&I|bTg)9kuJyMopV9n0ZfbwMVQ?Bal35nyYx^)Qv1xC)LwCd5>};Ah1(bz@ zHh*&j(g9;+Q2fo>A>#ZY@kFVm?)C<8J;FQ_SJ(m@6WIf+_!{J=vnwn}j0<8pm0x>Q z^nQCh-#|2NTE}0Ga-$xegWB{xj$Q*Qm}@`Z&;4kb85UGz1S9X9L>w(c2GdKg=1N&nC0KI2tPCW{h9T&%(q$Fpk zfPC3+3V1c^ArMii3DiI)%@`JM^$dW!NSeoY@f#vfcJ%lR%*$shXO_dK?nBzK(=lMo zDsRMoIg<_DX^gl7uns!DQpiJxvjl-dPgHnU>`gKWybZc*qFdv>JQh^uB2#?SkV}&z zszQK-NIJw%7B}krop)~Bm5Rq${hZ-~#nu_^K6+6eWce&;YkG$@MV?B&b}C_ohpKbOI$4zG2^(9^&MvF9F1*UsliD@1HI>|;Wdy*yXNL70?}VvaNXV6@RQ`DH7(vY2pyXkq6_ z+$%oUp&^kNsoLUotR{pN*#Jcfk9))*wD4;*?x6#njAn}0826mRj;L_#?4!_rO=ibv zkExSo_Hl5A-G173C0Y<31U$V?dRIs*Vf8la-)P`|?jA?D+mb7WrYPMTJPl4ZQ@l!# zO+~GuT*fk^vok6u76OidWk&Pg=vOG^BNqf_znRfXZX37fi}<;cR9**EwGVHPceAUq z6rCy#0nTT|rY5am7HG}|E_2zXUUWw&Ei}kt%%lrv(n2bm-Ao*zZ_y=%+v)itaMN2& z8pwvkbSSx{nmz3k+h|vdxH)Kpny!(?ZW193&I?!WUh$l zX=j-RRe>;|Q{K*6Rluv&>JlNLoSRcBl6y_>>gQUQ|AsN|Q;eHkxKP6s4|10<7O8{kZ zQB}+K0$=2s!1(n@{WXH>_#B{fJ4}h8>BOOu(JAWvOp#~J z3hR`0pzS2?b*F4NB{`Bo-+1$&X&hk&8jCnQ5Q^VtwI#H|`Io$r_}_olJZ=Ex(0c*h zXeTyprQ>Js^nUqetqJP>LegYuv-OGn_YaVc)0E1jI!6%^qc!$G5Rp?&Nm z{Z`b#MUV$uQggPmJ3~%td9>JUr9*Esxfsp`)nfPt!;uE?0NXENBE_2k2#{;`k^^M1 z-byd!Pv2mnki^)46$pSSh~F%+4lnF< z<)ySnQZM5il}*MLzY2_rsb`$^aKwYzXi5^sO%5qZ%sdCh7vVYh81#qfe=OeUUhR!t zTmqKZe3&Y$K{@9G1xw)~qxYCI$!7pj62>m$p;+Jzv%kJQ%2aPVibSRDj7|JW<^_?# zYK9I|L~IpnzZlwF_kRxk?uC25cxe6KEALdjPR^;m{(~bUf70`QL$@2TLtJ#3^?If_`Twd1`+$|TZ&pb9QtDLN z6t}m6&gwB|pCS3hC)73ze1dGY^BnBYwyGv+r<6*dZCl5%C}p8iMY{1m@6%a>tP<*Y z44^@dvgSyGNzG~g!ezk%=>DPa1FNR<#WcbbpyUeco{QHyAcuo=b?&e$5lbMQKDb}7 zqJUDdBwZJn1(`E&*O~FjE&>`!)YPi=W|8t5wnaY2U60TGRHtNI(xtpe=XpiMXoI++ z!!{+?4MpCF=+QI8Rdf#H(sDz=VeY0M4)oClb55 zafX+bXps+>DR~)Lub4CW+j|ri{d%(NFArgPxZn>Kcdq`-2%6G^|1w+308NkYlPr&qziX38Ee&87o(@2CeWY z>q`9_5{|+p%1tt>oZ@f;02E;`n$HMtVq=1Nd*+s|Cud)x#seVe8CaZ<5pqF;NHN5-4Qu^mZ(lbc9zJq4iU7J31X(aoM zS~)I`zmrVFdMH0>DizmsK!o-j22v_8KBdb7*XyIRn}C=+x9EddNjpm}UIVhs3zq_j z7#@&XvoBl+382ix3Ag_c}U~& zVxxi}Htn+7V&Q3U|^`=fTfK_#Lp!%f_* z%PX?tT~K20cG;%lR;#Hj-ENPa3yi>4;Yoj}$sxBJ*JVh=Q;AIIsUolr7Fb}|`IboC zQw!_e#XK$H8y>>>5E`_JD0YlESWG@xylPnAUeeSUMaOODH^Hof&)ZWAj|eLH08c~y z%ZJ?*uOHlxALwFLwq06mN*b~wRU_Fjto(A?fWnR^)`#1@$euc^l~93JvddA2v7}>l z&CO;LbQMVWwk=rxl;?cr@@{^Dvj_`pLx(LxMk+tCDFJ0m?kQUqG;-T|*0b~nMu)0# z%=Xn>(9z3;y;|cnZ6%QSx0C}t=V%QLM%)&S!n|q}TaYHHFTMWmCZUuU)hN`+=5pS0 zK9j^#^(Dqhf6ArRXf|MKx@nhcd}p-QAiSLb{Y_pp0W&KsdEH1+!z+;ayV}h0Rb8ra zI}h@p+dMuwguPLCtIY{J z7zIp=#*GLDVa7BqZ{n;tGi&5cS3u- z=Z%raE_{`L(pZWE&E&<#l^bK?iLwMHPs8K!p6SAA#^r$;G|mggZgYpWQ&W5SI=wbq zFVzcmXO)BD)1!SbyrS(iRDXyA2=DnrL$E(v)d9OnukC^wje1|+ka(G$h-*UGIcnjC zM@t*P-**$$iE^!yvNOlL6TJ+HhP+_9^cf$9yg7Oh9*$?&Z*=qIyiT5p@z58$yO=Y{ z;nr&=XK;IC8*99-&N`EQdNoj}&|l_eV1oTJ3>yp$>TW<6 znzt-r0x>vE)CRYP*+~lrZ{~Fvyk?x%SZIexz048v-GV>!W49^eZ7sSb7VpivARyT@#dyDpAXS7=Zc9{JV; zmy1-n0lb$jW#!N&=93#mpv%Cr@Wul{R_8BfUR7vG_B7^+{V(!}AX^W{%VH-BPwe<+ z>cRMD7D`SyHbB%^<~nS2oHN{vrO)iA!il6a`3VJ8IG&(pS;p5F4l#Fv{8;=vOe{i5t#^%2hFC45Qk==%dVeWRWW}0DcpF}Pv-`{M__bXr z7|wPDs|wa|`op;cCp)PXMwhCEI$pFG+~eD4crvIou$sg-PS>(ZF*Fk|;|W4^(zd@! zXHwXqW3!I6IB%Iq{P%Tfkb`p4O~1!M6_muezxw3A?K!ja=Of5q2hX$;per8>9rvBf zPkIT|rcO%kN^e9wGV2(gHQUeDP1RDYhiyD)=@_cqKtRJsM3qWWtZuUPY~hS$O}xwz ztC$4qGiO}=y3r8tQqEgC)<4R~*~%%=zB(%1C|gMcda?jXamOwfIV9`NpIK8>Cli9w zd8`5+VkN6fx z){ee2H?#kny~6C!<81W3I?K48`-eq81L5Q94=}_)n?}#NGT<$j63lhGnm)a1yX@Ll zLSl?$^0)~&a#HOX|2uifoS4!#1F|BFc}TFQKK0giDWf5&%}xnl6qx?KUb>DEyMkKg z>B@kyUhR>_&g*5*Roy9`Ye0Pr=fcu??u;#1x2}1JW z0nR^a=3^gt3vq+8)p@Jwmpj)&*^4|V2ZlHXr0yzECux?8owe?N(oS!cWj;qkMh@}L zp^fs#KUpl=EdMkurkr~bJzQY1o%EF+{tH~qRbyDqpE3NAUfRu{*0vyHNNe&f@J$lJ zOnjRK)&U2tE||scgegu6zHfrAI-pL(hgeKB%$3=x;woN=%A|!N)a@JrTiG6Ete4r9 z>vU9Hh3H($pX;q%vUMZA3>wdQ$PS+8WmCx*S7l`5^ZTz1tAl!dr?p#OqjhOFly^hA zsy7*H#UzUq3}L180jione?8r0moC#^|3Pj3jF#sKszq3DFuVW=5iuN-kJ~-U>ha>j zUsL-$P$$!~)6$}q5{~G)-X8Q!A)iD zpNS^Sx@#^5kmXCp;qA}yJq~Ng89dhU#Pphc%v>6(86Km*TG;M^;Kh;^G(Y;VT4@pz zFDI8(z!Y{w#<%UZ5naK$vSo{vCT;v}%LEdU4R2TOT9_vISDl(uk%G$&qL+xtAP za&-O*SC$l8+H`!21rQm`P7c00tf^kZm`)Ulparm5sq1%`cS{~ttE>Thls?e-p?aPq z$HI^Jg$l|ZS2VfNBewEtKgi)tKsM&*)O+Xw4=-UyVYiF38(5L?cq<*_EKNzJP1yuD z%?nLsq1}qfLupaoqfNM{3=3MqxXw$c={-poX1YoDH*uP<0uy5?gKP{}KvK#mFhM^r z!41U6W=*0)n>SPAV*(9Y-5g&4bM@U!lln|=}+c09be^bZ-XEfKbZf?K$6;hybVHXn0f`>dmS#ouWvKx6tNkV zD_k4wv^UuCc$k5i zj4r-Pa4W)Vr6y+>KE*pCZBw#*$?$2>9M>^vv;cAi*dKQSIT{f++o9Q$um8I=~AG%PQqf~kb3@Un$fuTv#!B>C2F`ONF{Nk!R`7AUoQRZ;$g z!MiKy4RnSgCrXW=7QWrkwW_jSGNGw6bIvHIE`71qbn<55OJJ>e#G`wYocD)Ax@i`;ZD zV_CnHI0XRXk9O;@KBQau*dB;i@e^?s0G@Nyn1W1IwYO?Mh8^^XIg~WR8aa2CgxQ)U zfTQJv=}^L(PAi+^jlfF6;8v(LZ_JNxYC^{ z6zcjYR}Ssikd^Zt#&Azecc88E3V?h{INH=52x}tlV(hewL?o%FBbQC@KH|q>ox8xae}uZd<*TCd>)wd5g0%7WMkRKYc-eAF;nNb z#Mt@MKrU}(z_swE$1_*rPSIKBJrvK4&n4VJLfpnFG`_(zodR&vi9#llPl;X0p+O1m zW(F19bfS|3x_l*xm-SpV;8Cd+BoQQNlAQwLc^;BY?0<^+B=fR#FPqCFfP}i101)bC z17xz-fEtm+z_|FZv&HL-Qy_{^L)5@tp~c?3{B|ZpX=I>GmUY;0)sv$i0c)R^K%?L9 zbOC5Ot8AO4{C^jQy>J~%KAYnPQ-gS76nj9=gt#@?QIb5ziLxSC;*UtJRtb9nMLoRX`KNJhV@7ffACGby z#PC5dsNkrjQwo;xc%!=k|3(|SiRA&9krHXq_aVG`ILykl!ZtT-uIDvY^)~R)xj$BU zKNDqeQqpOd;8JJQ1aLjqyMpLoH~_!P@SbJTqO+NJC-Bj(212MbDVY!);J++{Wn>Dt zf9aym)t?>d=d}DWBK*6wyz7(O=>~H)gQ_L{OrQQjZ!|a3D*#7|q-=#TMh)lXWwSV^ z-KnP%B3exX#X?jJB=tf>hBsMOHIqS zS@04B%bywwmoX@iR5n+g92B0Z5RFw3!xTI6lK@sn$(i^80MMR7QS7Ly*C-|2c9(;T zxN9f*|9BTdbwXQL(wDx#S;%Q`siVdimubz|_|`5HK*=9iTx+5M79ti%a5TP1 z1Pz5?!iX-QVU8Arr^w@3XeQq2(aX^!;hMGp9VchvAs)JJx!(L5ePY?h>oDa9rpySn zOGn{~T{A*^7&qPmbeO_4km>k5L!&je{B0lCk${eL`MU79i+Xb^Q~8f=9L# z)x%0|Hl0;5GiXhG=*D49XNM2cCF2- zvY+Xi+PbHzpMI)qx~Hn_>FJ)Cs_E&O>FIuY=BepRM#hpEihw{tDFR7iu0WFPAUi0F zxS)uU$}Wfss1%9|i}U;azc0}ipUCfi%URy@p7*@_n|PO`&85r*FZ7bg@~vXbV!k?@ zDUVDHmsc;-D{OVwFP=@mDbF;~8un2xRFy4Jz;;{x#RnKyw=6LNcIOD$p#ibpkuts|st38nmex6uKN3;`u5+%=HvnT{bs!Gpv*q=0hHgN9ix0J}Z1Ie)wE z>?rA@n~GIpqEY?Mgc*Ygau*6g|D<#Nw5nQwoU5L0Ayv2SV-lg$x6anALUhZvpf6AA znAiZu3-eBG*U+T4LKW0erj60i-9|vk2Bps-gvle8n~9*9X#CAkxwUHfe0PiuzSsHu z3!CBN$R~JZX0Vy_@B0p9@~u{TeM_SR%zWr23a-gEiY_<*+$NaNFs*gUXSf^9L6gZ0 z0C$cozw1U|-6@v?hk?4jv2uv6Ct3mF?)1Hf=G&xMRsh)tVtFW{w^RiL37$W}sj*K{ zj6p#EN~hg!d5Vv%BiGh$T|W_jnOWU#$*AW3iuPr?Z1INZ-bw1&YVO3DXjYoaA}usN zLVIn>SbFa?E;!6#d#cgTCMF&Dx2mxZ^NuRE#ZL{p7N||!N}Lp8mWh5aHlq|3i*2>7 z{~IC%yhn`|PkPZ!hBRzaKicZS1|BgiYO)7Kc^LmqBSm4ioubc9$aaC_h}|p zx9XgO&C`ubY**Ngi!g{HZMj~y)1->p6z~XHnzH<5!eEoy_w8cWT=NC!8Qn+Zx0V{R zD&{yc!aB%CCoyM4`i;mpVd|OjG7s!~S~8-Fj(`(uS*(D+zkCP76!3 zjixl)XiBr$){bvG-~ zRNLWqY@o3er4--E;?QxT3MZ&fSs`R`;L_T~n9({db#YFKn@c=@I98n_aHWITJ_h({@F%A-^+j$ zVHCuC`h|?OIpqhhL#$?_`To_TqPXgPO4a0e9rDRQXUow9mGsLr+NJ&zwBOO!nleLh zK=n?@9?&)hSAMsDGx)OBnI)LfKGpPiv>mRmaH_cf$aPb1pb29-K~`W>m`R^z`xCKcn@iJ-vAc^cvfp zL+E3{0Q`G3>&@3iz<^v%_I*N0nb8GX!khGlrpcFu*X`#8cS|`1i~lGpB=l zH9r9C{E?l5hk2(71%-pa54vf_AS4S#q2u`~%YwNc#r@4;`L4@!8ddeKjE(>x^Wrum zvdqU@8ZA89D7&-L6t!05=H?cd=Adj3d3uA1%3CsO%kRD$E*0*77jLT*wG!M7Nppi! zEs?a&_G#Kyqq;Nqn~aj&e)J^hFP8mr$ep^Dz4a;LXZBiwbOj^kWgcmq>X_ zhv>SK!tf9n#<&nSSHb;Zat26ZuCLF4t(aPyA`r~rI{M`a86z=`W#9+1@22EpXZ3jR zn(2zTr;dwb6zF08J5aA>0w@R?HhT$18e;a-!hJ&EY&HqXgdXQlBlVrdG(Z*trbNak zfUSJ|waD2&;0atbQa#dTy_jEel-pC64<2d!2->48vQfhzz|mrWCz}~;tegW;488A^ zRfgF{>Lb2(bTokfw`Fm}GBL@mg?OLIejfyvWnWmmVs@45@i=pzHy#o z1#omx1VVLhV8af-zzA7z%3Lfaoa*-!m53lr#Rfo;%)mWNi`r>Ym7$66luPqFIDYzwH_!^S6@|k#Lvw38TuP}CNM}CEcHKo@|HD3h{ zqXUr0yJ&)mv$>uXWP$QDl*`27Z1Rz%e|&w`_#=POh5iufmC^qXBSd^N+BT%#LL@C0 zm8&s-%EnA-gjtsgD=jqCS^Uc9z^y^~JZN)rF-PwJiiBQ$v$tMQ>;RGn~V**YqEwuV_cuUoql{)=S(p{$VAFXHIf$ z9M?}6CF}fOmslNqDKNeOd_Y&knG8^%54%|-n>>1eU&ULRFRps$!*2`x4`x9Q@HaAT zr<_L%>&lb-ZCB*Cy%LkZJ7Q^{7PUET+vhv-s=W`AOuUnTz+M45WGYPaGhVU*+FVw- z_r=lQ@(f|4%@h%%9A3;o@KT^*!59hl)em!$4_@9XNPd)#dh%@WsE1gom`ETf?=*enSh;|%$5H@$ZMhJ9 zC7)K>Ylk(Yr+;sEa+TYAm$G!fh>KWsk$18PGG)s@Z+6acx%KgKH#47;v5!mHRvpBq zib;4T6pE9vUA_ZaJ9p6*8{P|?XUchuQ^mm2nVn~HLZ$y-2ANvm(oV;S%w!N_;k;^2 zAFpUBXcou*=)*WjMAA@W)M%;nJS72%c5Z#*v?i4I;~kacWJIXCbU=5VJtaLc+N)pl zoB!Sy8R`Djz9EoQ+xClG;@$wy>jFlBe7DA(g< z*2(RIXSm!HvlUOJJ4Wogw5&!whY?7Pm*2gc*Tw}603Z4n_;t$1(&#O8&~B9xuRf54 z$fYfAu@X?+mO84@a82UTn)0uG^&2#x{$OR}1EDC*2K$#IBd3+cpj?EnkOH%g;40qV zOH~IDn&4|x!QOoHM7f(EC?4b$)cWJhKx8X%t?j9Q?n!puoIYF6rn`>Ix~c4Dk>bt0 zDQ-&L@;Tt#uz9IB629N+kpSdwsRG<9=Jf3e>IUoDL*X5ZpngINqMnlO*Sqa~K9L1VLOHY%$z0ajG+B#TMx@5?J8HI>-1OV9csb*O}oZ+-QgF1xM%CLvq&J`Se_MnBI zfMkO4P3P1I6cky~kb8K}I)S9x1wY5jBaCsYiiYKSAHo<^L2*AE_aC5d;I`1FMgx|E zor5pY(^Pp(a4S;HGxFPc44E6-K4dC~>xZCcB&Jj7JTJIQ8;f<{36cxW#G_+@x=oml zNGx2eSMzuZbhxSBexhKamL|*mVi2I6XzXYN%i4HbuYfN_HoA%}k?Vx3aj2kj3lLtYH;}y2A znuLs{|CwyJ-=+avSQR!RJnT)mxts)y(jov#UN;N;{%%H?Ivb}l%?0vhy^EaV3Ub0iiH0dNG`e~lOc^Yy>r#^pvo&)4>$WeSo}eHjua9yFqJAS+1qhYZ7oG~Gmr@NJ#W{j zrbPN|hmC_Oon@Atq#%!@Zkwxog8UK6Df7wF17(pt9yU_tS*$kd9aB2-1`I2>qjQb* zhpf6lcqgJOy!hN>U`;JQVidJ}IWy~zVd};?;TtRhoCZ&93F9;CbSQ3r#O2pnJ)BPbk6i z9X`;YHtS`!#9{Jqi?O2G6#!$AIlzCQ6+_Q5cp_6iohG6FkS*O=$jw6o$4Y55KyS|n zQaMJox(Cd?8W}aZ9et4=g!0%PvIwmCkLan90T-AZ*@HP>|AFW-5BUAZCaCm1;{ z=q&r8I?pR`M@SWXPHp2BD$x|txT|d}!AjMA1hYrq`mm@<`Fo(=b7DK9m3M0D=#<7Q z>@G$-X*Ri*(P>717TDb0aK6us=uX)wFKXMtYfK_M?v1U&4O$UnL!z2eMI(DFR=rl) z&mIg5(}paqd9(Gv%1zcQTO2(kabT-?`)y)I*!hHNc61#fd-qxS=nJ50XN{yFEZJr) zUhR-!`C{h^yLxQ7H1GSmbfjga*eC7J+*2U|oyFKmdhz-iOD3FCShngo&EE9TxneLh zJ<4eLHhT(KBVftqlPZISW!4k;uyYa_5h9A9obKy4x6ij&gDHuUiw z*&i3j@v=SJHz(q@y3ioDPU6XW^8yHnZf}CT+u-zR&+>D;Je!Gt37zBv>XdKwybiY< zsQ*>lC7YEIm^n#zqHB|m?H~h)Yb%jN^@S*E#I$SXF3!>9E~@4AfOx`)A4t(~|Arm3 zfe8o%=o^Q!MnetbxkR$#O5F;B5GrLU4OQULZ^kdEub~f2lqRo|3+%@jg0G zKa*Z3@OHjp@ORmTyRvHTTbxX!!^p)qp=ZH=oZoOFCV) zD^{-^e2Fhf_|P5tPmfdK@am=H4|z5FQog=htQQdgz&ylVpHLEyi=VqBaMd$rrrZI# zQvx@ys*>y$8m6RQ0DUG8g%spm9z#4HJiznG@*pScR#5K%st(cwKF7_D5*l$IGzEtl zEZuWtrKcBY2W1W{-x;0DIdYHc_P$6L(>kjpjz3YCv7yJmVy&A#=1}M`Q;gAkyxa+P zZd&$lQ%#t2JHF^#>)*4O(^f>A*} zHxbiL>}SYRTBUFmPr{PqPlBAym|&PC?PT~-!DC(XCrymU;i9E4za|rKA#X;Q4vzv3 zfo}^ZUGOug{m*>i{r-WrK^pv%z1y3jY9OR!Tbn}UVNMuXr{Vtk>`A^g8T*CYD@cIR=@iCrc>PHjve4ASgabiLILjy+s%j>|wUi{G4rp6e z&Dzs^U+&7Xj(}<4c#cA7Mv}tTX9h4;ka1X8pmb;2JP5*}+>B-5aQzIQtuK8 zPJ>Qi2Xy9eNIPc{)c63?)cE1jAv<*`n^vz5=i&KA0yi~1BEBgitTkF>x@Wf4`we`FL`8P<~*h{?!*(j2Qi&whS%iuK3u=# zB6NhiJ#lJla)-Nf}>p54xnmK-(m3XK>c12 zg%~xif5_g01Y@l@17uQD0XJRs5XU-A4NJEq5E^rKsv^l@oqUJm&gaq_zHAsm7yS71GtR0skXlM#!LICPz76u1@ zV*2Lv<{vH3fr?G_aQGuP-%sd>FizUD6vvUpx!VEs@v$FlEU%5)wa&{Iau?rR^hZ_& zSqQ)MTFyVJ{s2k+8>Hs@swKr>IIA0ib-Q^h>koKv-d?EBGaP2s6a;2?&$3aWG<}Jf zyTEf^D+9X%IV-&kw$n2}V1t8mkU{n)Z@u31O%>n;mOzV@?UGW>LRQhK^HBSQ}gE5v#ev_>kZAI;(ib}j_C^+KDm}Yz`F7e!!vc>QRt~RS*ULWO(KBo{oks(Gs)S`^_C_@8YfThr~Ql}ToFFuJKjZ`T(AldS{SU2?K~v@0dLlN+6K&$#3HLoXW- zACv_3)j(O-VkCu4XXF$)(6h@%)qM~UD>dWvv0q{m@*oLcSg5t;YmgqysfLH~0@cxxI}y8ct&GthAlIz+$$hw9(;Yz}y7W ze2CdHcMg`D24`nM_ojbOec7HHb9xwU_6=~&;{u%uS0J~c6*N+xXJiIc;ZJq4BO^`0 zK;2NcyaC$8Az^v&O8WUb(PJop_SPr)j5C}KyL{Za!BitknzCjHA{hlxJ-QUfnXGxD ze2R(mv@UHZAK~?0`2tOPu&%Vy>Ueny79L%DW3oGDh0l5lDm|S!`*6B|2{R~mnBlGnf0FrQ`#w9B9jGFvr2fRSj0!RIzg$dTii-Gk} zHw8#q->CX(h_zMwD! zf4X_}2vR7!D%-*@$t1$$sCEELbGUDa%v)J)&IXvQZS{uWIgAZBs0$p9$@*K&56+~a zPWd=9b_r>Fxqdc48aA7vT4p1K>qYOYf@j?f=q^BxZOS9sv+CKl2YSzZg|N9l?t&8P z{TY&RN#Ft+K>jU1*Gu_1%;AP4gem5*!jFn3hgJDQzC&7b7r4YR7u^P9WGMQp;rY+D z!zyB=+Ms+q*!a*y=Ax?A%*YevN^W;!vDJRCaAf;&SMqI>n|8W9HtIXSZS%W;myvn2 ze)g7O)49xvQ;T;Wn+;-z8)slbsm%IuRtVL0g0#0^aj@y7w=*XR}Qn%S+tgZZOYb$k9>KVvh&u*KSa2mNTP0sfCL ze2I>H-*YtK(%=Zl{LO(2%P>ZMWU`W{Kt4yR)F3krCA0N`ATqsz|d-Xcn)AMq9{nr6fECFyaa`gY7AFF{~ zWV{V;u@J}Ju*1uLCa)Ghx0n;~l@s{*1SlurZ%zMxS|h;fJu-AN^D6!oROT3 z7NAP*6P|b!H9lmDZ|^AI;~P@kseLxaf2*qGy#xJ9*QTmblYJhJirEZ=&Q!UdQLuH@ zE%Mbjff67lfyh3853{SjO;h-+ys7h)s1qne#Rph7S-6Vz$VNsE&ppMetc|U4tUSZX zCGiKE``GeUF$%fdULOo+;;y=>uI5{K!6msLR^qF{gfUWsTy9nkupQL<9&dG%2S(^h zQVld_RLZt|=<f#@?wE%`M<>2eDV_eLgE4d22U{M>9UtMgriQQd+Rd8ZO=V_8Z`@Y zq3}IWFQ-AM2*o8#h>*JWL9ynb(8&LXaxTj-I&#=~z{-5$CTf_yfk17Iiuz`T0mvWBt5uxsPr2v*RAo(Fls+ylcB`) zFqpXuz$E@6WFXdTsi*oEHV_B@w&3JVyvF2|T99KwWa-Xid9fGB!wB8Vbk$FA8Zje( zAs{PjG2dm+-wPW6s303ybG}m!_x)>~b{FgrSd{pJg?fOmWoK?!-NOUjL1{T@qxFE$ zWuG`wqouWhBcyL8Ea~5re4|q5z&?So&0R`%mRg?f{pnmuWNnCh!Ga5V5Q0H%m z3Nl#VG{~urCm~oG$OvpMhgog_E)OEd;I;(!{dC}9C(7eh--NDq%hf}hKEy{giK%LN zq8p>vhZ~*@tiutWOql_E9FD?2|Q-aiAM)JI`o_W!I_F++|sFiH~tkJ5O;J`}|8+ z<;aNK8hYuO@*^O#Mo%l1KJZCo@P=nndSE2q4+5*?sdq49e9x%MLgEgtjbq^DA2O}= z(N6Atzi6<|GqRRDN8Ad)a?Jge94RpYjFo$MlB(%rTItai+Sz+>e;X!UOTX*~31Vf$ z$%c2w$`=0wp5oeGHeIj0-W)fhAq^kNSs-b+kMR6x(U7>n}qxUq3SlidLwP!~Wi%=yrd=-q1QLs#RbN+NHL~)DsxBv*kX8&Z zwXCp$Ju$SCEfM+p;$Op2bTFLpX4gM#n@%FTh#`i8aY{G*g~}sC>Vfwv`LR|JpO~Cp z0J#6xeRQQ>TxRw$K(M}bo6C>mc_6QFBNJ(?&K%vh@sROdoc8~dH~*!7SjL3lwhk{D!kiBx7=^?8NB;aekQ`5ij!v>A z+UCCcF!Z<8{_X)*Dl}O3p{l-Ha}B-!-8+HJOeB~wllqa=%sv59E&G{h5^_#SB&PK| znvAx)Y8Yshu21v6>>@1O!WKVWWYFM5_bOyn>vWRe04`1Z= z$3Jj^dc^L3l7Xo*XIrw0(jOCOkR(gPIJ}8}!T}Dxce2l%Ip;R}lPx8|c_w7YSR6S4 znQl(+k%j*43@+81aZq9a`;IgLjFhx&Xxco)MQ6$#K7yE-Az-fTW&;`*!~V!wj5B}F z`Hm^r^*slY>6aUP$eBl%daI*h70QpWb5ztUZcy3R&E~`e^^a}gKln#NKiI{VZW1;m z%uIB@om-wwq_@a4)LjVVBMV#qo|Y!`IGIjCa*8T|KAQ@&o+#G?jHc0g>sBu<@PP#; zp?)fPY5IYt*b1{&ayKa(it}I~((RF@-*}MrNfpVewjs^5359T}cq?pL66q!__vZ3V zUL1?#g*-jvOzv(b2?8Sv?^xY?nz<&Y%GtbWnohaFi$L}T~>KNG? zmzyDfo!uK>#sl>x#&}%L#W2+z=?#`qylluqv64IMc3OmQ8Cf{RNYP1Y5Ksc#y>dHv zKBm)bOOGz)cQzSaGhdt&n{k+@R^8F!-1EVZ&jkU_Y5+;r!3&v`gpX>!vNy>L0siaV zKAIAEUAkB|eXyodxb)#?f;bAHT@_nG4YrZOlY4|E%mLY?4Qo zPW(P^Oq35~ESEdSSqX4Q58_UF1neXQ{Mg9eZDUXJA^~x}lD~0z46B5X1nWNtm5U?d zX4iO)q^(U<0j9V9lT!};xBdAsT3Rb%OlU^W-ADQ}mTy}B_#{i5*6T6x;*+l11iLLf z1w)9NrK<$W@MoCR1lShEr>r&yV(ROr@+{nx(_w|iZM-8dHz972zmg^=94damj)ehH zIRkXYRPU25fxb7_vS)<_mpsXznSSix)6k!PYqP9c;^32MAx7J-(2Ngf?xh@Kn{0*A z#IYo4t4B3g1bAc%vlXC|YPjoxKj2H+AWJYaMAj;BkB0|A!1RnZ+HQDpb5jF;4fPKu z8n+E5GhzIGpfg^6-GTaz7WFaJ$?btmw@r9_N=TC5lOY7m8CIL_95`CHmXY&xyln9s zcoXoSQEhBu`Lijqh5$2VveJz_4R6z7=1~(%*7~zE#_XeZWT*Jzhv=a_4|A12(cPJS z8aZZSA3Nuo98g0MopX4S4YEU~i$hCA2c$0%0Yndk#w?!Te!xI(urwR5{|%Px7}p!% zB~9bbs>&DP0)MMW%4zF~+(TiF%*hV?3IhP0G@Qa@L$1L8xUVi%<7rS3Vt61$SiRuw zLJ8M?4!`qGa*{7+8ZZfS21#_v7l2zW4-b4MO&L5++r9DvfR+GooGbth(nbJFH7GByGFDYg0CeQg zmq*M)-7zU-OC6bfM+PQ4GWgz>Ec+Ah1vj^N1>U!TmoYyfBqzUQr~IZ@Q}o{=FCw+{lgMQ{JUf?QO{ZMTx^&2~ ziX92m*j`m!DVpI!P!9tMU_Y|BQOUF;ahr$%XLEUunY&+?{>G}I(+RO%=SYt)?-jY} zJ3%G0W>=k3x0>LjNh#tQnVqC?7}G_o@`^d0xjfc*o$SwXCBmM{-|?83lHGZYls8#a z)`YuYw~e}vk?52?SE|VaAgi1<@xw<*zdO+Ek?z1a=EW`IWslepPokeNE|Kf-kbQ_R zcW@7ea)8xgFT$91&ZXH;)897OXY%m3h9 z?na~ty%UQ3{7T?%iob~_J2$^`R1Z(RJ+1g?JelVDEzf>iKG7qvIlHpSHf(2=5)70> zp;Nw{hTtHUHXhmHBiiX?OysujW^auwz4Hew)y6QK1i9ynw>T04FvPzfa|cC)li?5i z$bb#F09cj+<0mtr9X)L&)6~0 zRi)uvZpl9#8Tpe=xs10qDy+6ik)UXu@_N@* z<+$7+@+WQ2RK|qwLRyvNC1oEqtBfSoQS4qi8N2u=|Nc)$P8t65ZR;@d|6+uDVTtnS z6cZN`d`;8iM)tM7oA2|cYcO4QGE?{U$|<^@OqA^meb(bXIpwD$_v7(SSW|A|x*@#D zHkn9T+<4e@;x(?NS7|`27l#J+ur9KA^#h@y+8v4NVP{L+t{L100Q<=>0k1c{N+?r~ z93z;1jvb@B&tiQ6Vz*8*t}8#P0_YVfQgC;`{2DZ|U;{f~0&)ye45gov5mYv8n%%aT z*Gz}U(Hc{j9)p-$M)vNnh8i?_fK;ZO6*)pjS~BMuA9y7OWy0;)Npxg*hm@xrlNkzQ zf%v95TJenba;fI+vAeh8FxYQ{B-0#B4$R?*({HhQfr&s zW_hsIHNh&JXdo2#Jg$=9mN{a)85yp*T*il&ttt5U zGrq=6Ipb+GAwa+_*9nYGP!RP{=r?J$Qb8z8QHGW*Sd(GTQb8>~l}H>d3zsQYE0uK0 znaremI;?Ffi~W?9i-YkAuK%Qe+Qs7a&DE{@4(IZAq0wuK17woin=Jpx>HU(e3; zZL=J^J<}ilR>SAH%f{a^E<8FOJSm~lX@a15wLMRN&8yH3I&beuvKrjWa%8(^5vF|r z2BiW5Nr8fYp(MOGVT2VG$jL?mL$sS*|Z<`1CKMhJRkYffqRI zaGb;M5OMLYj0Z}x3{hxoTH5cgM+km#i&znJ3a?&Xml!6tjf5i|NT@*65HN}QIzxCC z>yH^-I9d*ELO-z+ql>=UJ{@H$XP&&HUVm~ZNoG8v16 zGL>`a9skH8l3YE&uJ978;ScZxt4CaXnK zahA{yn_XjD879b=kN{}70PFfE_{rO;8k~>E4S283E(yx(%(5#EXTP7$iSJUx&m zoY-)!@3z6;^v^Y7?Xc|NpI*bvL2eT}aGHcS0%3BZ@EHY zElh5>IDIAeUhp8ENIeHkNMS{TR&v--z6o$3gSvYS?gSZXe2W@Y$yzHy4+)VljJcm8 z3!)VE+g02rz@nh*0_eXOR~Qf;^8yBt!L3_Fr4NGuNTmp3yV(*~a1XS@;@Fryt_CO7 z`0h;Ed9ISBjD1FxBiG>Yyf{9t8SN+W#A;bv^dOE-0>SH6&?J4@-9enl*OYRPXrwg?j z!|)wbOn`AmrU^o9<5f+-khhjia-NK_JH&xza3pKxeh{sA_lKaWwtT(B&SG*npon5u z&RtF~ofMBJIS2Cq_w@C700LtA)obdd9LC_yC~HMLclM%rhYmnb)fY-MAxw;MzCKPT z>_6Vsap}$sNEccPa)h2+jl%cAT}UP=&6t3s=WxA@^@0OX9r;7BMYPkz?)vMTL*O7f zX#BvC7VohWtZ_C13f1d)pOKm>3wONRw5x=d;Fvg?K3BVosuVIVGc87dS(xAn@D6~z ziN$AUqAfn%)!>s(WP<}i*@J_?A(#hr8#{TmoCm8qviM*0Ywf^<4H-R`$*US+v)hz) zxRu42)(jg~W=urLs~wJ8m@0}t~V>F&5RwjT*)yqpOFuhkQGy8J5v#8M@Z zWxrTWhXsO|)I*Gv-1#a{b)}W0?RaD4K9(sQ?NkKv*5mLKj3+!;&trc^u_dV+H`#TF z;be9|v}Iur8)hy9Jj%3zlsQ*5LeHe-ZAe==-Wme>4ORY4C-(D>`HvhZgnK$!V{@Xk zT06#)kgrKeY8bvi0F|v{ne=0zGA&V$b}YKIY{oFVQ4dZ!oPNA~C(G@CtCK|3uFlGw zo2g%J<1XQMY-A=B3WNAmSF-RBAPC*|{-^Sb0X0Z}WO;CJDCZNog{!dwMZE+vB{M*{ za(4F>K%Sm3;|_01@MJA#Fm_1G=qkFkcxk?=eb-@%a||DuY?Mn_D!wHeu+cAlB*Z5^ zdxKX%wTa|&0&QUq^k=w(ZDZfdtNZM`e@So<+U)>VhETQBx45ERT!DTXneiD$s0)6Q z8p0wx&%TP@oAEiLA22AyTKvtklM<~>IiH?hVJbWh(JLD(@oH|hxIRUO>VGm50E-#G z1c^tEdi@I|&Z;m!esLiS1T$n5CBMGHAj9L#vl;Jz0Pr=M$DcYc-J6{bQCLj=e5m44 zcYMA!db82yX{a(QA4Fs9;Of31q~|{7%FtrPcI>9BWNt)xuGG%~-RXFc@!)kp2L;m2 zd+pPVr&+b>a$kon2?I4j5f`A$a0e@_L@hrY6OS0v{NVObKLuyNihut+P+&t1T~_D! z1|!rxMt=&!*=#O#R`T?5My&N%X7lR|FE+P~6*xn?Vw(3&6_SjS<)3J>jqp-m&UUQ) zmRU3|)DU5>S|egdSot&<_AsoRus4!C4GnX_49#CU6iD z+j&0D)0NP5L*kLEq3hr@1N#<46ORi9#zGPp6J>x4&9aD>V#W%G&IA00`8XPX!pdm= z9F(0S8D{WxhrVX3tWS<=^#)7z8%zoegQM47>{EatuB$uij-Z*T5YfNYuje&GjYzx4 zXJS@M3APQ+XOf-rMK-oAsqNj@S9cph%H!4agkNW3Q=EcY<-QP{;soB&zn{w(Ui7_U zNKC~99I!f8pqTYBDOqgmbm$XWvC%idL^2e^Tk2iBqsJN;)pyBrRNw%_!}%%~Ah z@6vY-gKuq;k?(+sYs>#BCBc~p#%`(A= zyh(OsVz#UMtSdBEO<3-NDK{m8R& zc~@@KaSey|Jh+h!Jz8D@*lM%bW}coJ*`cYkx}Qa)C#bsbrQ+8W^*&YrsuMRz(fkI0 z4t9(~hvWTHZ{eVGdko=#=DrCG$5Jk&SI%T;Qm7@-xSpTgVQPo$xNI4{FgNk3Wkav# zTFfwN@Im@y4Jc$L`(sfVl>5O4(-guusu^359cvI7(O_R4KE+p}1KPoG_a~MkfZ?6m zg13j9#`r?wT-rk*#ocWIN^}HFfrhb|pud7d?` z>Rs9>))72-cXOf)fzGTmnl0CWAjuNWa7I>+F^*uJlG|+4;1^iJ4Jxm4Jo=fb`yz)9 z$`WIiKU(UWCw8OAvyq?61Bq=nF*%9lS)4nc7J4Z~+AVjC!-Zk7NqSsnas*y6;=0R7 zHrl}_0S+ZJ!6lSxN>>Z@tP^cIyz+NXwQb^Y;U~?G8-Eej4nww9e$;ur%b;oG-mPkU zbrXr>*R1;BNmWH8kN;setCn@zMt1^oXFUitk>g#dw*Y|{>RrR)Zj=gVmj6jQfQoQP8ydrt{xDC;A*WP>6(t}Zl-O(4UR1EQ*zj7n~zbRKpW_v z^vXcvS-{UWLE`T?be67FcK^5je2?$LX*b52X;-S#+~h!M_1L#CKya+0IMAT=SvEfk zjQ&23Pm==6u((-K1qbiN*9_Td_tycmqsmxVXgL-&bV)e%yEy6~%-Gg#x z=6nXr8OB!9^L-j6PkPr|_%aatX@I7ZB)h(Rh6_cw3DEVy3stoE~_nH+Dt6< zXpbrL40ASiOMO7*XTHX|9U~3d=yn#eu&s;LMF3DaF9)wM!N%&Z|H(?#MGg=Vy1b_W zA}biE!tasNna)28xn>Rf3yfD=xZ}N!RY;SHMfyVPxwm6hc6p*9T|1b4)H+K^N{!z) zgEgc_Jq+rl{>!XZRKBq7;7h}qDE}#rm`?li44Y^q`4;;;f=o&C;sd-7$qbq={uV@% zmyscPk>^TOx+S`YE@t0Bb5%s`>@TqMx_WKOzHpchPUpsn<(YA3CuhjC9tO@ZS0f9v zKV%@A%gaLsJ?^$5KCanIh93ZJG<{5@|F;~F@k>Bo(LV@_BL^zarBvw|Z1^#O!*zdp zZ@O83Uw&Xbqnf#J{9l8&Y!Xe4U@(4sc!cTjJv3YZ05~DA^Gl4cOJ<&cfs_J>`Rsj9 z&~XiUZ?U;GNngDggg7+#UUtNzU_`{h?e&v6(3`{Q9;iaXk(LIp(=Xntkbf8wJot#5 z0xhT=Ff{BK$RDBg_?GUhpP@JR2K@P!k2A|ic+rb&j+uW#(o8;Y_IATM)bG$1#zA$U z*j1Ur+XN<18k|GFIHBj&uri6CBWDBx0?Cw4W&RisA(M>gF@_5X{~7vlN1$Qy4xrHg ze&)JH03!IoT`sWtj2Um&)f;`I?xF=oP88uPUrr}W`SHo;GNlrT_yIp|_lbGnsB!%@ zVBM^UO)6J5qyp^HU-vl0h^4v{m^kV~R6Bl}+i&OE5PYRlXjE`NJqsU>;QESDz?)K?UkX z0e2Irm zo#;1sK+*7fc%qWy!0L4^ExQ+hp~D-8f(JaaLhvneWpkmho0-F6y$|qfraQcvt(eu{ z%9G2F7rob%@354UyJ)cY!Q$dee5tg{W?W({#O z$dT}N*pO{?Cn)5BZXBI3d>tD9<`JPc_Y}2NvXw@5ZZz57WSNIe>nQ?tk2V|UYn<3&v4rW9d}^KrY9YKSD`Vm4wfkaQ{T46i-*_km4fR2`XsLN# zvT>H{=b1K(s*&Zm8yV$95{Zv$Dj%@>obW>j_PvEz0oL9!w#Hs0j%D2Ker|`Fb}G;c%M0tIY`} zKEi}}U@$M*$4y`3r8VVQT4!GH%u&KKyuD~5NI zGp;#826uwZ(`EO#jf9LWf;4I^ShY9k2_zobx%CF#B4~w%TGtj%ZYpVl7h~mn+|41d zdg%CjfWTCQ@X>yGE=!z0FLhY|qaxCrB2GW{@NSy|gtJ#!ec6f zicLZacmUaifx*q~>~916;)~A8H5sQO;Cbv3)qO-G-P5ea5Oh@g*n61;pL&xvJ`pLW zizV^L+(%9wT}8D!_u$EPaBEjkG|Sd3_uxYS7grF_Wy&0zJF?l;kn6^>T<_vL-r6$XJeUmU;<4WV}eTG;>jKGK0YQrlFIGn%&mqjZFHOU35JY zy@fQD<@&$<12d_-3gZ}dQ(<&7#%~yh?4@NOs-5H@41I3w2VXr(8g=zK7Pz%Xo&u~~*q_Jn#Dw*i9z zU2Oi>fOogd13#69$g7k;9@Lh-atr6F-M>e>tSNAxoEvo0H%F7_~KsVGG zLEGff7^pqm0swX26ZO8r01P8v*`jM+ln&jpjdo|s-}w6trZ%nakUcb-8JIjmfgX_n zV*^c?5%uU^sAq>oY7NBoMV8CsnNae8>xKNjn%yS)vPKc7-TjB_i+t(nvAXxzPXRPC z7C>x{gQUX4JjW=;O6JMdny1sDS`Q^GWT^E&;rQ_&a*+OE_q*lUb3febrCFyw%tYm= zowN+BSf)_w?+Zfo6a{u3r+$lkH?&jZQ~tK{5GJM(6-m7-(fEyLaSVAiD-n*ADkde)p6f6_cTzSz>H*T-(|uHxAnx`#!I;T zINswm1a!**#xY**;%~P+IAn2QU^3^Z>mC7o4NW+_@h;#uS@!bg2IHF6mV>;l5KFsC z6QTCd){O>fQFuE|W{n?CvrPqfrzKQBUhm@y_TNM(7mPS!48OnYJSD|BrH*ZM%3#7E zhk3mF;tzR(dOv8fQ{(L!rK)k90a?tLqt}(Ex!j~Ca6ESTE#(1j%$19Ho7gLdPmk-B z#(QBUPqUDmz?UhSd0Z&?-hi1>(;<&a!5|1(1J_&7>>x;p+F4;gsDCGYn zP@Hvpew*XwCXRguIPPcOn7bKxQ80j|KPiEZo-{k_eL?VU&+Oz>9N1P(hYCe>n?K1) zgw8N2TA4kUsgR-ynodV~A!W^X*jEe=}fz*1lJcM{y< zh>ODW*zHVzy8Jg%Bc706>JirSTpBk0uF^U_Z71&@R85s_GSJcnv1S@rd&FCl<@Fak54=d zkJfQuIi6~&yK_K@~3G3Hr~NDT<5`~H)Sem z>@ko}j)w^FZw)^q1l4co2k>Pkcnom5e%2Bgtx@6vmh&iejC~b9bk=KN4hfjMtY|F% zqvdLH{r$;ZpWHvZ=nuASgt^)jRX~EWQg{FWk6fEN^%BwR>f@|_l*EEZ8xRUQ{p?x| zKAKdryCPlHd$J#lPil0r$KM*z=q0fxf%EG#5E5VktWB!cS)IS$$x9Tn>AJFiX5;tp zzD7Vk;=O@p#Q0T?Ml4I{qG$q$|A{C&?T65tZuu^=OnR1IC|A&2FOEI&qw1DGN~#E0 zHCl7b>H_;8&!P9Y4oxTWf6QZNE}r@ck69H^gi=UTzEiqzl#}q%akK*Tw~> zUp~(a);YR4ow_D8vh>TpcEkty2&w=Pi}Qi!xpKIs`fVY8S*V73pQ76dniOsY9R1Mt z$s|b`lQT+T8b}`eb)$h(qbpBBn+6iOUY}v{ytPt|<3a4IyzADv^&LZ@(53PrJZlV_ z8hA-%#7e3xRo8M_+S!Xdy^wd}cP> zmCUz=4+c-NPqU&4Zt0h2>7VO%O_mv5gR=EYThckmu~MI>H?UB6v2Y*cB?Z!zQ=a4g zXk#Wk0I;*_)8&4SfU#tQ87qJ3hhZE{S&V{1a=V+I4m1$Y4%WL&^hK&~)sqJR%1YHr zqfY*Cy(-X$vN;F|Jk=9OWaUgn(*OEr55aGflVPT2*Quy*dZ za0VNc=ZG2v##8PH(qMQxcc|w?Rz^*!A^s zt`QQ4gibbmrCG_OHP_ek02mBQBZAzNsd|K}(o)!38F-J%JivydC3I)0TJA8Os(2^- z>Ze0sTN9POWf2?OduK64PN9~q|v!m zrqwU6Kq$KLzO=3FbMi(TdRjlajb@mv!Hs1lQWEm-=4|67z^PBWNN5 z(N^>64)X1w{IzEjbaG93jR~zOPcT$5`dV=0&WO3mJ#c7?M`cH7b|U@_JycWBWnLR8%>0;a3<#nMg+CC(1_X(Pif>KK5{{lB0SVZ|*uI?Ep_BhoKJS&IG$W#rNd-4|w|&z<_Oj zR^3nv+tl*hh^QJnM+?X=(@@~OT))Y7GSQUkAI_ySSUye+Vw7$UUv&|0M)ZlUWAJKd z+6Nkh8d=!zX&y&sOe$%%5I#Eg%I0kNuBx}bTJ>vJch?`Jsm4zMOdII*IuVN>n$U*L zybHLSifcJRlz2`lDjk1!I=z>ju`bSO{qiZ$X|g=a-`Uo&VeojSbS;CBjky7T*T1Fx z#%9v&HQ^ba*&O&cTEmF*iZ)-l-_bH{l3 z1Mglw;$QBri=!ZwtAPrB7XJA+J_`GBWxbg`l@?=|sGrU*SF||7Tw7k7Lbe~=>RV0T z(;w$av(XszBg{3Pc)B37t)jt&4zmAM4>rOF1Wqkz54{&rXjJy&9<7Gck)ZMP(Q z&4ab?vD?ZeGE((xLvVmqLm9enpu%-c@`9g2PTFM{vZv@@5+rNl4&R@)RbDmH@xuU3 zCgafHCjseK#~p7gj4C{sI-K1(vhr@tX~#>TH*4NIu{;-`t{XZb%_o*xp?6@o!9TO* zCjdE$&nD}TDglD)1VjBWXDvKOHgUa^F=6y-ylBqmhe$_a$$=KR_2q{>|8|rAJPc|| z(>kervMg#MzQ8OYN@OUXPwH~7DU!SCJ{st@rHS`4y0xWo5|Kh((|p5@mSK~m9d$)G zqk$Qe3g#rsiPVpC*jGa;rH&cK$r5j}M*Sz-T`LrBW5VO*085z!0h4l160;kt9vw$> zzSVO$2EuS{)8NJM4={k>gFp1uZYY!`2ICOY*NAF|eohk4O)Zj0? z%DCvXD_B~=6F^4En}x#B*9U*QS|BwFf_LXh_-N^TQH@yGOUAyaW z{!g}T8ZC<_6Og|GZY{3vS-t9uBQItgchUhFt4BmdW|dH(iSi1+nlfWJEO(lz z<*&R-E2nZbN;-Sy&c|8!6lzp{+k4C-?9M^Ch!6A4So!C4m}-&HGg;+ywTNZPjXm5L*sV4@d+^|*uV^1XtPHKWP}48kqJ(4h6Pr*z!h%D40m|I6IqZI z+293lWJeD8z!y1@3%QX8dEp0t1WMLV=d2gINwI-xVVpewqeJ9?ledZ9P^pfCENKL%hR24OIUU?_%RI7T2ABQXl2 zF$QBX4&yNa6EO*sF$Hm$ifNdR8JLM#n2kA@i+IdK0_I}@7Ge<=V+j(m6w9z2E3gu) zuo`Qy7VEGcN!Wmm*o4j4g00ww?bv~x*oEELgT2^?{WySwNX8)?#t|IFF&xJUoWv=d z#u=Q&Ih@A@T*M_@#uZ$}HC)FH+{7*1#vR16wJj5eB#uGfnGo;`-Uf?BO;Wggi zE#BchKHwuh;WNJAE56}7e&8p5Ar)!(jdc9M-^mFv#%WMYij87cY!y4jUdfwkCIpMQ~Z^D zN`R7IDWC)@1(hJBkWyGFq7+qvm10V9rG!#aDW!xcrIj*DS*4s(Ua6pjDixJVN@b;r qQdOy@R99*!HI-UQZ6!>pql7CFN~BU(si)LeqLgT3GM|buX3akeY`cR1 diff --git a/app/src/main/java/com/quranapp/android/QuranApp.kt b/app/src/main/java/com/quranapp/android/QuranApp.kt index 513102228..1b28116ac 100644 --- a/app/src/main/java/com/quranapp/android/QuranApp.kt +++ b/app/src/main/java/com/quranapp/android/QuranApp.kt @@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatDelegate import com.alfaazplus.sunnah.ui.utils.shared_preference.DataStoreManager import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.bookmark.UserDataMigrationManager +import com.quranapp.android.search.SearchIndexScheduler import com.quranapp.android.utils.app.DownloadSourceUtils import com.quranapp.android.utils.app.NotificationUtils import com.quranapp.android.utils.exceptions.CustomExceptionHandler @@ -50,5 +51,7 @@ class QuranApp : Application() { RecitationModelManager.get(this).migrateLegacyData() ReaderIndexViewModel.migrateFavourites(this) UserDataMigrationManager(this).migrate() + + SearchIndexScheduler.scheduleTranslationSearchIndexIfNeeded(applicationContext) } } diff --git a/app/src/main/java/com/quranapp/android/activities/ActivitySearch.java b/app/src/main/java/com/quranapp/android/activities/ActivitySearch.java deleted file mode 100644 index 3b2c96869..000000000 --- a/app/src/main/java/com/quranapp/android/activities/ActivitySearch.java +++ /dev/null @@ -1,610 +0,0 @@ -package com.quranapp.android.activities; - -import static android.view.View.FOCUS_DOWN; -import static android.view.View.GONE; -import static android.view.View.VISIBLE; -import static com.quranapp.android.utils.univ.RegexPattern.CHAPTER_OR_JUZ_PATTERN; -import static com.quranapp.android.utils.univ.RegexPattern.VERSE_JUMP_PATTERN; -import static com.quranapp.android.utils.univ.RegexPattern.VERSE_RANGE_JUMP_PATTERN; -import static com.quranapp.android.widgets.compound.PeaceCompoundButton.COMPOUND_TEXT_GRAVITY_LEFT; - -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.speech.RecognizerIntent; -import android.text.Editable; -import android.text.TextUtils; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.widget.NestedScrollView; -import androidx.fragment.app.FragmentTransaction; - -import com.peacedesign.android.utils.DrawableUtils; -import com.quranapp.android.R; -import com.quranapp.android.activities.base.BaseActivity; -import com.quranapp.android.api.models.translation.TranslationBookInfoModel; -import com.quranapp.android.components.quran.QuranMeta; -import com.quranapp.android.components.search.ChapterJumpModel; -import com.quranapp.android.components.search.JuzJumpModel; -import com.quranapp.android.components.search.SearchResultModelBase; -import com.quranapp.android.components.search.TafsirJumpModel; -import com.quranapp.android.components.search.VerseJumpModel; -import com.quranapp.android.compose.utils.preferences.ReaderPreferences; -import com.quranapp.android.databinding.ActivitySearchBinding; -import com.quranapp.android.db.DatabaseProvider; -import com.quranapp.android.repository.UserRepository; -import com.quranapp.android.db.search.SearchHistoryDBHelper; -import com.quranapp.android.frags.search.FragSearchResult; -import com.quranapp.android.frags.search.FragSearchSuggestions; -import com.quranapp.android.utils.Log; -import com.quranapp.android.utils.extensions.ViewPaddingKt; -import com.quranapp.android.utils.quran.QuranUtils; -import com.quranapp.android.utils.reader.factory.QuranTranslationFactory; -import com.quranapp.android.utils.search.SearchFilters; -import com.quranapp.android.utils.search.SearchLocalHistoryManager; -import com.quranapp.android.utils.simplified.SimpleTextWatcher; -import com.quranapp.android.utils.univ.StringUtils; -import com.quranapp.android.widgets.bottomSheet.PeaceBottomSheet; -import com.quranapp.android.widgets.bottomSheet.PeaceBottomSheetParams; -import com.quranapp.android.widgets.radio.PeaceRadioButton; -import com.quranapp.android.widgets.radio.PeaceRadioGroup; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.MatchResult; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import kotlin.Unit; - -public class ActivitySearch extends BaseActivity { - private final ActivityResultLauncher mActivityResultLauncher = activityResultHandler(); - public ActivitySearchBinding mBinding; - public QuranTranslationFactory mTranslFactory; - public UserRepository userRepository; - public SearchHistoryDBHelper mHistoryDBHelper; - private FragSearchResult mFragSearchResult; - private FragSearchSuggestions mFragSearchSugg; - public QuranMeta mQuranMeta; - public SearchFilters mSearchFilters; - public SearchLocalHistoryManager mLocalHistoryManager; - private final Handler mSuggHandler = new Handler(Looper.getMainLooper()); - public Map availableTranslModels; - public boolean mSupportsVoiceInput; - - @Override - protected void onDestroy() { - if (mFragSearchResult != null) { - mFragSearchResult.destroy(); - } - if (mFragSearchSugg != null) { - mFragSearchSugg.destroy(); - } - if (mHistoryDBHelper != null) { - mHistoryDBHelper.close(); - } - - if (mTranslFactory != null) { - mTranslFactory.close(); - } - - super.onDestroy(); - } - - @Override - protected void onResume() { - super.onResume(); - prepareAvailableTrans(); - } - - @Override - protected int getLayoutResource() { - return R.layout.activity_search; - } - - @Override - protected void preActivityInflate(@Nullable Bundle savedInstanceState) { - mTranslFactory = new QuranTranslationFactory(this); - userRepository = DatabaseProvider.INSTANCE.getUserRepository(this); - mHistoryDBHelper = new SearchHistoryDBHelper(this); - } - - @Override - protected void onActivityInflated(@NonNull View activityView, @Nullable Bundle savedInstanceState) { - mBinding = ActivitySearchBinding.bind(activityView); - - QuranMeta.prepareInstance(this, quranMeta -> { - mQuranMeta = quranMeta; - - prepareAvailableTrans(); - init(); - }); - } - - private void prepareAvailableTrans() { - availableTranslModels = mTranslFactory.getAvailableTranslationBooksInfo(); - } - - private void init() { - initManagers(this); - initHeader(); - initFrags(); - initVoiceInput(); - } - - private void initHeader() { - mBinding.back.setOnClickListener(v -> onBackPressed()); - mBinding.voiceSearch.setOnClickListener(v -> startVoiceRecognitionActivity()); - mBinding.filter.setOnClickListener(v -> mSearchFilters.show()); - - mBinding.search.setOnFocusChangeListener((v, hasFocus) -> { - if (hasFocus) { - showSugg(); - } - }); - mBinding.search.addTextChangedListener(new SimpleTextWatcher() { - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (!mBinding.search.hasFocus()) { - return; - } - - mBinding.voiceSearch.setVisibility((mSupportsVoiceInput && s.length() == 0) ? VISIBLE : GONE); - } - - @Override - public void afterTextChanged(Editable s) { - if (!mBinding.search.hasFocus()) { - return; - } - - if (!mFragSearchSugg.isVisible()) { - showSugg(); - } - - mSuggHandler.removeCallbacksAndMessages(null); - mSuggHandler.postDelayed(() -> initSugg(s.toString()), 200); - } - }); - mBinding.search.setOnEditorActionListener((v, actionId, event) -> { - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - initSearch(v.getText().toString(), false, false); - } - return true; - }); - - Drawable chevronRight = drawable(com.peacedesign.R.drawable.dr_icon_chevron_right); - chevronRight = DrawableUtils.rotate(this, chevronRight, 90F); - chevronRight.setTintList(colorStateList(R.color.colorIcon)); - mBinding.btnSelectTransl.setDrawables(null, null, chevronRight, null); - - mBinding.btnSelectTransl.setOnClickListener(v -> showTranslationSheet()); - mBinding.btnQuickLinks.setOnCheckChangedListener((button, isChecked) -> { - mSearchFilters.showQuickLinks = isChecked; - reSearch(); - - return Unit.INSTANCE; - }); - - mBinding.filter.setVisibility(GONE); - mBinding.btnSelectTransl.setVisibility(GONE); - mBinding.btnQuickLinks.setVisibility(GONE); - } - - private void setupActionButtons(boolean isSuggFrag) { - if (isSuggFrag) { - mBinding.filter.setVisibility(GONE); - boolean isQueryEmpty = mBinding.search.getText() == null || mBinding.search.getText().length() == 0; - mBinding.voiceSearch.setVisibility((mSupportsVoiceInput && isQueryEmpty) ? VISIBLE : GONE); - } else { - if (!mFragSearchResult.mIsLoadingInProgress) { - mBinding.filter.setVisibility(VISIBLE); - mBinding.voiceSearch.setVisibility(mSupportsVoiceInput ? VISIBLE : GONE); - } - } - } - - private void initFrags() { - mFragSearchResult = new FragSearchResult(); - mFragSearchSugg = FragSearchSuggestions.newInstance(); - - getSupportFragmentManager().addFragmentOnAttachListener((fragmentManager, fragment) -> { - boolean isSuggFrag = fragment instanceof FragSearchSuggestions; - - setupActionButtons(isSuggFrag); - setupHeader(isSuggFrag); - mBinding.search.setLongClickable(isSuggFrag); - - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - - if (isSuggFrag) { - mBinding.search.requestFocus(); - } else { - mBinding.search.clearFocus(); - imm.hideSoftInputFromWindow(mBinding.search.getWindowToken(), 0); - } - }); - - addInitialFrag(); - } - - private void addInitialFrag() { - mBinding.search.postDelayed(() -> mBinding.search.requestFocus(FOCUS_DOWN, null), 50); - } - - private void initVoiceInput() { - // Disable button if no recognition service is present - PackageManager pm = getPackageManager(); - List activities = pm.queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), - 0); - mSupportsVoiceInput = activities.size() != 0; - if (!mSupportsVoiceInput) { - mBinding.voiceSearch.setEnabled(false); - mBinding.voiceSearch.setVisibility(GONE); - } - } - - private void initManagers(ActivitySearch activitySearch) { - String initiallySelectedSlug = null; - - Set savedTranslations = ReaderPreferences.INSTANCE.getTranslations(); - for (String slug : savedTranslations) { - if (availableTranslModels.containsKey(slug)) { - initiallySelectedSlug = slug; - break; - } - } - - if (initiallySelectedSlug == null) { - for (TranslationBookInfoModel bookInfo : availableTranslModels.values()) { - if ("en".equals(bookInfo.getLangCode())) { - initiallySelectedSlug = bookInfo.getSlug(); - break; - } - } - } - - if (initiallySelectedSlug == null && !availableTranslModels.isEmpty()) { - initiallySelectedSlug = availableTranslModels.keySet().iterator().next(); - } - - mSearchFilters = new SearchFilters(activitySearch, initiallySelectedSlug); - mLocalHistoryManager = new SearchLocalHistoryManager(); - } - - public void reSearch() { - initSearch(mLocalHistoryManager.getLastQuery(), true, true); - } - - public void initSearch(String query, boolean reSearch, boolean protectFromLocalHistory) { - String finalQuery = query.toLowerCase(); - - if (TextUtils.isEmpty(finalQuery)) { - return; - } - - boolean isSameQuery = mLocalHistoryManager.isCurrentQuery(finalQuery); - if (!protectFromLocalHistory) { - mLocalHistoryManager.onTravelForward(finalQuery); - } else { - mLocalHistoryManager.setLastQuery(finalQuery); - } - - hideSugg(() -> { - if (reSearch || !isSameQuery) { - mHistoryDBHelper.addToHistory(finalQuery, () -> Log.d("ADDED/UPDATED TO HISTORY")); - mFragSearchResult.initSearch(this); - } - }); - } - - private void initSugg(String query) { - try { - mFragSearchSugg.initSuggestion(this, query); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void applyFilters() { - reSearch(); - } - - private void showSugg() { - if (mFragSearchSugg.isVisible()) { - return; - } - - FragmentTransaction t = getSupportFragmentManager().beginTransaction(); - t.replace(R.id.frameLayout, mFragSearchSugg); - t.runOnCommit(() -> initSugg(mLocalHistoryManager.getLastQuery())); - t.commitAllowingStateLoss(); - } - - private void hideSugg(Runnable runnable) { - if (mFragSearchResult.isVisible()) { - mBinding.search.clearFocus(); - mBinding.search.setText(mLocalHistoryManager.getLastQuery()); - mBinding.header.setExpanded(true); - - if (runnable != null) { - runnable.run(); - } - return; - } - - mBinding.search.clearFocus(); - mBinding.search.setText(mLocalHistoryManager.getLastQuery()); - mBinding.header.setExpanded(true); - - FragmentTransaction t = getSupportFragmentManager().beginTransaction(); - t.replace(R.id.frameLayout, mFragSearchResult); - t.runOnCommit(() -> { - if (runnable != null) { - runnable.run(); - } - setupHeader(false); - }); - t.commitAllowingStateLoss(); - } - - private void showTranslationSheet() { - PeaceBottomSheet sheet = new PeaceBottomSheet(); - NestedScrollView scrollView = new NestedScrollView(this); - PeaceRadioGroup radioGroup = new PeaceRadioGroup(this); - ViewPaddingKt.updatePaddingVertical(radioGroup, dp2px(15F), dp2px(25F)); - int padH = dp2px(25F); - int padV = dp2px(13F); - int spaceBtwn = dp2px(15F); - - for (TranslationBookInfoModel bookInfo : availableTranslModels.values()) { - PeaceRadioButton radio = new PeaceRadioButton(this); - ViewPaddingKt.updatePaddings(radio, padH, padV); - radio.setBackgroundResource(com.peacedesign.R.drawable.dr_bg_action); - radio.setText(bookInfo.getBookName()); - radio.setTag(bookInfo); - radio.setTextAppearance(R.style.TextAppearanceCommonTitle); - radio.setChecked(bookInfo.getSlug().equals(mSearchFilters.selectedTranslSlug)); - radio.setForceTextGravity(COMPOUND_TEXT_GRAVITY_LEFT); - radio.setSpaceBetween(spaceBtwn); - radioGroup.addView(radio); - } - radioGroup.setOnCheckChangedListener((btn, id) -> { - sheet.dismiss(); - TranslationBookInfoModel bookInfo = (TranslationBookInfoModel) btn.getTag(); - mSearchFilters.selectedTranslSlug = bookInfo.getSlug(); - mBinding.btnSelectTransl.setText(bookInfo.getBookName()); - reSearch(); - - return Unit.INSTANCE; - }); - scrollView.addView(radioGroup); - - PeaceBottomSheetParams params = sheet.getParams(); - params.setHeaderTitleResource(R.string.strLabelSelectTranslation); - params.setContentView(scrollView); - sheet.show(getSupportFragmentManager()); - } - - private void setupHeader(boolean isFragSugg) { - mBinding.header.setElevation(isFragSugg ? 0 : dp2px(4)); - mBinding.btnSelectTransl.setVisibility(isFragSugg ? GONE : VISIBLE); - mBinding.btnQuickLinks.setVisibility(isFragSugg ? GONE : VISIBLE); - } - - /** - * Fire an intent to start the voice recognition activity. - */ - private void startVoiceRecognitionActivity() { - if (!mSupportsVoiceInput) { - return; - } - - try { - Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); - intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); - intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Search in Quran"); - mActivityResultLauncher.launch(intent); - } catch (ActivityNotFoundException ignored) { - } - } - - /** - * Handle the results from the voice recognition activity. - */ - private ActivityResultLauncher activityResultHandler() { - return registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), resultIntent -> { - Intent data = resultIntent.getData(); - if (data == null) { - return; - } - int resultCode = resultIntent.getResultCode(); - if (resultCode == RESULT_OK) { - // Populate the wordsList with the String values the recognition engine thought it heard - ArrayList matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); - if (matches.size() < 1) { - return; - } - String query = matches.get(0); - initSearch(query, false, false); - } - }); - } - - @Override - public void onBackPressed() { - if (mFragSearchSugg.isVisible()) { - if (!TextUtils.isEmpty(mLocalHistoryManager.getLastQuery())) { - hideSugg(null); - } else { - super.onBackPressed(); - } - } else { - if (mLocalHistoryManager.hasHistories()) { - initSearch(mLocalHistoryManager.onTravelBackward(), false, true); - } else { - super.onBackPressed(); - } - } - } - - public void pushQuery(CharSequence query) { - mBinding.search.setText(query); - mBinding.search.setSelection(query.length()); - } - - public ArrayList prepareJumper(QuranMeta quranMeta, String query) throws NumberFormatException { - query = StringUtils.escapeRegex(query); - - ArrayList jumperSuggCollection = new ArrayList<>(); - - Matcher mtchrVRangeJump = VERSE_RANGE_JUMP_PATTERN.matcher(query); - Matcher mtchrVJump = VERSE_JUMP_PATTERN.matcher(query); - Matcher mtchrChapOrJuzNo = CHAPTER_OR_JUZ_PATTERN.matcher(query); - - Pattern patternQuery = Pattern.compile(query, Pattern.DOTALL | Pattern.CASE_INSENSITIVE); - - if (mtchrVRangeJump.find()) { - MatchResult r = mtchrVRangeJump.toMatchResult(); - if (r.groupCount() >= 3) { - int chapNo = Integer.parseInt(r.group(1)); - - if (!QuranMeta.isChapterValid(chapNo)) { - return jumperSuggCollection; - } - - int fromVerse = Integer.parseInt(r.group(2)); - int toVerse = Integer.parseInt(r.group(3)); - - // swap - int tmpFrom = fromVerse; - fromVerse = Math.min(fromVerse, toVerse); - toVerse = Math.max(tmpFrom, toVerse); - - if (quranMeta.isVerseRangeValid4Chapter(chapNo, fromVerse, toVerse)) { - makeVerseSuggestion(quranMeta, jumperSuggCollection, chapNo, fromVerse, toVerse); - } - - if (fromVerse == 0) { - fromVerse = 1; - } - - boolean isFromVerseValid = quranMeta.isVerseValid4Chapter(chapNo, fromVerse); - boolean isToVerseValid = quranMeta.isVerseValid4Chapter(chapNo, toVerse); - - if (isFromVerseValid) { - makeVerseSuggestion(quranMeta, jumperSuggCollection, chapNo, fromVerse, fromVerse); - } - - if (isToVerseValid) { - makeVerseSuggestion(quranMeta, jumperSuggCollection, chapNo, toVerse, toVerse); - } - - if (isFromVerseValid) { - makeTafsirSuggestion(quranMeta, jumperSuggCollection, chapNo, fromVerse); - } - - if (isToVerseValid) { - makeTafsirSuggestion(quranMeta, jumperSuggCollection, chapNo, toVerse); - } - - makeChapterSuggestion(quranMeta, jumperSuggCollection, chapNo); - } - } else if (mtchrVJump.find()) { - MatchResult r = mtchrVJump.toMatchResult(); - if (r.groupCount() >= 2) { - int chapNo = Integer.parseInt(r.group(1)); - int verseNo = Integer.parseInt(r.group(2)); - - if (!QuranMeta.isChapterValid(chapNo)) { - return jumperSuggCollection; - } - - if (quranMeta.isVerseValid4Chapter(chapNo, verseNo)) { - makeVerseSuggestion(quranMeta, jumperSuggCollection, chapNo, verseNo, verseNo); - makeTafsirSuggestion(quranMeta, jumperSuggCollection, chapNo, verseNo); - } - - makeChapterSuggestion(quranMeta, jumperSuggCollection, chapNo); - } - } else if (mtchrChapOrJuzNo.find()) { - MatchResult r = mtchrChapOrJuzNo.toMatchResult(); - if (r.groupCount() >= 1) { - int chapOrJuzNo = Integer.parseInt(r.group(1)); - - if (QuranMeta.isChapterValid(chapOrJuzNo)) { - makeChapterSuggestion(quranMeta, jumperSuggCollection, chapOrJuzNo); - } - - if (QuranMeta.isJuzValid(chapOrJuzNo)) { - makeJuzSuggestion(quranMeta, jumperSuggCollection, chapOrJuzNo); - } - } - } - - QuranUtils.iterateChapterNo(chapterNo -> { - QuranMeta.ChapterMeta chapterMeta = quranMeta.getChapterMeta(chapterNo); - if (chapterMeta != null) { - String destination = chapterMeta.tags; - Matcher mtchrQuery = patternQuery.matcher(destination); - if (mtchrQuery.find()) { - makeChapterSuggestion(quranMeta, jumperSuggCollection, chapterNo); - } - } - }); - - return jumperSuggCollection; - } - - private void makeJuzSuggestion(QuranMeta quranMeta, ArrayList collection, int juzNo) { - JuzJumpModel juzJumpModel = new JuzJumpModel(juzNo, "Juz " + juzNo, - quranMeta.getJuzNameTransliterated(juzNo), quranMeta.getJuzNameArabic(juzNo)); - - collection.add(juzJumpModel); - } - - private void makeChapterSuggestion(QuranMeta quranMeta, ArrayList collection, int chapNo) { - collection.add(new ChapterJumpModel(chapNo, String.valueOf(chapNo), quranMeta.getChapterName(this, chapNo), - quranMeta.getChapterNameTranslation(chapNo))); - } - - private void makeVerseSuggestion(QuranMeta quranMeta, ArrayList collection, int chapNo, int fromVerse, int toVerse) { - String formatted = getString(R.string.strTitleReadVerseRange, fromVerse, toVerse); - if (fromVerse == toVerse) formatted = getString(R.string.strTitleGotoVerseNo, fromVerse); - - VerseJumpModel verseJumpModel = new VerseJumpModel(chapNo, fromVerse, toVerse, formatted, - quranMeta.getChapterName(this, chapNo, true)); - - collection.add(verseJumpModel); - } - - private void makeTafsirSuggestion(QuranMeta quranMeta, ArrayList collection, int chapNo, int verseNo) { - TafsirJumpModel tafsirJumpModel = new TafsirJumpModel(chapNo, verseNo, getString(R.string.strTitleReadTafsirOfVerse, verseNo), - quranMeta.getChapterName(this, chapNo, true)); - - collection.add(tafsirJumpModel); - } - - public static class SearchResultViewType { - public static final int VERSE_JUMPER = 0x0; - public static final int CHAPTER_JUMPER = 0x1; - public static final int JUZ_JUMPER = 0x2; - public static final int TAFSIR_JUMPER = 0x3; - public static final int RESULT_COUNT = 0x4; - public static final int RESULT = 0x5; - } -} diff --git a/app/src/main/java/com/quranapp/android/activities/ActivitySearch.kt b/app/src/main/java/com/quranapp/android/activities/ActivitySearch.kt new file mode 100644 index 000000000..9c04da167 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/activities/ActivitySearch.kt @@ -0,0 +1,83 @@ +package com.quranapp.android.activities + +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.speech.RecognizerIntent +import android.view.View +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import com.quranapp.android.activities.base.BaseActivity +import com.quranapp.android.compose.screens.search.SearchScreen +import com.quranapp.android.compose.theme.QuranAppTheme +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow + +class ActivitySearch : BaseActivity() { + private val _voiceQueryFlow = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) + + override fun getLayoutResource(): Int = 0 + + override fun onActivityInflated(activityView: View, savedInstanceState: Bundle?) { + enableEdgeToEdge() + + setContentView( + ComposeView(this).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + QuranAppTheme { + val voiceLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ) { result -> + if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult + + val matches = + result.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) + + val text = + matches?.firstOrNull() ?: return@rememberLauncherForActivityResult + + _voiceQueryFlow.tryEmit(text) + } + + val supportsVoice = packageManager.queryIntentActivities( + Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), + PackageManager.MATCH_DEFAULT_ONLY, + ).isNotEmpty() + + val voiceSearchFlow = _voiceQueryFlow.asSharedFlow() + + SearchScreen( + supportsVoiceSearch = supportsVoice, + voiceSearchFlow = voiceSearchFlow, + onVoiceSearchClick = { + runCatching { + val intent = + Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { + putExtra( + RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH, + ) + putExtra( + RecognizerIntent.EXTRA_PROMPT, + "Search in Quran" + ) + } + + voiceLauncher.launch(intent) + } + }, + ) + } + } + }, + ) + } +} diff --git a/app/src/main/java/com/quranapp/android/adapters/search/ADPSearchSugg.java b/app/src/main/java/com/quranapp/android/adapters/search/ADPSearchSugg.java deleted file mode 100644 index 2184a2687..000000000 --- a/app/src/main/java/com/quranapp/android/adapters/search/ADPSearchSugg.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.quranapp.android.adapters.search; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Configuration; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.recyclerview.widget.RecyclerView; - -import com.peacedesign.android.utils.ColorUtils; -import com.peacedesign.android.widget.dialog.base.PeaceDialog; -import com.quranapp.android.R; -import com.quranapp.android.activities.ActivitySearch; -import com.quranapp.android.components.search.ChapterJumpModel; -import com.quranapp.android.components.search.JuzJumpModel; -import com.quranapp.android.components.search.SearchHistoryModel; -import com.quranapp.android.components.search.SearchResultModelBase; -import com.quranapp.android.components.search.TafsirJumpModel; -import com.quranapp.android.components.search.VerseJumpModel; -import com.quranapp.android.databinding.LytReaderJuzSpinnerItemBinding; -import com.quranapp.android.databinding.LytSearchHistoryItemBinding; -import com.quranapp.android.vh.search.VHChapterJump; -import com.quranapp.android.vh.search.VHJuzJump; -import com.quranapp.android.vh.search.VHSearchResultBase; -import com.quranapp.android.vh.search.VHTafsirJump; -import com.quranapp.android.vh.search.VHVerseJump; -import com.quranapp.android.widgets.IconedTextView; -import com.quranapp.android.widgets.chapterCard.ChapterCard; - -import java.util.ArrayList; - -public class ADPSearchSugg extends RecyclerView.Adapter { - private final Configuration mConfigs; - private ActivitySearch mActivitySearch; - public ArrayList mSuggModels = new ArrayList<>(); - private final LayoutInflater mInflater; - - public ADPSearchSugg(Context context) { - mInflater = LayoutInflater.from(context); - mConfigs = context.getResources().getConfiguration(); - - setHasStableIds(true); - } - - @SuppressLint("NotifyDataSetChanged") - public void setSuggModels(ActivitySearch activitySearch, ArrayList suggModels) { - mActivitySearch = activitySearch; - mSuggModels = suggModels; - } - - @Override - public int getItemCount() { - return mSuggModels.size(); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public int getItemViewType(int position) { - return position; - } - - @NonNull - @Override - public VHSearchResultBase onCreateViewHolder(@NonNull ViewGroup parent, int position) { - final VHSearchResultBase vh; - - SearchResultModelBase modelBase = mSuggModels.get(position); - if (modelBase instanceof VerseJumpModel) { - vh = new VHVerseJump(new AppCompatTextView(parent.getContext()), true); - } else if (modelBase instanceof ChapterJumpModel) { - vh = new VHChapterJump(new ChapterCard(parent.getContext()), true); - } else if (modelBase instanceof JuzJumpModel) { - vh = new VHJuzJump(LytReaderJuzSpinnerItemBinding.inflate(mInflater, parent, false), true); - } else if (modelBase instanceof TafsirJumpModel) { - vh = new VHTafsirJump(new IconedTextView(parent.getContext()), true); - } else if (modelBase instanceof SearchHistoryModel) { - vh = new VHSearchHistoryItem(LytSearchHistoryItemBinding.inflate(mInflater, parent, false)); - } else { - vh = new VHSearchResultBase(new View(parent.getContext())); - } - return vh; - } - - @Override - public void onBindViewHolder(@NonNull VHSearchResultBase holder, int position) { - SearchResultModelBase modelBase = mSuggModels.get(position); - holder.bind(modelBase, position); - } - - class VHSearchHistoryItem extends VHSearchResultBase { - private final LytSearchHistoryItemBinding mBinding; - - public VHSearchHistoryItem(LytSearchHistoryItemBinding binding) { - super(binding.getRoot()); - mBinding = binding; - } - - @Override - public void bind(@NonNull SearchResultModelBase parentModel, int pos) { - if (mConfigs.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - mBinding.push.setRotation(90); - } - - SearchHistoryModel model = (SearchHistoryModel) parentModel; - bindHistoryItem(mBinding, model); - } - - private void bindHistoryItem(LytSearchHistoryItemBinding binding, SearchHistoryModel historyModel) { - binding.getRoot().setOnClickListener(v -> { - if (mActivitySearch != null) { - mActivitySearch.initSearch(historyModel.getText().toString(), false, false); - } - }); - binding.getRoot().setOnLongClickListener(v -> { - removeHistoryCheckpoint(v.getContext(), historyModel); - return true; - }); - binding.text.setText(historyModel.getText()); - binding.push.setOnClickListener(v -> { - if (mActivitySearch != null) { - mActivitySearch.pushQuery(historyModel.getText()); - } - }); - } - - private void removeHistoryCheckpoint(Context context, SearchHistoryModel historyModel) { - PeaceDialog.Builder builder = PeaceDialog.newBuilder(context); - builder.setTitle(R.string.strMsgHistoryRemove); - builder.setMessage(historyModel.getText()); - builder.setTitleTextAlignment(View.TEXT_ALIGNMENT_CENTER); - builder.setMessageTextAlignment(View.TEXT_ALIGNMENT_CENTER); - builder.setNeutralButton(R.string.strLabelCancel, null); - builder.setPositiveButton(R.string.strLabelRemove, ColorUtils.DANGER, (dialog, which) -> { - if (mActivitySearch != null) { - mActivitySearch.mHistoryDBHelper.removeFromHistory(historyModel.getId(), () -> { - String msg = context.getString(R.string.strMsgHistoryRemoved, historyModel.getText()); - Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); - - mSuggModels.remove(getAdapterPosition()); - notifyItemRemoved(getAdapterPosition()); - }); - } - }); - builder.setFocusOnPositive(true); - builder.show(); - } - } -} diff --git a/app/src/main/java/com/quranapp/android/adapters/search/ADPVerseResults.java b/app/src/main/java/com/quranapp/android/adapters/search/ADPVerseResults.java deleted file mode 100644 index f9577e578..000000000 --- a/app/src/main/java/com/quranapp/android/adapters/search/ADPVerseResults.java +++ /dev/null @@ -1,448 +0,0 @@ -package com.quranapp.android.adapters.search; - -import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static com.quranapp.android.activities.ActivitySearch.SearchResultViewType.CHAPTER_JUMPER; -import static com.quranapp.android.activities.ActivitySearch.SearchResultViewType.JUZ_JUMPER; -import static com.quranapp.android.activities.ActivitySearch.SearchResultViewType.RESULT; -import static com.quranapp.android.activities.ActivitySearch.SearchResultViewType.RESULT_COUNT; -import static com.quranapp.android.activities.ActivitySearch.SearchResultViewType.TAFSIR_JUMPER; -import static com.quranapp.android.activities.ActivitySearch.SearchResultViewType.VERSE_JUMPER; -import static com.quranapp.android.utils.univ.Keys.READER_KEY_SAVE_TRANSL_CHANGES; -import static com.quranapp.android.utils.univ.Keys.READER_KEY_TRANSL_SLUGS; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.graphics.Typeface; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.ForegroundColorSpan; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.fragment.app.FragmentManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.peacedesign.android.utils.Dimen; -import com.peacedesign.android.utils.span.TypefaceSpan2; -import com.quranapp.android.R; -import com.quranapp.android.activities.ActivityReader; -import com.quranapp.android.activities.ActivitySearch; -import com.quranapp.android.adapters.extended.PeaceBottomSheetMenuAdapter; -import com.quranapp.android.api.models.translation.TranslationBookInfoModel; -import com.quranapp.android.components.quran.subcomponents.Translation; -import com.quranapp.android.components.search.ChapterJumpModel; -import com.quranapp.android.components.search.JuzJumpModel; -import com.quranapp.android.components.search.SearchResultModelBase; -import com.quranapp.android.components.search.TafsirJumpModel; -import com.quranapp.android.components.search.VerseJumpModel; -import com.quranapp.android.components.search.VerseResultCountModel; -import com.quranapp.android.components.search.VerseResultModel; -import com.quranapp.android.databinding.LytReaderJuzSpinnerItemBinding; -import com.quranapp.android.databinding.LytSearchResultItemBinding; -import com.quranapp.android.repository.UserRepository; -import com.quranapp.android.frags.search.FragSearchResult; -import com.quranapp.android.interfaceUtils.Destroyable; -import com.quranapp.android.utils.extensions.ContextKt; -import com.quranapp.android.utils.extensions.LayoutParamsKt; -import com.quranapp.android.utils.extensions.ViewKt; -import com.quranapp.android.utils.extensions.ViewPaddingKt; -import com.quranapp.android.utils.reader.factory.ReaderFactory; -import com.quranapp.android.utils.univ.StringUtils; -import com.quranapp.android.vh.search.VHChapterJump; -import com.quranapp.android.vh.search.VHJuzJump; -import com.quranapp.android.vh.search.VHSearchResultBase; -import com.quranapp.android.vh.search.VHTafsirJump; -import com.quranapp.android.vh.search.VHVerseJump; -import com.quranapp.android.widgets.IconedTextView; -import com.quranapp.android.widgets.bottomSheet.PeaceBottomSheetMenu; -import com.quranapp.android.widgets.chapterCard.ChapterCard; -import com.quranapp.android.widgets.list.base.BaseListItem; - -import java.util.ArrayList; -import java.util.List; - -import kotlin.ranges.IntRange; - -public class ADPVerseResults extends RecyclerView.Adapter implements Destroyable { - private final FragmentManager mFm; - private final int mTransTextSize; - private final int mMoreBtnTextSize; - private final int mAuthorTextSize; - private final int mColorPrimary; - private final int mColorSecondary; - private final String mMoreTransText; - private final Typeface fontUrdu; - private final Typeface defaultTransFont; - private final Typeface mQueryHighlightTypeface; - private final LayoutInflater mInflater; - private ArrayList mResultModels = new ArrayList<>(); - private ActivitySearch mActivity; - - public ADPVerseResults(Context context, FragSearchResult fragSearchResult) { - mFm = fragSearchResult.getParentFragmentManager(); - mInflater = LayoutInflater.from(context); - mTransTextSize = ContextKt.getDimenPx(context, R.dimen.dmnCommonSize); - mAuthorTextSize = ContextKt.getDimenPx(context, R.dimen.dmnCommonSize2); - mMoreBtnTextSize = ContextKt.getDimenPx(context, R.dimen.dmnCommonSize2); - mColorSecondary = ContextKt.color(context, R.color.colorText3); - mColorPrimary = ContextKt.color(context, R.color.colorPrimary); - mMoreTransText = context.getString(R.string.strLabelMoreTranslations); - fontUrdu = ContextKt.getFont(context, R.font.noto_nastaliq_urdu_variable); - defaultTransFont = Typeface.DEFAULT; - mQueryHighlightTypeface = Typeface.create("sans-serif", Typeface.BOLD_ITALIC); - - setHasStableIds(true); - } - - @Override - public int getItemCount() { - return mResultModels.size(); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public int getItemViewType(int position) { - SearchResultModelBase modelBase = mResultModels.get(position); - if (modelBase instanceof VerseJumpModel) { - return VERSE_JUMPER; - } else if (modelBase instanceof ChapterJumpModel) { - return CHAPTER_JUMPER; - } else if (modelBase instanceof JuzJumpModel) { - return JUZ_JUMPER; - } else if (modelBase instanceof TafsirJumpModel) { - return TAFSIR_JUMPER; - } else if (modelBase instanceof VerseResultCountModel) { - return RESULT_COUNT; - } else if (modelBase instanceof VerseResultModel) { - return RESULT; - } - return -1; - } - - public void setResults(ArrayList results) { - mResultModels = results; - } - - @NonNull - @Override - public VHSearchResultBase onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - final VHSearchResultBase vh; - switch (viewType) { - case VERSE_JUMPER: - vh = new VHVerseJump(new AppCompatTextView(parent.getContext()), false); - break; - case CHAPTER_JUMPER: - vh = new VHChapterJump(new ChapterCard(parent.getContext()), false); - break; - case JUZ_JUMPER: - vh = new VHJuzJump(LytReaderJuzSpinnerItemBinding.inflate(mInflater, parent, false), false); - break; - case TAFSIR_JUMPER: - vh = new VHTafsirJump(new IconedTextView(parent.getContext()), false); - break; - case RESULT_COUNT: - vh = new VHVerseResultCount(makeResultCountView(parent.getContext())); - break; - case RESULT: - vh = new VHVerseResultVerse(LytSearchResultItemBinding.inflate(mInflater, parent, false)); - break; - default: - vh = new VHSearchResultBase(new View(parent.getContext())); - break; - } - - return vh; - } - - private View makeResultCountView(Context context) { - AppCompatTextView resultCountView = new AppCompatTextView(context); - - ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(MATCH_PARENT, WRAP_CONTENT); - params.topMargin = mActivity.dp2px(5); - resultCountView.setLayoutParams(params); - - ViewPaddingKt.updatePaddingHorizontal(resultCountView, mActivity.dp2px(15)); - ViewPaddingKt.updatePaddingVertical(resultCountView, mActivity.dp2px(2)); - - resultCountView.setTextColor(mColorSecondary); - resultCountView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mAuthorTextSize); - - return resultCountView; - } - - @Override - public void onBindViewHolder(@NonNull VHSearchResultBase holder, int position) { - holder.bind(mResultModels.get(position), position); - } - - public void setActivitySearch(ActivitySearch activitySearch) { - mActivity = activitySearch; - } - - @Override - public void destroy() { - mResultModels.clear(); - } - - static class VHVerseResultCount extends VHSearchResultBase { - public VHVerseResultCount(@NonNull View itemView) { - super(itemView); - } - - @SuppressWarnings("ConstantConditions") - @Override - public void bind(SearchResultModelBase baseModel, int pos) { - VerseResultCountModel model = (VerseResultCountModel) baseModel; - Context context = itemView.getContext(); - final String text; - - TranslationBookInfoModel translModel = model.getBookInfo(); - if (model.resultCount > 0) { - if (model.resultCount == 1) { - text = context.getString(R.string.strMsgSearchOneResultFoundIn, translModel.getBookName()); - } else { - text = context.getString(R.string.strMsgSearchMultResultsFound, translModel.getBookName(), - model.resultCount); - } - } else { - if (translModel != null) { - text = context.getString(R.string.strMsgSearchNoResultsFoundIn, translModel.getBookName()); - } else { - text = context.getString(R.string.strMsgSearchNoResultsFound); - } - } - ((TextView) itemView).setText(text); - } - } - - class VHVerseResultVerse extends VHSearchResultBase { - private final LytSearchResultItemBinding mBinding; - - public VHVerseResultVerse(@NonNull LytSearchResultItemBinding binding) { - super(binding.getRoot()); - mBinding = binding; - - View root = binding.getRoot(); - root.setElevation(Dimen.dp2px(root.getContext(), 4)); - } - - @Override - public void bind(SearchResultModelBase baseModel, int pos) { - if (mBinding == null) { - return; - } - - VerseResultModel model = (VerseResultModel) baseModel; - - mBinding.verseSerial.setText(model.verseSerial); - mBinding.menu.setOnClickListener(v -> openItemMenu(v.getContext(), model)); - - mBinding.transPlaceholder.removeAllViews(); - if (model.translationsView == null) { - model.translationsView = makeTranslations(model); - } else { - ViewKt.removeView(model.translationsView); - } - mBinding.transPlaceholder.addView(model.translationsView); - itemView.setOnClickListener(v -> openItem(itemView.getContext(), model)); - } - - private View makeTranslations(VerseResultModel model) { - LinearLayout container = new LinearLayout(itemView.getContext()); - container.setOrientation(LinearLayout.VERTICAL); - - int visibleTransCount = 1; - - List translations = model.translations; - for (int i = 0, l = Math.min(translations.size(), visibleTransCount); i < l; i++) { - makeSingleTranslation(container, model.translDisplayNames.get(i), translations.get(i), - model.startIndices.get(i), - model.endIndices.get(i)); - } - - int hideableTransCount = model.translations.size() - visibleTransCount; - if (hideableTransCount > 0) { - makeMoreTransNavigator(model, container, visibleTransCount, hideableTransCount, model); - } - - return container; - } - - private void makeSingleTranslation( - LinearLayout container, String translDisplayName, - Translation translation, int startIndex, int endIndex - ) { - Context context = itemView.getContext(); - - LinearLayout translRoot = new LinearLayout(context); - translRoot.setOrientation(LinearLayout.VERTICAL); - - AppCompatTextView transTextView = new AppCompatTextView(context); - ViewPaddingKt.updatePaddingHorizontal(transTextView, mActivity.dp2px(10)); - transTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTransTextSize); - transTextView.setText(prepareTransText(translation.getText(), startIndex, endIndex, translation.isUrdu())); - transTextView.setShadowLayer(mTransTextSize, 0f, 0f, Color.TRANSPARENT); - transTextView.setLayerType(View.LAYER_TYPE_SOFTWARE, transTextView.getPaint()); - translRoot.addView(transTextView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); - - AppCompatTextView authorTextView = new AppCompatTextView(context); - ViewPaddingKt.updatePaddingHorizontal(authorTextView, mActivity.dp2px(10)); - authorTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mAuthorTextSize); - authorTextView.setTextColor(mColorSecondary); - authorTextView.setShadowLayer(mAuthorTextSize, 0f, 0f, Color.TRANSPARENT); - authorTextView.setLayerType(View.LAYER_TYPE_SOFTWARE, authorTextView.getPaint()); - authorTextView.setText(translDisplayName); - - if (translation.isUrdu()) { - transTextView.setTypeface(fontUrdu); - authorTextView.setTypeface(fontUrdu); - transTextView.setIncludeFontPadding(false); - } else { - transTextView.setTypeface(defaultTransFont); - authorTextView.setTypeface(defaultTransFont); - transTextView.setIncludeFontPadding(true); - } - - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); - params.topMargin = mActivity.dp2px(5); - translRoot.addView(authorTextView, params); - - LinearLayout.LayoutParams rootParams = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); - LayoutParamsKt.updateMarginVertical(rootParams, mActivity.dp2px(5)); - - container.addView(translRoot, rootParams); - } - - private void makeMoreTransNavigator(VerseResultModel verseResultModel, LinearLayout container, int visibleTransCount, int hideableCount, VerseResultModel model) { - AppCompatTextView moreTransView = new AppCompatTextView(itemView.getContext()); - ViewPaddingKt.updatePaddings(moreTransView, mActivity.dp2px(5)); - - moreTransView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMoreBtnTextSize); - moreTransView.setText(String.format(mMoreTransText, hideableCount)); - moreTransView.setTextColor(mColorPrimary); - moreTransView.setBackgroundResource(R.drawable.dr_bg_hover_cornered); - moreTransView.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); - LayoutParamsKt.updateMarginHorizontal(params, mActivity.dp2px(10)); - LayoutParamsKt.updateMarginVertical(params, mActivity.dp2px(5)); - container.addView(moreTransView, params); - - moreTransView.setOnClickListener(v -> { - ViewKt.removeView(moreTransView); - List translations = model.translations; - for (int i = visibleTransCount, l = translations.size(); i < l; i++) { - Translation translation = translations.get(i); - makeSingleTranslation(container, model.translDisplayNames.get(i), - translation, model.startIndices.get(i), model.endIndices.get(i)); - } - }); - } - - - private CharSequence prepareTransText(String text, int startIndex, int endIndex, boolean isUrdu) { - int starEndDiff = endIndex - startIndex; - int textInitialLength = text.length(); - - int subStrStart = startIndex - 50; - if (subStrStart < 20) { - subStrStart = 0; - } - - int subStrEnd = Math.min(endIndex + 20, textInitialLength); - - if (!isUrdu) { - char c = text.charAt(subStrStart); - - while (subStrStart > 0 && (StringUtils.isRTL(c) || c == ' ')) { - c = text.charAt(--subStrStart); - } - } - - CharSequence substring = text.substring(subStrStart, subStrEnd); - - startIndex -= subStrStart; - endIndex = startIndex + starEndDiff; - CharSequence highlighted = highlightQuery(substring, startIndex, endIndex, isUrdu); - - String startEllipsis = subStrStart == 0 ? "" : "..."; - String endEllipsis = (subStrEnd == textInitialLength ? "" : "..."); - return TextUtils.concat(startEllipsis, highlighted, endEllipsis); - } - - - private CharSequence highlightQuery(CharSequence textPart, int start, int end, boolean isUrdu) { - SpannableString ss = new SpannableString(textPart); - ss.setSpan(new ForegroundColorSpan(mColorPrimary), start, end, SPAN_EXCLUSIVE_EXCLUSIVE); - ss.setSpan(new TypefaceSpan2(isUrdu ? fontUrdu : mQueryHighlightTypeface), start, end, - SPAN_EXCLUSIVE_EXCLUSIVE); - return ss; - } - - - private void openItemMenu(Context context, VerseResultModel model) { - UserRepository userRepository = mActivity.userRepository; - final boolean isBookmarked = userRepository.isBookmarkedBlocking(model.chapterNo, new IntRange(model.verseNo, model.verseNo)); - - PeaceBottomSheetMenu dialog = new PeaceBottomSheetMenu(); - dialog.getParams().setHeaderTitle(model.chapterName + " " + model.verseSerial); - - int[] icons = {R.drawable.dr_icon_open, isBookmarked ? R.drawable.ic_bookmark_added : R.drawable.ic_bookmark_add}; - int[] labels = {R.string.strLabelOpen, isBookmarked ? R.string.strLabelRemoveBookmark : R.string.strDescAddToBookmarks}; - int[] descs = {R.string.strLabelOpenInReader, 0}; - - PeaceBottomSheetMenuAdapter adapter = new PeaceBottomSheetMenuAdapter(context); - for (int i = 0, l = labels.length; i < l; i++) { - BaseListItem item = new BaseListItem(icons[i], context.getString(labels[i])); - if (descs[i] != 0) { - item.setMessage(context.getString(descs[i])); - } - item.setId(i); - adapter.addItem(item); - } - - dialog.setAdapter(adapter); - dialog.setOnItemClickListener((menu, item) -> { - switch (item.getId()) { - case 0: { - openItem(context, model); - } - break; - case 1: { - if (isBookmarked) { - userRepository.removeFromBookmarkBlocking(model.chapterNo, model.verseNo, model.verseNo); - } else { - userRepository.addToBookmarkBlocking(model.chapterNo, new IntRange(model.verseNo, model.verseNo), null); - } - break; - } - } - menu.dismiss(); - }); - - dialog.show(mFm); - } - - - private void openItem(Context context, VerseResultModel model) { - Intent intent = ReaderFactory.prepareSingleVerseIntent(model.chapterNo, model.verseNo); - intent.setClass(context, ActivityReader.class); - String[] requestedSlugs = model.translSlugs.toArray(new String[0]); - intent.putExtra(READER_KEY_TRANSL_SLUGS, requestedSlugs); - intent.putExtra(READER_KEY_SAVE_TRANSL_CHANGES, false); - context.startActivity(intent); - } - } -} diff --git a/app/src/main/java/com/quranapp/android/compose/components/common/AppBar.kt b/app/src/main/java/com/quranapp/android/compose/components/common/AppBar.kt index fb0db1202..a8bcd15c0 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/common/AppBar.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/common/AppBar.kt @@ -158,7 +158,7 @@ private fun AppBarSearchField( }, trailingIcon = if (query.isNotEmpty()) { { - SimpleTooltip(text = stringResource(R.string.strLabelClose)) { + SimpleTooltip(text = stringResource(R.string.clear)) { IconButton( onClick = { onQueryChange("") }, ) { diff --git a/app/src/main/java/com/quranapp/android/compose/components/common/MessageCard.kt b/app/src/main/java/com/quranapp/android/compose/components/common/MessageCard.kt index ecf8e0a64..a7cdd69c2 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/common/MessageCard.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/common/MessageCard.kt @@ -1,7 +1,6 @@ package com.quranapp.android.compose.components.common import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -36,50 +35,25 @@ import androidx.compose.ui.unit.dp import com.quranapp.android.R import com.quranapp.android.compose.utils.DataLoadError -/** - * Defines the visual style of a MessageCard - */ enum class MessageCardStyle { - /** For error states - uses error colors */ Error, - /** For informational messages - uses primary colors */ Info, - /** For warning messages - uses warning/tertiary colors */ Warning, - /** For success messages - uses success/primary colors */ Success, - /** Neutral style - uses surface variant colors */ Neutral } -/** - * Data class representing button configuration for MessageCard - */ data class MessageCardAction( - @StringRes val textRes: Int, + val textRes: Int, val onClick: () -> Unit, val isPrimary: Boolean = true ) -/** - * A reusable message card component for displaying errors, empty states, - * informational messages, and other status indicators. - * - * @param icon Resource ID for the icon to display - * @param message The message text to display - * @param modifier Modifier for the composable - * @param title Optional title text - * @param style The visual style of the card - * @param primaryAction Optional primary action button - * @param secondaryAction Optional secondary action button - * @param fillMaxSize Whether the card should fill the maximum available size - * @param showCard Whether to show as a card (with background) or just content - */ @Composable fun MessageCard( - @DrawableRes icon: Int, - message: String, modifier: Modifier = Modifier, + @DrawableRes icon: Int? = null, + message: String, title: String? = null, style: MessageCardStyle = MessageCardStyle.Neutral, primaryAction: MessageCardAction? = null, @@ -97,20 +71,21 @@ fun MessageCard( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - // Icon with background circle - Box( - modifier = Modifier - .size(80.dp) - .clip(CircleShape) - .background(colors.iconBackground), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(icon), - contentDescription = null, - tint = colors.iconTint, - modifier = Modifier.size(40.dp) - ) + if (icon != null) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(colors.iconBackground), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(icon), + contentDescription = null, + tint = colors.iconTint, + modifier = Modifier.size(40.dp) + ) + } } Spacer(modifier = Modifier.height(20.dp)) @@ -197,37 +172,6 @@ fun MessageCard( } } -/** - * Convenience overload using string resource IDs - */ -@Composable -fun MessageCard( - @DrawableRes icon: Int, - @StringRes messageRes: Int, - modifier: Modifier = Modifier, - @StringRes titleRes: Int? = null, - style: MessageCardStyle = MessageCardStyle.Neutral, - primaryAction: MessageCardAction? = null, - secondaryAction: MessageCardAction? = null, - fillMaxSize: Boolean = true, - showCard: Boolean = false -) { - MessageCard( - icon = icon, - message = stringResource(messageRes), - modifier = modifier, - title = titleRes?.let { stringResource(it) }, - style = style, - primaryAction = primaryAction, - secondaryAction = secondaryAction, - fillMaxSize = fillMaxSize, - showCard = showCard - ) -} - -/** - * Creates a MessageCard from a DataLoadError - */ @Composable fun ErrorMessageCard( error: DataLoadError, @@ -248,7 +192,7 @@ fun ErrorMessageCard( MessageCard( icon = icon, - messageRes = message, + message = stringResource(message), modifier = modifier, style = style, primaryAction = MessageCardAction( @@ -272,21 +216,25 @@ private fun getMessageCardColors(style: MessageCardStyle): MessageCardColors { iconTint = MaterialTheme.colorScheme.onError, buttonColor = MaterialTheme.colorScheme.error ) + MessageCardStyle.Info -> MessageCardColors( iconBackground = MaterialTheme.colorScheme.primaryContainer, iconTint = MaterialTheme.colorScheme.onPrimary, buttonColor = MaterialTheme.colorScheme.primary ) + MessageCardStyle.Warning -> MessageCardColors( iconBackground = MaterialTheme.colorScheme.tertiaryContainer, iconTint = MaterialTheme.colorScheme.onTertiaryContainer, buttonColor = MaterialTheme.colorScheme.tertiary ) + MessageCardStyle.Success -> MessageCardColors( iconBackground = MaterialTheme.colorScheme.primaryContainer, iconTint = MaterialTheme.colorScheme.onPrimaryContainer, buttonColor = MaterialTheme.colorScheme.primary ) + MessageCardStyle.Neutral -> MessageCardColors( iconBackground = MaterialTheme.colorScheme.surfaceVariant, iconTint = MaterialTheme.colorScheme.onSurfaceVariant, diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt index d1cf8ad9e..3e46706ea 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationReader.kt @@ -75,6 +75,7 @@ import com.quranapp.android.db.relations.SurahWithLocalizations import com.quranapp.android.utils.reader.LocalVerseActions import com.quranapp.android.utils.reader.TranslUtils import com.quranapp.android.utils.reader.TranslationPageBuilderParams +import com.quranapp.android.utils.univ.StringUtils import com.quranapp.android.viewModels.ReaderViewModel import kotlinx.coroutines.flow.distinctUntilChanged @@ -341,7 +342,7 @@ private fun TranslationModePage( val playerState = LocalRecitation.current val isPlaying = playerState.isAnyPlaying val playingVerse = playerState.playingVerse - val isRtl = TranslUtils.isRtl(item.translationSlug) + val isRtl = StringUtils.isRtlLanguage(item.translationSlug) val colors = colorScheme val sections = remember(item.sections, isPlaying, playingVerse, colors) { diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationText.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationText.kt index c8bb320f4..8a6d70d89 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationText.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/TranslationText.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp import com.quranapp.android.compose.theme.alpha import com.quranapp.android.utils.reader.TranslUtils +import com.quranapp.android.utils.univ.StringUtils @Composable @@ -38,7 +39,7 @@ fun TranslationText( modifier = Modifier.fillMaxWidth(), color = if (isDark) colorScheme.onBackground.alpha(0.8f) else colorScheme.onBackground, style = TextStyle( - textDirection = if (TranslUtils.isUrdu(it.first)) TextDirection.Rtl else TextDirection.Ltr, + textDirection = if (StringUtils.isRtlLanguage(it.first)) TextDirection.Rtl else TextDirection.Ltr, ) ) } diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt index 64886503a..5c64af8d9 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/VerseView.kt @@ -211,14 +211,14 @@ private fun VerseSerial(verse: VerseWithDetails) { modifier = Modifier .semantics { this.contentDescription = contentDescription } .clip(RoundedCornerShape(5.dp)) - .background(colorResource(R.color.colorBGLightGrey)) + .background(colorScheme.surface) .clickable( onClick = { context.copyToClipboard(verse.id.toString()) }, ) .padding(horizontal = 8.dp, vertical = 4.dp), - color = colorResource(R.color.colorIcon), + color = colorScheme.onSurface, style = typography.labelLarge, ) } diff --git a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt index 7c20ed88f..ceb20111c 100644 --- a/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt +++ b/app/src/main/java/com/quranapp/android/compose/components/reader/dialogs/QuickReference.kt @@ -1,6 +1,5 @@ package com.quranapp.android.compose.components.reader.dialogs -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,7 +7,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -38,6 +36,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import com.quranapp.android.R import com.quranapp.android.compose.components.dialogs.AlertDialog import com.quranapp.android.compose.components.dialogs.AlertDialogAction @@ -52,11 +51,11 @@ import com.quranapp.android.compose.theme.alpha import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.DatabaseProvider import com.quranapp.android.repository.UserRepository -import com.quranapp.android.utils.reader.FontResolver import com.quranapp.android.utils.reader.LocalVerseActions import com.quranapp.android.utils.reader.ReaderItemsBuilder import com.quranapp.android.utils.reader.TextBuilderParams import com.quranapp.android.utils.univ.RegexPattern +import com.quranapp.android.viewModels.ReaderProviderViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -196,10 +195,7 @@ private fun QuickReferenceContent( onClose: () -> Unit, ) { val context = LocalContext.current - - val repository = remember(context) { DatabaseProvider.getQuranRepository(context) } - val bookmarksRepo = remember(context) { DatabaseProvider.getUserRepository(context) } - val fontResolver = remember(context) { FontResolver.getInstance(context) } + val viewModel = viewModel() val colors = MaterialTheme.colorScheme val type = MaterialTheme.typography @@ -212,7 +208,7 @@ private fun QuickReferenceContent( var prepared by remember { mutableStateOf(null) } var isLoading by remember { mutableStateOf(true) } val isBookmarked by if (verseRange != null) { - bookmarksRepo + viewModel.userRepository .isBookmarkedFlow(data.chapterNo, verseRange) .collectAsStateWithLifecycle(false) } else { @@ -225,7 +221,7 @@ private fun QuickReferenceContent( withContext(Dispatchers.IO) { val params = TextBuilderParams( context = context, - fontResolver = fontResolver, + fontResolver = viewModel.fontResolver, verseActions = verseActions, colors = colors, type = type, @@ -233,11 +229,12 @@ private fun QuickReferenceContent( script = ReaderPreferences.getQuranScript(), arabicSizeMultiplier = ReaderPreferences.getArabicTextSizeMultiplier(), translationSizeMultiplier = ReaderPreferences.getTranslationTextSizeMultiplier(), - slugs = ReaderPreferences.getTranslations(), + slugs = data.slugs.takeIf { it.isNotEmpty() } + ?: ReaderPreferences.getTranslations(), ) prepared = ReaderItemsBuilder.buildQuickReferenceItems( - context, params, repository, data.chapterNo, verseNos + context, params, viewModel.repository, data.chapterNo, verseNos ) } isLoading = false @@ -265,9 +262,6 @@ private fun QuickReferenceContent( onClose = onClose, ) - if (data.slugs.isEmpty()) { - TranslationWarning() - } if (isLoading) { Box( @@ -292,7 +286,7 @@ private fun QuickReferenceContent( key = { _, item -> item.key } ) { index, verseUi -> VerseViewWrapped( - bookmarksRepo, + viewModel.userRepository, verseUi = verseUi, showDivier = index < verseRows.lastIndex, ) @@ -382,29 +376,6 @@ private fun QuickReferenceHeader( } } -@Composable -private fun TranslationWarning() { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Icon( - painter = painterResource(R.drawable.dr_icon_info), - contentDescription = null, - modifier = Modifier.size(20.dp), - tint = colorScheme.error, - ) - Text( - text = stringResource(R.string.strMsgTranslNoneSelected), - style = typography.bodySmall, - color = colorScheme.error, - ) - } -} - @Composable private fun ChapterOnlyDialog( chapterNo: Int, diff --git a/app/src/main/java/com/quranapp/android/compose/components/search/QuickLinks.kt b/app/src/main/java/com/quranapp/android/compose/components/search/QuickLinks.kt new file mode 100644 index 000000000..df8424172 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/search/QuickLinks.kt @@ -0,0 +1,157 @@ +package com.quranapp.android.compose.components.search + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.quranapp.android.R +import com.quranapp.android.compose.components.reader.dialogs.QuickReference +import com.quranapp.android.compose.components.reader.dialogs.QuickReferenceData +import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.search.QuickLinkItem +import com.quranapp.android.search.stableKey +import com.quranapp.android.utils.reader.factory.ReaderFactory +import com.quranapp.android.viewModels.QuranSearchViewModel + +@Composable +fun QuickLinks(viewModel: QuranSearchViewModel) { + val quickLinks by viewModel.quickLinks.collectAsState() + if (quickLinks.isEmpty()) return + + var quickRefData by remember { mutableStateOf(null) } + val context = LocalContext.current + + Surface( + color = colorScheme.surfaceContainer, + modifier = Modifier + .fillMaxWidth(), + ) { + FlowRow( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + quickLinks.forEach { item -> + key(item.stableKey()) { + CompactQuickLinkCard( + item = item, + onClick = { + when (item) { + is QuickLinkItem.Verse -> { + quickRefData = QuickReferenceData( + chapterNo = item.chapterNo, + verses = item.verseNo.toString(), + slugs = emptySet() + ) + } + + is QuickLinkItem.Tafsir -> { + viewModel.recordCurrentSearchQuery() + ReaderFactory.startTafsir(context, item.chapterNo, item.verseNo) + } + + is QuickLinkItem.Chapter -> { + viewModel.recordCurrentSearchQuery() + ReaderFactory.startChapter(context, item.surah.surah.surahNo) + } + + is QuickLinkItem.Juz -> { + viewModel.recordCurrentSearchQuery() + ReaderFactory.startJuz(context, item.juzNo) + } + + is QuickLinkItem.Hizb -> { + viewModel.recordCurrentSearchQuery() + ReaderFactory.startHizb(context, item.hizbNo) + } + } + }, + ) + } + } + } + } + + QuickReference( + data = quickRefData, + onOpenInReader = { chapterNo, range -> + quickRefData = null + viewModel.recordCurrentSearchQuery() + ReaderFactory.startVerseRange(context, chapterNo, range.first, range.last) + }, + onClose = { quickRefData = null }, + ) +} + +@Composable +private fun CompactQuickLinkCard( + item: QuickLinkItem, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val label = when (item) { + is QuickLinkItem.Chapter -> { + val name = stringResource(R.string.strLabelSurah, item.surah.getCurrentName()) + listOf("${item.surah.surah.surahNo}.", name).joinToString(" ") + } + + is QuickLinkItem.Verse -> + stringResource(R.string.strLabelVerseWithChapNo, item.chapterNo, item.verseNo) + + is QuickLinkItem.Tafsir -> + stringResource( + R.string.tafsirForVerse, + item.chapterNo, + item.verseNo, + ) + + is QuickLinkItem.Juz -> + stringResource(R.string.strLabelJuzNo, item.juzNo) + + is QuickLinkItem.Hizb -> + stringResource(R.string.labelHizbNo, item.hizbNo) + } + + val shape = RoundedCornerShape(8.dp) + + Surface( + modifier = modifier + .clip(shape) + .clickable(onClick = onClick), + shape = shape, + color = colorScheme.surfaceContainerHigh, + border = BorderStroke( + width = 1.dp, + color = colorScheme.outlineVariant.alpha(0.45f), + ), + ) { + Text( + text = label, + style = MaterialTheme.typography.labelMedium, + color = colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), + ) + } +} diff --git a/app/src/main/java/com/quranapp/android/compose/components/search/SearchHistorySection.kt b/app/src/main/java/com/quranapp/android/compose/components/search/SearchHistorySection.kt new file mode 100644 index 000000000..062991143 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/search/SearchHistorySection.kt @@ -0,0 +1,332 @@ +package com.quranapp.android.compose.components.search + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.quranapp.android.compose.components.dialogs.AlertDialog +import com.quranapp.android.compose.components.dialogs.AlertDialogAction +import com.quranapp.android.compose.components.dialogs.AlertDialogActionStyle +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.quranapp.android.R +import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.db.search.SearchHistoryEntry +import com.quranapp.android.viewModels.QuranSearchViewModel + +@Composable +fun SearchHistorySuggestionStrip( + suggestions: List, + onSelect: (String) -> Unit, +) { + if (suggestions.isEmpty()) return + + Surface( + color = colorScheme.surfaceContainer, + modifier = Modifier.fillMaxWidth(), + ) { + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(start = 16.dp, end = 16.dp, bottom = 12.dp), + ) { + items( + items = suggestions, + key = { it.id }, + ) { entry -> + SearchHistoryQueryChip( + text = entry.text, + onClick = { onSelect(entry.text) }, + ) + } + } + } +} + +@Composable +private fun SearchHistoryQueryChip( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val shape = RoundedCornerShape(8.dp) + Surface( + modifier = modifier + .clip(shape) + .clickable(onClick = onClick), + shape = shape, + color = colorScheme.surfaceContainerHigh, + border = BorderStroke( + width = 1.dp, + color = colorScheme.outlineVariant.alpha(0.45f), + ), + ) { + Text( + text = text, + style = MaterialTheme.typography.labelMedium, + color = colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), + ) + } +} + +@Composable +fun SearchEmptyScrollContent( + viewModel: QuranSearchViewModel, + modifier: Modifier = Modifier, +) { + val history by viewModel.searchHistory.collectAsState() + var showClearAllDialog by remember { mutableStateOf(false) } + + ClearSearchHistoryDialog( + isOpen = showClearAllDialog, + onDismiss = { showClearAllDialog = false }, + onConfirm = { + viewModel.clearSearchHistory() + }, + ) + + LazyColumn( + modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = 120.dp), + ) { + item { + SearchTipsCard(viewModel) + } + + if (history.isNotEmpty()) { + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.titleRecentSearches), + style = MaterialTheme.typography.titleSmall, + color = colorScheme.onBackground.alpha(0.75f), + ) + TextButton( + onClick = { showClearAllDialog = true }, + ) { + Text(stringResource(R.string.msgClearSearchHistory)) + } + } + } + + items( + items = history, + key = { it.id }, + ) { entry -> + SearchHistoryRow( + entry = entry, + onClick = { + viewModel.recordSearchQuery(entry.text) + viewModel.onQueryChange(entry.text) + }, + onRemove = { viewModel.removeSearchHistory(entry.id) }, + ) + } + } + } +} + +@Composable +private fun ClearSearchHistoryDialog( + isOpen: Boolean, + onDismiss: () -> Unit, + onConfirm: () -> Unit, +) { + AlertDialog( + isOpen = isOpen, + onClose = onDismiss, + title = stringResource(R.string.msgClearSearchHistory), + actions = listOf( + AlertDialogAction( + text = stringResource(R.string.strLabelCancel), + ), + AlertDialogAction( + text = stringResource(R.string.strLabelRemoveAll), + style = AlertDialogActionStyle.Danger, + onClick = onConfirm, + ), + ), + ) { + Text( + text = stringResource(R.string.strMsgSearchHistoryDeleteAll), + style = MaterialTheme.typography.bodyMedium, + color = colorScheme.onSurface, + ) + } +} + +@Composable +private fun SearchHistoryRow( + entry: SearchHistoryEntry, + onClick: () -> Unit, + onRemove: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 24.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(R.drawable.dr_icon_history), + contentDescription = null, + modifier = Modifier + .padding(end = 12.dp) + .size(22.dp), + tint = colorScheme.primary, + ) + + Text( + text = entry.text, + style = MaterialTheme.typography.bodyMedium, + color = colorScheme.onBackground, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + + IconButton(onClick = onRemove) { + Icon( + painter = painterResource(R.drawable.dr_icon_close), + contentDescription = stringResource(R.string.strLabelClose), + modifier = Modifier.size(20.dp), + ) + } + } +} + +@Composable +fun SearchTipsCard( + viewModel: QuranSearchViewModel, + modifier: Modifier = Modifier, +) { + Card( + modifier = modifier + .fillMaxWidth() + .padding(16.dp), + shape = shapes.medium, + colors = CardDefaults.cardColors( + containerColor = colorScheme.surfaceVariant.copy(alpha = 0.45f) + ) + ) { + Column { + Column( + modifier = Modifier.padding(16.dp), + ) { + Text( + text = stringResource(R.string.searchTipsTitle), + style = typography.titleSmall, + color = colorScheme.onBackground.alpha(0.75f) + ) + } + + HorizontalDivider( + color = colorScheme.outlineVariant.copy(alpha = 0.6f), + modifier = Modifier.padding(horizontal = 12.dp) + ) + + Column( + modifier = Modifier.padding(vertical = 16.dp), + ) { + TipRow("2:255", stringResource(R.string.searchTipDirectVerse)) { example -> + viewModel.recordSearchQuery(example) + viewModel.onQueryChange(example) + } + TipRow("baqarah", stringResource(R.string.searchTipChapter)) { example -> + viewModel.recordSearchQuery(example) + viewModel.onQueryChange(example) + } + TipRow("30", stringResource(R.string.searchTipDirectJuz)) { example -> + viewModel.recordSearchQuery(example) + viewModel.onQueryChange(example) + } + TipRow("mercy", stringResource(R.string.searchTipTranslation)) { example -> + viewModel.recordSearchQuery(example) + viewModel.onQueryChange(example) + } + TipRow("الرحيم", stringResource(R.string.searchTipArabic)) { example -> + viewModel.recordSearchQuery(example) + viewModel.onQueryChange(example) + } + } + } + } +} + +@Composable +private fun TipRow(example: String, description: String, onClick: (String) -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onClick(example) + } + .padding(vertical = 7.dp, horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier.width(82.dp) + ) { + Text( + text = example, + color = MaterialTheme.colorScheme.primary, + style = typography.labelMedium, + modifier = Modifier + .background(colorScheme.background, shapes.small) + .padding(horizontal = 10.dp, vertical = 8.dp) + ) + } + + Text( + text = description, + style = MaterialTheme.typography.bodyMedium, + color = colorScheme.onBackground.alpha(0.75f) + ) + } +} diff --git a/app/src/main/java/com/quranapp/android/compose/components/search/SurahSearchResults.kt b/app/src/main/java/com/quranapp/android/compose/components/search/SurahSearchResults.kt new file mode 100644 index 000000000..f37d5f2ff --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/search/SurahSearchResults.kt @@ -0,0 +1,65 @@ +package com.quranapp.android.compose.components.search + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.quranapp.android.R +import com.quranapp.android.compose.components.common.Loader +import com.quranapp.android.compose.components.reader.navigator.ChapterCard +import com.quranapp.android.db.relations.SurahWithLocalizations +import com.quranapp.android.utils.reader.factory.ReaderFactory +import com.quranapp.android.viewModels.QuranSearchViewModel + +@Composable +fun SurahSearchResults( + viewModel: QuranSearchViewModel, + surahResults: List?, +) { + if (surahResults == null) { + return Loader(true) + } + + if (surahResults.isEmpty()) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + stringResource(R.string.noResults), + style = typography.labelLarge, + ) + } + return + } + + val context = LocalContext.current + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 24.dp, bottom = 120.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + items(surahResults) { + ChapterCard( + surah = it, + onClick = { + viewModel.recordCurrentSearchQuery() + ReaderFactory.startChapter(context, it.surah.surahNo) + }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/components/search/TextSearchResults.kt b/app/src/main/java/com/quranapp/android/compose/components/search/TextSearchResults.kt new file mode 100644 index 000000000..f229cfe66 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/components/search/TextSearchResults.kt @@ -0,0 +1,205 @@ +package com.quranapp.android.compose.components.search + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextDirection +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems +import com.quranapp.android.R +import com.quranapp.android.compose.components.common.Loader +import com.quranapp.android.compose.components.reader.dialogs.QuickReference +import com.quranapp.android.compose.components.reader.dialogs.QuickReferenceData +import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.search.SearchResult +import com.quranapp.android.search.SearchResultMatch +import com.quranapp.android.utils.extensions.copyToClipboard +import com.quranapp.android.utils.reader.factory.ReaderFactory +import com.quranapp.android.viewModels.QuranSearchViewModel + +@Composable +fun TextSearchResults(viewModel: QuranSearchViewModel, results: LazyPagingItems) { + if (results.loadState.refresh is LoadState.Loading) { + return Loader(true) + } + + if (results.itemCount == 0) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + stringResource(R.string.noResults), + style = typography.labelLarge, + ) + } + + return + } + + val context = LocalContext.current + var quickRefData by remember { mutableStateOf(null) } + + LazyColumn( + contentPadding = PaddingValues(start = 12.dp, end = 12.dp, top = 20.dp, bottom = 120.dp), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + items( + results.itemCount, + key = { + val result = results[it] + if (result != null) { + "${result.chapterNo}:${result.verseNo}-${result.matches.size}" + } else { + it + } + } + ) { + val result = results[it] ?: return@items + TextSearchResultCard(result) { + quickRefData = QuickReferenceData( + chapterNo = result.chapterNo, + verses = result.verseNo.toString(), + slugs = result.matches + .filterIsInstance() + .map { it.slug } + .toSet() + ) + } + } + } + + QuickReference( + data = quickRefData, + onOpenInReader = { chapterNo, range -> + quickRefData = null + viewModel.recordCurrentSearchQuery() + ReaderFactory.startVerseRange(context, chapterNo, range.first, range.last) + }, + onClose = { quickRefData = null }, + ) +} + +@Composable +private fun TextSearchResultCard(result: SearchResult, onClick: (SearchResult) -> Unit) { + val context = LocalContext.current + + Surface( + modifier = Modifier + .fillMaxWidth(), + tonalElevation = 1.dp, + shape = shapes.small, + border = BorderStroke(1.dp, colorScheme.outlineVariant.alpha(0.75f)), + onClick = { + onClick(result) + } + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + + Row( + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = stringResource( + R.string.strLabelVerseSerial, + result.chapterNo, + result.verseNo + ), + modifier = Modifier + .clip(RoundedCornerShape(5.dp)) + .background(colorScheme.background) + .clickable( + onClick = { + context.copyToClipboard("${result.chapterNo}:${result.verseNo}") + }, + ) + .padding(horizontal = 8.dp, vertical = 4.dp), + color = colorScheme.onBackground, + style = typography.labelLarge, + ) + } + + result.matches.forEachIndexed { index, match -> + if (index > 0) { + HorizontalDivider( + color = colorScheme.outlineVariant.copy(alpha = 0.5f), + modifier = Modifier.padding(vertical = 8.dp) + ) + } + + when (match) { + is SearchResultMatch.TranslationMatch -> { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Text( + text = match.displayName, + style = typography.labelMedium, + color = colorScheme.primary, + ) + + Text( + text = match.preview, + style = typography.bodyMedium, + color = colorScheme.onSurface, + maxLines = 4, + overflow = TextOverflow.Ellipsis, + ) + } + } + + is SearchResultMatch.QuranTextMatch -> { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + Text( + modifier = Modifier.fillMaxWidth(), + text = match.preview, + style = typography.bodyMedium.copy( + textDirection = TextDirection.Rtl + ), + color = colorScheme.onSurface, + maxLines = 4, + overflow = TextOverflow.Ellipsis, + ) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/compose/screens/BookmarksScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/BookmarksScreen.kt index a5264a4b8..2da5993a3 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/BookmarksScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/BookmarksScreen.kt @@ -153,7 +153,7 @@ fun BookmarksScreen(vm: BookmarksViewModel = viewModel()) { uiState.bookmarks.isEmpty() -> { MessageCard( icon = R.drawable.ic_bookmark, - messageRes = R.string.strMsgBookmarkNoItems, + message = stringResource(R.string.strMsgBookmarkNoItems), modifier = Modifier.padding(paddingValues), ) } diff --git a/app/src/main/java/com/quranapp/android/compose/screens/ReadHistoryScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/ReadHistoryScreen.kt index b1e6af582..56959ecee 100644 --- a/app/src/main/java/com/quranapp/android/compose/screens/ReadHistoryScreen.kt +++ b/app/src/main/java/com/quranapp/android/compose/screens/ReadHistoryScreen.kt @@ -102,7 +102,7 @@ fun ReadHistoryScreen(vm: ReadHistoryViewModel = viewModel()) { allHistories.itemCount == 0 -> { MessageCard( icon = R.drawable.dr_icon_history, - messageRes = R.string.strMsgReadHistoryNoItems, + message = stringResource(R.string.strMsgReadHistoryNoItems), modifier = Modifier.padding(paddingValues), ) } diff --git a/app/src/main/java/com/quranapp/android/compose/screens/search/SearchScreen.kt b/app/src/main/java/com/quranapp/android/compose/screens/search/SearchScreen.kt new file mode 100644 index 000000000..fdb03b643 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/compose/screens/search/SearchScreen.kt @@ -0,0 +1,313 @@ +package com.quranapp.android.compose.screens.search + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.Badge +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SecondaryTabRow +import androidx.compose.material3.Surface +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.LoadState +import androidx.paging.compose.collectAsLazyPagingItems +import com.quranapp.android.R +import com.quranapp.android.compose.components.common.AppBar +import com.quranapp.android.compose.components.dialogs.SimpleTooltip +import com.quranapp.android.compose.components.search.QuickLinks +import com.quranapp.android.compose.components.search.SearchEmptyScrollContent +import com.quranapp.android.compose.components.search.SearchHistorySuggestionStrip +import com.quranapp.android.compose.components.search.SurahSearchResults +import com.quranapp.android.compose.components.search.TextSearchResults +import com.quranapp.android.compose.theme.alpha +import com.quranapp.android.viewModels.QuranSearchViewModel +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch + +@Composable +fun SearchScreen( + supportsVoiceSearch: Boolean, + voiceSearchFlow: SharedFlow, + onVoiceSearchClick: () -> Unit, +) { + val viewModel = viewModel() + + LaunchedEffect(Unit) { + viewModel.refreshSearchHistory() + } + + LaunchedEffect(Unit) { + voiceSearchFlow.collect { viewModel.onQueryChange(it) } + } + + Scaffold( + containerColor = colorScheme.background, + topBar = { + AppBar( + title = stringResource(R.string.titleGlobalSearch), + actions = { + if (supportsVoiceSearch) { + IconButton(onClick = onVoiceSearchClick) { + Icon( + painterResource(R.drawable.dr_icon_mic), + contentDescription = stringResource(R.string.strHintSearch), + ) + } + } + }, + ) + }, + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding), + ) { + SearchBox(viewModel) + + val query by viewModel.searchQuery.collectAsState() + val quickLinks by viewModel.quickLinks.collectAsState() + val searchHistory by viewModel.searchHistory.collectAsState() + + val historySuggestions = remember(query, quickLinks, searchHistory) { + viewModel.historySuggestionsForDisplay(query, quickLinks) + } + + SearchHistorySuggestionStrip( + suggestions = historySuggestions, + onSelect = { text -> + viewModel.recordSearchQuery(text) + viewModel.onQueryChange(text) + }, + ) + + QuickLinks(viewModel) + + TabbedResults(viewModel) + } + } +} + +@Composable +private fun SearchBox(viewModel: QuranSearchViewModel) { + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + + var hasFocus by remember { mutableStateOf(false) } + val bgColor = colorScheme.background + + val query by viewModel.searchQuery.collectAsState() + + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .background( + color = colorScheme.surfaceContainer, + ) + .padding( + start = 16.dp, end = 16.dp, bottom = 16.dp + ) + .border( + width = 1.dp, + color = if (hasFocus) colorScheme.outline.alpha(0.75f) else colorScheme.outline.alpha( + 0.4f + ), + shape = shapes.medium + ) + .focusRequester(focusRequester) + .focusable() + .onFocusChanged { + hasFocus = it.hasFocus + + if (it.isFocused) { + keyboardController?.show() + } + }, + colors = OutlinedTextFieldDefaults.colors( + unfocusedContainerColor = bgColor, + focusedContainerColor = bgColor, + unfocusedBorderColor = Color.Transparent, + focusedBorderColor = Color.Transparent + ), + placeholder = { + Text( + stringResource(R.string.strHintSearchQuran), + style = MaterialTheme.typography.titleSmall, + color = colorScheme.onBackground.alpha(0.6f) + ) + }, + trailingIcon = if (query.isNotEmpty()) { + { + SimpleTooltip(text = stringResource(R.string.clear)) { + IconButton( + onClick = { viewModel.onQueryChange("") }, + ) { + Icon( + painter = painterResource(R.drawable.dr_icon_close), + contentDescription = stringResource(R.string.strLabelClose), + modifier = Modifier.size(20.dp), + ) + } + } + } + } else null, + value = query, + onValueChange = viewModel::onQueryChange, + textStyle = MaterialTheme.typography.titleSmall, + shape = MaterialTheme.shapes.medium, + singleLine = true, + ) +} + +private enum class SearchResultTab { + results, chapters, +} + +@Composable +private fun ColumnScope.TabbedResults(viewModel: QuranSearchViewModel) { + val query by viewModel.searchQuery.collectAsState() + + val scope = rememberCoroutineScope() + + val tabs = remember { + listOf( + SearchResultTab.results, + SearchResultTab.chapters, + ) + } + + val pagerState = rememberPagerState( + initialPage = 0, pageCount = { tabs.size }) + + if (query.isBlank()) { + SearchEmptyScrollContent(viewModel, Modifier.weight(1f)) + return + } + + val searchResults = viewModel.searchResults.collectAsLazyPagingItems() + val surahResults by viewModel.surahResults.collectAsState() + + SearchResultTabs( + tabs, + counts = mapOf( + SearchResultTab.results to if (searchResults.loadState.refresh is LoadState.Loading) null else searchResults.itemCount, + SearchResultTab.chapters to surahResults?.size, + ), + selectedTabIndex = pagerState.currentPage, + ) { + scope.launch { + pagerState.animateScrollToPage(it) + } + } + + HorizontalPager( + state = pagerState, + modifier = Modifier + .weight(1f) + .fillMaxSize(), + ) { page -> + when (page) { + 0 -> TextSearchResults(viewModel, searchResults) + 1 -> SurahSearchResults(viewModel, surahResults) + } + } +} + + +@Composable +private fun SearchResultTabs( + tabs: List, + counts: Map, + selectedTabIndex: Int, + onTabSelected: (Int) -> Unit, +) { + Surface( + modifier = Modifier + .fillMaxWidth() + .background(colorScheme.surfaceContainer), + shadowElevation = 2.dp, + ) { + SecondaryTabRow( + selectedTabIndex = selectedTabIndex, containerColor = colorScheme.surfaceContainer + ) { + tabs.forEachIndexed { index, tab -> + val isSelected = selectedTabIndex == index + + Tab( + selected = isSelected, + selectedContentColor = colorScheme.primary, + unselectedContentColor = colorScheme.onSurfaceVariant, + onClick = { onTabSelected(index) }, + text = { + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource( + when (tab) { + SearchResultTab.results -> R.string.results + SearchResultTab.chapters -> R.string.strTitleReaderChapters + } + ), + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + val count = counts[tab] + + if (count != null) { + Badge( + containerColor = Color.Gray.alpha(0.5f), + contentColor = colorScheme.onBackground + ) { + Text("$count") + } + } + } + }) + } + } + } +} diff --git a/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt b/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt index c501fa57c..329b20ace 100644 --- a/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt +++ b/app/src/main/java/com/quranapp/android/db/DatabaseProvider.kt @@ -2,6 +2,7 @@ package com.quranapp.android.db import android.content.Context import androidx.room.Room +import com.quranapp.android.db.searchindex.SearchIndexDatabase import com.quranapp.android.repository.QuranRepository import com.quranapp.android.repository.UserRepository @@ -16,6 +17,9 @@ object DatabaseProvider { @Volatile private var quranRepository: QuranRepository? = null + @Volatile + private var searchIndexDatabase: SearchIndexDatabase? = null + fun getUserDatabase(context: Context): UserDatabase { return userDatabase ?: synchronized(this) { userDatabase ?: Room.databaseBuilder( @@ -68,4 +72,17 @@ object DatabaseProvider { ).also { quranRepository = it } } } + + fun getSearchIndexDatabase(context: Context): SearchIndexDatabase { + return searchIndexDatabase ?: synchronized(this) { + searchIndexDatabase ?: Room.databaseBuilder( + context.applicationContext, + SearchIndexDatabase::class.java, + "SearchIndex.db", + ) + .fallbackToDestructiveMigration(true) + .build() + .also { searchIndexDatabase = it } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/db/QuranDatabase.kt b/app/src/main/java/com/quranapp/android/db/QuranDatabase.kt index 7cd7c25e6..bd44e2a89 100644 --- a/app/src/main/java/com/quranapp/android/db/QuranDatabase.kt +++ b/app/src/main/java/com/quranapp/android/db/QuranDatabase.kt @@ -4,12 +4,14 @@ import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters import com.quranapp.android.db.converters.QuranConverters +import com.quranapp.android.db.dao.ArabicSearchDao import com.quranapp.android.db.dao.AyahDao import com.quranapp.android.db.dao.AyahWordDao import com.quranapp.android.db.dao.MushafDao import com.quranapp.android.db.dao.NavigationDao import com.quranapp.android.db.dao.SurahDao import com.quranapp.android.db.dao.SurahSearchDao +import com.quranapp.android.db.entities.quran.ArabicSearchFtsEntity import com.quranapp.android.db.entities.quran.AyahEntity import com.quranapp.android.db.entities.quran.AyahWordEntity import com.quranapp.android.db.entities.quran.MushafEntity @@ -32,13 +34,15 @@ import com.quranapp.android.db.entities.quran.SurahSearchAliasEntity AyahWordEntity::class, NavigationRangeEntity::class, MushafEntity::class, - MushafMapEntity::class + MushafMapEntity::class, + ArabicSearchFtsEntity::class ], - version = 1, + version = 2, exportSchema = false ) @TypeConverters(QuranConverters::class) abstract class QuranDatabase : RoomDatabase() { + abstract fun arabicSearchDao(): ArabicSearchDao abstract fun surahDao(): SurahDao abstract fun surahSearchDao(): SurahSearchDao abstract fun ayahDao(): AyahDao diff --git a/app/src/main/java/com/quranapp/android/db/dao/ArabicSearchDao.kt b/app/src/main/java/com/quranapp/android/db/dao/ArabicSearchDao.kt new file mode 100644 index 000000000..593520560 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/dao/ArabicSearchDao.kt @@ -0,0 +1,22 @@ +package com.quranapp.android.db.dao + +import androidx.room.Dao +import androidx.room.Query +import com.quranapp.android.db.entities.quran.ArabicSearchFtsEntity + +@Dao +interface ArabicSearchDao { + @Query( + """ + SELECT ayah_id, text FROM arabic_search + WHERE arabic_search MATCH :ftsQuery + ORDER BY ayah_id + LIMIT :limit OFFSET :offset + """ + ) + suspend fun pageMatchedAyahs( + ftsQuery: String, + limit: Int, + offset: Int, + ): List +} diff --git a/app/src/main/java/com/quranapp/android/db/entities/quran/ArabicSearchFtsEntity.kt b/app/src/main/java/com/quranapp/android/db/entities/quran/ArabicSearchFtsEntity.kt new file mode 100644 index 000000000..d4d37af11 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/entities/quran/ArabicSearchFtsEntity.kt @@ -0,0 +1,17 @@ +package com.quranapp.android.db.entities.quran + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Fts4 + +@Fts4( + tokenizer = "unicode61" +) +@Entity(tableName = "arabic_search") +data class ArabicSearchFtsEntity( + @ColumnInfo(name = "ayah_id") + val ayahId: Int, + + @ColumnInfo(name = "text") + val text: String +) \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/db/relations/SearchResultRow.kt b/app/src/main/java/com/quranapp/android/db/relations/SearchResultRow.kt new file mode 100644 index 000000000..cf5696686 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/relations/SearchResultRow.kt @@ -0,0 +1,13 @@ +package com.quranapp.android.db.relations + +data class SearchResultVerseRow( + val surahNo: Int, + val ayahNo: Int, +) + +data class SearchResultSearchRow( + val slug: String, + val surahNo: Int, + val ayahNo: Int, + val text: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/db/search/SearchHistoryDBHelper.java b/app/src/main/java/com/quranapp/android/db/search/SearchHistoryDBHelper.java index 2e745c335..9244667b9 100644 --- a/app/src/main/java/com/quranapp/android/db/search/SearchHistoryDBHelper.java +++ b/app/src/main/java/com/quranapp/android/db/search/SearchHistoryDBHelper.java @@ -53,7 +53,17 @@ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void addToHistory(String text, Runnable runOnSucceed) { - if (updateHistory(text)) { + if (text == null) { + return; + } + + final String trimmed = text.trim(); + + if (trimmed.isEmpty()) { + return; + } + + if (updateHistory(trimmed)) { if (runOnSucceed != null) { runOnSucceed.run(); } @@ -63,7 +73,7 @@ public void addToHistory(String text, Runnable runOnSucceed) { SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(COL_TEXT, text); + values.put(COL_TEXT, trimmed); values.put(COL_DATE, DateUtils.getDateTimeNow()); long rowId = db.insert(TABLE_NAME, null, values); @@ -94,10 +104,14 @@ public void removeFromHistory(int historyId, Runnable runOnSucceed) { } public boolean isInHistory(String text) { + if (text == null) { + return false; + } + SQLiteDatabase db = getReadableDatabase(); - String selection = COL_TEXT + "=?"; - String[] selectionArgs = {text.toLowerCase()}; + String selection = "LOWER(" + COL_TEXT + ") = LOWER(?)"; + String[] selectionArgs = {text.trim()}; long count = DatabaseUtils.queryNumEntries(db, TABLE_NAME, selection, selectionArgs); @@ -107,8 +121,8 @@ public boolean isInHistory(String text) { public boolean updateHistory(String text) { SQLiteDatabase db = getWritableDatabase(); - String whereClause = COL_TEXT + "=?"; - String[] whereArgs = {text.toLowerCase()}; + String whereClause = "LOWER(" + COL_TEXT + ") = LOWER(?)"; + String[] whereArgs = {text.trim()}; ContentValues values = new ContentValues(); values.put(COL_DATE, DateUtils.getDateTimeNow()); diff --git a/app/src/main/java/com/quranapp/android/db/search/SearchHistoryStore.kt b/app/src/main/java/com/quranapp/android/db/search/SearchHistoryStore.kt new file mode 100644 index 000000000..ec1c1bbd3 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/search/SearchHistoryStore.kt @@ -0,0 +1,39 @@ +package com.quranapp.android.db.search + +import android.content.Context +import com.quranapp.android.components.search.SearchHistoryModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +data class SearchHistoryEntry( + val id: Int, + val text: String, + val date: String, +) + +class SearchHistoryStore(context: Context) { + private val appContext = context.applicationContext + + private val helper: SearchHistoryDBHelper by lazy { SearchHistoryDBHelper(appContext) } + + suspend fun loadAll(): List = withContext(Dispatchers.IO) { + helper.getHistories("").mapNotNull { model -> + (model as? SearchHistoryModel)?.let { + SearchHistoryEntry(it.id, it.text.toString(), it.date) + } + } + } + + suspend fun add(trimmedQuery: String) = withContext(Dispatchers.IO) { + if (trimmedQuery.isBlank()) return@withContext + helper.addToHistory(trimmedQuery, null) + } + + suspend fun remove(id: Int) = withContext(Dispatchers.IO) { + helper.removeFromHistory(id, null) + } + + suspend fun clear() = withContext(Dispatchers.IO) { + helper.clearHistories() + } +} diff --git a/app/src/main/java/com/quranapp/android/db/searchindex/SearchIndexDao.kt b/app/src/main/java/com/quranapp/android/db/searchindex/SearchIndexDao.kt new file mode 100644 index 000000000..ec9bc9adb --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/searchindex/SearchIndexDao.kt @@ -0,0 +1,77 @@ +package com.quranapp.android.db.searchindex + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import com.quranapp.android.db.relations.SearchResultSearchRow +import com.quranapp.android.db.relations.SearchResultVerseRow + +@Dao +interface SearchIndexDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertTranslationRows(rows: List) + + @Query("DELETE FROM translation_search_content WHERE slug = :slug") + suspend fun deleteTranslationRowsForSlug(slug: String) + + @Query("SELECT * FROM translation_index_meta WHERE slug = :slug") + suspend fun getMeta(slug: String): TranslationIndexMetaEntity? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun upsertMeta(meta: TranslationIndexMetaEntity) + + @Query("DELETE FROM translation_index_meta WHERE slug = :slug") + suspend fun deleteMeta(slug: String) + + @Transaction + suspend fun replaceSlugIndex( + slug: String, + rows: List, + fingerprint: String + ) { + deleteTranslationRowsForSlug(slug) + + if (rows.isNotEmpty()) { + insertTranslationRows(rows) + } + + if (rows.isEmpty()) { + deleteMeta(slug) + } else { + upsertMeta(TranslationIndexMetaEntity(slug = slug, fingerprint = fingerprint)) + } + } + + @Query( + """ + SELECT surahNo, ayahNo + FROM translation_search_fts + WHERE translation_search_fts MATCH :ftsQuery + GROUP BY surahNo, ayahNo + ORDER BY surahNo, ayahNo + LIMIT :limit OFFSET :offset + """ + ) + suspend fun pageMatchedVerses( + ftsQuery: String, + limit: Int, + offset: Int, + ): List + + @Query( + """ + SELECT slug, surahNo, ayahNo, text + FROM translation_search_fts + WHERE translation_search_fts MATCH :ftsQuery + AND (surahNo || ':' || ayahNo) IN (:keys) + ORDER BY surahNo, ayahNo, slug + """ + ) + suspend fun rowsForPagedVerses( + ftsQuery: String, + keys: List, + ): List +} diff --git a/app/src/main/java/com/quranapp/android/db/searchindex/SearchIndexDatabase.kt b/app/src/main/java/com/quranapp/android/db/searchindex/SearchIndexDatabase.kt new file mode 100644 index 000000000..9987971e6 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/searchindex/SearchIndexDatabase.kt @@ -0,0 +1,19 @@ +package com.quranapp.android.db.searchindex + +import androidx.room.Database +import androidx.room.RoomDatabase + +const val SEARCH_INDEX_DB_VERSION = 1 + +@Database( + entities = [ + TranslationSearchContentEntity::class, + TranslationSearchFtsEntity::class, + TranslationIndexMetaEntity::class, + ], + version = SEARCH_INDEX_DB_VERSION, + exportSchema = false, +) +abstract class SearchIndexDatabase : RoomDatabase() { + abstract fun searchIndexDao(): SearchIndexDao +} diff --git a/app/src/main/java/com/quranapp/android/db/searchindex/TranslationIndexMetaEntity.kt b/app/src/main/java/com/quranapp/android/db/searchindex/TranslationIndexMetaEntity.kt new file mode 100644 index 000000000..2b255ed1f --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/searchindex/TranslationIndexMetaEntity.kt @@ -0,0 +1,15 @@ +package com.quranapp.android.db.searchindex + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "translation_index_meta") +data class TranslationIndexMetaEntity( + @PrimaryKey + @ColumnInfo(name = "slug") + val slug: String, + + @ColumnInfo(name = "fingerprint") + val fingerprint: String, +) diff --git a/app/src/main/java/com/quranapp/android/db/searchindex/TranslationSearchContentEntity.kt b/app/src/main/java/com/quranapp/android/db/searchindex/TranslationSearchContentEntity.kt new file mode 100644 index 000000000..8f4da3c32 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/searchindex/TranslationSearchContentEntity.kt @@ -0,0 +1,30 @@ +package com.quranapp.android.db.searchindex + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "translation_search_content", + indices = [ + Index(value = ["slug", "surahNo", "ayahNo"], unique = true), + ], +) +data class TranslationSearchContentEntity( + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") + val id: Long = 0L, + + @ColumnInfo(name = "slug") + val slug: String, + + @ColumnInfo(name = "surahNo") + val surahNo: Int, + + @ColumnInfo(name = "ayahNo") + val ayahNo: Int, + + @ColumnInfo(name = "text") + val text: String, +) diff --git a/app/src/main/java/com/quranapp/android/db/searchindex/TranslationSearchFtsEntity.kt b/app/src/main/java/com/quranapp/android/db/searchindex/TranslationSearchFtsEntity.kt new file mode 100644 index 000000000..3ea0c39e3 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/db/searchindex/TranslationSearchFtsEntity.kt @@ -0,0 +1,21 @@ +package com.quranapp.android.db.searchindex + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Fts4 + +@Fts4(contentEntity = TranslationSearchContentEntity::class) +@Entity(tableName = "translation_search_fts") +data class TranslationSearchFtsEntity( + @ColumnInfo(name = "slug") + val slug: String, + + @ColumnInfo(name = "surahNo") + val surahNo: Int, + + @ColumnInfo(name = "ayahNo") + val ayahNo: Int, + + @ColumnInfo(name = "text") + val text: String, +) diff --git a/app/src/main/java/com/quranapp/android/frags/BaseFragment.kt b/app/src/main/java/com/quranapp/android/frags/BaseFragment.kt deleted file mode 100644 index eea8de930..000000000 --- a/app/src/main/java/com/quranapp/android/frags/BaseFragment.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.quranapp.android.frags - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.os.Process -import android.view.View -import android.view.inputmethod.InputMethodManager -import androidx.activity.result.ActivityResult -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.CallSuper -import androidx.core.app.ActivityOptionsCompat -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import com.quranapp.android.interfaceUtils.ActivityResultStarter -import com.quranapp.android.utils.receivers.NetworkStateReceiver -import com.quranapp.android.utils.receivers.NetworkStateReceiver.Companion.intentFilter -import com.quranapp.android.utils.receivers.NetworkStateReceiver.NetworkStateReceiverListener - -abstract class BaseFragment : Fragment(), NetworkStateReceiverListener, ActivityResultStarter { - private val mActivityResultLauncher = activityResultHandler() - private var mNetworkReceiver: NetworkStateReceiver? = null - - override fun onDestroy() { - if (context != null && mNetworkReceiver != null) { - mNetworkReceiver!!.removeListener(this) - requireContext().unregisterReceiver(mNetworkReceiver) - } - super.onDestroy() - } - - @CallSuper - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - if (networkReceiverRegistrable() && context != null) { - mNetworkReceiver = NetworkStateReceiver().apply { - addListener(this@BaseFragment) - } - - ContextCompat.registerReceiver( - requireContext(), - mNetworkReceiver, - intentFilter, - ContextCompat.RECEIVER_EXPORTED - ) - } - } - - fun getArgs(): Bundle = arguments ?: Bundle() - - fun restartMainActivity(ctx: Context) { - // start a new Intent of the app - ctx.packageManager.getLaunchIntentForPackage(ctx.packageName)?.apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK) - }?.let { - startActivity(it) - } - - // kill the current process - Process.killProcess(Process.myPid()) - } - - protected open fun networkReceiverRegistrable(): Boolean { - return false - } - - fun hideSoftKeyboard(activity: Activity) { - (activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).let { - if (it.isAcceptingText) { - activity.currentFocus?.let { focus -> - it.hideSoftInputFromWindow(focus.windowToken, 0) - } - } - } - } - - fun launchActivity(ctx: Context?, cls: Class<*>?) { - startActivity(Intent(ctx, cls)) - } - - fun runOnUIThread(runnable: Runnable?) { - activity?.runOnUiThread(runnable) - } - - override fun startActivity4Result(intent: Intent, options: ActivityOptionsCompat?) { - mActivityResultLauncher.launch(intent, options) - } - - private fun activityResultHandler(): ActivityResultLauncher { - return registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - onActivityResult2( - result - ) - } - } - - protected open fun onActivityResult2(result: ActivityResult) {} - override fun networkAvailable() {} - override fun networkUnavailable() {} -} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/frags/search/FragSearchResult.kt b/app/src/main/java/com/quranapp/android/frags/search/FragSearchResult.kt deleted file mode 100644 index 1b3cbb352..000000000 --- a/app/src/main/java/com/quranapp/android/frags/search/FragSearchResult.kt +++ /dev/null @@ -1,290 +0,0 @@ -package com.quranapp.android.frags.search - -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.LinearLayoutManager -import com.peacedesign.android.utils.Dimen -import com.quranapp.android.R -import com.quranapp.android.activities.ActivitySearch -import com.quranapp.android.adapters.search.ADPVerseResults -import com.quranapp.android.components.quran.QuranMeta -import com.quranapp.android.components.quran.subcomponents.Translation -import com.quranapp.android.components.search.SearchResultModelBase -import com.quranapp.android.components.search.VerseResultCountModel -import com.quranapp.android.components.search.VerseResultModel -import com.quranapp.android.databinding.FragSearchResultsBinding -import com.quranapp.android.db.translation.QuranTranslContract.QuranTranslEntry.* -import com.quranapp.android.frags.BaseFragment -import com.quranapp.android.interfaceUtils.Destroyable -import com.quranapp.android.utils.Log -import com.quranapp.android.utils.extended.GapedItemDecoration -import com.quranapp.android.utils.search.SearchFilters -import com.quranapp.android.utils.thread.runner.CallableTaskRunner -import com.quranapp.android.utils.thread.tasks.BaseCallableTask -import com.quranapp.android.utils.univ.StringUtils -import java.util.* -import java.util.concurrent.atomic.AtomicInteger -import java.util.regex.Pattern - -class FragSearchResult : BaseFragment(), Destroyable { - private val mTaskRunner = CallableTaskRunner>() - private lateinit var mBinding: FragSearchResultsBinding - - private var firstTime = true - private var mVerseResultsAdapter: ADPVerseResults? = null - - @JvmField - var mIsLoadingInProgress = false - override fun destroy() { - if (mVerseResultsAdapter != null) { - mVerseResultsAdapter!!.destroy() - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - if (firstTime || !::mBinding.isInitialized) { - mBinding = FragSearchResultsBinding.inflate(inflater, container, false) - } - return mBinding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - if (!firstTime) { - return - } - firstTime = false - init(view.context) - } - - private fun init(context: Context) { - initRecyclerView(context) - } - - private fun initTransls(actvt: ActivitySearch) { - val selectedTranslSlug = actvt.mSearchFilters.selectedTranslSlug - - for (bookInfo in actvt.availableTranslModels.values) { - if (selectedTranslSlug == bookInfo.slug) { - actvt.mBinding.btnSelectTransl.text = bookInfo.bookName - break - } - } - - actvt.mBinding.btnQuickLinks.isChecked = actvt.mSearchFilters.showQuickLinks - } - - private fun initRecyclerView(context: Context) { - mVerseResultsAdapter = ADPVerseResults(context, this) - - mBinding.let { - it.results.adapter = mVerseResultsAdapter - it.results.itemAnimator = null - it.results.layoutManager = LinearLayoutManager(context) - it.results.addItemDecoration(GapedItemDecoration(Dimen.dp2px(context, 5f))) - } - } - - fun initSearch(activitySearch: ActivitySearch) { - initTransls(activitySearch) - searchAsync(activitySearch) - } - - private fun searchAsync(activitySearch: ActivitySearch) { - val quranMeta = activitySearch.mQuranMeta - activitySearch.mBinding.filter.visibility = View.GONE - activitySearch.mBinding.voiceSearch.visibility = View.GONE - mIsLoadingInProgress = true - searchAsyncAfterTranslReady(activitySearch, quranMeta) - } - - private fun searchAsyncAfterTranslReady(activity: ActivitySearch, meta: QuranMeta) { - mTaskRunner.cancel() - - val query = activity.mLocalHistoryManager.lastQuery ?: return - - mTaskRunner.callAsync(object : BaseCallableTask>() { - override fun preExecute() { - mVerseResultsAdapter!!.setActivitySearch(activity) - - mBinding.loader.visibility = View.VISIBLE - mBinding.noResultsFound.visibility = View.GONE - mBinding.results.scrollToPosition(0) - mBinding.results.adapter = null - } - - @Throws(Exception::class) - override fun call(): ArrayList { - var jumpers = ArrayList() - val results = ArrayList() - - if (activity.mSearchFilters.showQuickLinks) { - jumpers = activity.prepareJumper(activity.mQuranMeta, query) - results.addAll(jumpers) - } - - val pattern = resolveQuerySearchPattern(activity.mSearchFilters, query) - val resultCount = AtomicInteger(0) - - val selectedTranslSlug = activity.mSearchFilters.selectedTranslSlug - if (!selectedTranslSlug.isNullOrEmpty()) { - searchInDB(activity, meta, results, selectedTranslSlug, query, 20, 0, resultCount, pattern) - } - - val jumpersCount = jumpers.size - if (results.size > 0) { - val resultCountModel = VerseResultCountModel( - if (!selectedTranslSlug.isNullOrEmpty()) activity.availableTranslModels[selectedTranslSlug] else null - ) - resultCountModel.resultCount = resultCount.get() - results.add(jumpersCount, resultCountModel) - } - - return results - } - - override fun onComplete(results: ArrayList) { - if (results.size == 0) { - mBinding.noResultsFound.visibility = View.VISIBLE - val bookInfo = activity.availableTranslModels[activity.mSearchFilters.selectedTranslSlug] - - mBinding.noResultsFound.text = if (bookInfo != null) { - activity.getString(R.string.strMsgSearchNoResultsFoundIn, bookInfo.bookName) - } else { - activity.getString(R.string.strMsgSearchNoResultsFoundAbsolute) - } - return - } - populateResults(results) - } - - override fun postExecute() { - mIsLoadingInProgress = false - mBinding.loader.visibility = View.GONE - if (isVisible) { - activity.mBinding.filter.visibility = View.VISIBLE - activity.mBinding.voiceSearch.visibility = - if (activity.mSupportsVoiceInput) View.VISIBLE else View.GONE - } - } - }) - } - - fun searchInDB( - actvt: ActivitySearch, - meta: QuranMeta, - results: ArrayList, - slug: String, - query: String, - limit: Int, - offset: Int, - resultCount: AtomicInteger, - pattern: Pattern - ) { - val bookInfo = actvt.mTranslFactory.getTranslationBookInfo(slug) - val db = actvt.mTranslFactory.dbHelper.readableDatabase - val rawQuery = actvt.mTranslFactory.prepareQuerySingle( - actvt.mSearchFilters, - query, - slug, - limit, - offset - ) - val cursor = db.rawQuery(rawQuery, null, null) - Log.d(rawQuery, Arrays.toString(cursor.columnNames), cursor.count) - - while (cursor.moveToNext()) { - val translations = ArrayList() - val nTranslSlugs = TreeSet() - val nTranslDisplayNames = ArrayList() - val wordStartIndices = ArrayList() - val wordEndIndices = ArrayList() - - val translation = Translation() - translation.chapterNo = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CHAPTER_NO)) - translation.verseNo = cursor.getInt(cursor.getColumnIndexOrThrow(COL_VERSE_NO)) - translation.text = cursor.getString(cursor.getColumnIndexOrThrow(COL_TEXT)) - translation.bookSlug = slug - - Log.d(translation.text) - - // find query indices in the translation text - if (translation.text.isNotEmpty()) { - translation.text = StringUtils.removeHTML(translation.text, false) - - if ("en" == bookInfo.langCode) { - translation.text = org.apache.commons.lang3.StringUtils.stripAccents( - translation.text - ) - } - - val matcher = pattern.matcher(translation.text) - if (matcher.find()) { - translations.add(translation) - nTranslSlugs.add(translation.bookSlug) - nTranslDisplayNames.add(bookInfo.getDisplayNameWithHyphen()) - wordStartIndices.add(matcher.start(1)) - wordEndIndices.add(matcher.end(1)) - resultCount.getAndIncrement() - } - } - - if (translations.isNotEmpty() && wordStartIndices.isNotEmpty()) { - val verseResultModel = prepareVerseResult( - actvt, meta, translation.chapterNo, translation.verseNo, nTranslSlugs, - nTranslDisplayNames, translations, wordStartIndices, wordEndIndices - ) - results.add(verseResultModel) - } - } - cursor.close() - } - - fun populateResults(results: ArrayList?) { - mVerseResultsAdapter?.setResults(results) - mBinding.results.adapter = mVerseResultsAdapter - } - - private fun resolveQuerySearchPattern(filters: SearchFilters, query: String): Pattern { - val nQuery = Regex.escape(query) - val wordPattern = if (filters.searchWordPart) { - "($nQuery)" - } else { - "\\b($nQuery)\\b" - } - - return Pattern.compile(wordPattern, Pattern.CASE_INSENSITIVE) - } - - private fun prepareVerseResult( - activity: ActivitySearch, - quranMeta: QuranMeta, - chapterNo: Int, - verseNo: Int, - translSlugs: Set, - translDisplayNames: List, - translations: List, - startIndices: List, - endIndices: List - ): VerseResultModel { - return VerseResultModel().apply { - this.chapterNo = chapterNo - this.verseNo = verseNo - this.chapterName = quranMeta.getChapterName(activity, chapterNo, true) - this.chapterNameSansPrefix = quranMeta.getChapterName(activity, chapterNo, false) - this.verseSerial = "$chapterNo:$verseNo" - this.translSlugs = translSlugs - this.translDisplayNames = translDisplayNames - this.translations = translations - this.startIndices = startIndices - this.endIndices = endIndices - } - } -} diff --git a/app/src/main/java/com/quranapp/android/frags/search/FragSearchSuggestions.java b/app/src/main/java/com/quranapp/android/frags/search/FragSearchSuggestions.java deleted file mode 100644 index e1f4e23ce..000000000 --- a/app/src/main/java/com/quranapp/android/frags/search/FragSearchSuggestions.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.quranapp.android.frags.search; - -import android.content.Context; -import android.os.Bundle; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.core.text.HtmlCompat; -import androidx.recyclerview.widget.LinearLayoutManager; -import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; - -import com.peacedesign.android.utils.ColorUtils; -import com.peacedesign.android.utils.span.RoundedBG_FGSpan; -import com.quranapp.android.R; -import com.quranapp.android.activities.ActivitySearch; -import com.quranapp.android.adapters.search.ADPSearchSugg; -import com.quranapp.android.components.search.SearchResultModelBase; -import com.quranapp.android.databinding.FragSearchSuggestionsBinding; -import com.quranapp.android.frags.BaseFragment; -import com.quranapp.android.interfaceUtils.Destroyable; -import com.quranapp.android.utils.univ.MessageUtils; - -import java.util.ArrayList; - -public class FragSearchSuggestions extends BaseFragment implements Destroyable { - private boolean firstTime = true; - private FragSearchSuggestionsBinding mBinding; - private int spannableTextColor; - private int spannableBGColor; - private ADPSearchSugg mSearchSuggAdapter; - - public FragSearchSuggestions() { - } - - public static FragSearchSuggestions newInstance() { - return new FragSearchSuggestions(); - } - - @Override - public void onDestroy() { - if (mSearchSuggAdapter != null) { - mSearchSuggAdapter.mSuggModels.clear(); - } - super.onDestroy(); - } - - @Override - public void destroy() { - if (mSearchSuggAdapter != null) { - mSearchSuggAdapter.mSuggModels.clear(); - } - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - if (firstTime || mBinding == null) { - mBinding = FragSearchSuggestionsBinding.inflate(inflater, container, false); - } - return mBinding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (!firstTime) { - return; - } - - firstTime = false; - - spannableTextColor = ContextCompat.getColor(mBinding.getRoot().getContext(), R.color.colorPrimary); - spannableBGColor = ContextCompat.getColor(mBinding.getRoot().getContext(), R.color.colorBGLightGrey); - - init(view.getContext()); - } - - private void init(Context context) { - initSuggRecycler(context); - } - - private void initSuggRecycler(Context context) { - mSearchSuggAdapter = new ADPSearchSugg(context); - mBinding.suggs.setLayoutManager(new LinearLayoutManager(context)); - mBinding.suggs.setAdapter(mSearchSuggAdapter); - mBinding.suggs.setItemAnimator(null); - } - - public void initSuggestion(ActivitySearch activitySearch, String query) throws NumberFormatException { - mBinding.btnClear.setOnClickListener(v -> MessageUtils.showConfirmationDialog( - activitySearch, - R.string.msgClearSearchHistory, - null, - R.string.strLabelRemoveAll, - ColorUtils.DANGER, - () -> { - activitySearch.mHistoryDBHelper.clearHistories(); - initSuggestion(activitySearch, query); - } - )); - - boolean isEmpty = TextUtils.isEmpty(query); - - mBinding.btnClear.setVisibility( - isEmpty && activitySearch.mHistoryDBHelper.getHistoriesCount() > 0 - ? View.VISIBLE - : View.GONE - ); - - prepareNShowSugg(activitySearch, query, isEmpty); - } - - private void prepareNShowSugg(ActivitySearch activitySearch, String query, boolean isEmpty) { - ArrayList suggModels = new ArrayList<>(); - - if (!isEmpty) { - suggModels.addAll(activitySearch.prepareJumper( - activitySearch.mQuranMeta, - query - )); - } - - suggModels.addAll(prepareHistories(activitySearch, isEmpty ? null : query)); - - mSearchSuggAdapter.setSuggModels(activitySearch, suggModels); - mBinding.suggs.setAdapter(mSearchSuggAdapter); - } - - private ArrayList prepareHistories(ActivitySearch activitySearch, String query) { - return activitySearch.mHistoryDBHelper.getHistories(query); - } -} diff --git a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt index 09414c994..4bf42a03f 100644 --- a/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt +++ b/app/src/main/java/com/quranapp/android/repository/QuranRepository.kt @@ -27,6 +27,7 @@ class QuranRepository( private val extDatabase: ExternalQuranDatabase ) { private val mushafDao get() = database.mushafDao() + private val arabicSearchDao get() = database.arabicSearchDao() private val ayahDao get() = database.ayahDao() private val ayahWordDao get() = database.ayahWordDao() private val surahDao get() = database.surahDao() @@ -601,6 +602,12 @@ class QuranRepository( } } + suspend fun arabicTextSearch( + ftsQuery: String, + limit: Int, + offset: Int, + ) = arabicSearchDao.pageMatchedAyahs(ftsQuery, limit, offset) + suspend fun searchSurahNos(query: String): List { return surahSearchDao.searchSurahNos( query diff --git a/app/src/main/java/com/quranapp/android/search/FtsQueryBuilder.kt b/app/src/main/java/com/quranapp/android/search/FtsQueryBuilder.kt new file mode 100644 index 000000000..0ab995d93 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/search/FtsQueryBuilder.kt @@ -0,0 +1,31 @@ +package com.quranapp.android.search + +/** + * Builds FTS4 MATCH strings from an already normalized query. + * Uses AND of prefix terms: `word1* AND word2*`. + */ +object FtsQueryBuilder { + fun toPrefixAndQuery(normalizedQuery: String): String? { + val tokens = normalizedQuery.split(' ') + .map { it.trim() } + .filter { it.isNotBlank() } + + if (tokens.isEmpty()) return null + + return tokens.joinToString(" AND ") { token -> + val escaped = escapeFtsToken(token) + + "${escaped}*" + } + } + + private fun escapeFtsToken(token: String): String { + var t = token.replace("\"", "\"\"") + + if (t.any { it.isWhitespace() || it == '*' }) { + t = "\"$t\"" + } + + return t + } +} diff --git a/app/src/main/java/com/quranapp/android/search/SearchIndexScheduler.kt b/app/src/main/java/com/quranapp/android/search/SearchIndexScheduler.kt new file mode 100644 index 000000000..56fe55bfe --- /dev/null +++ b/app/src/main/java/com/quranapp/android/search/SearchIndexScheduler.kt @@ -0,0 +1,99 @@ +package com.quranapp.android.search + +import android.content.Context +import android.content.Context.MODE_PRIVATE +import androidx.core.content.edit +import androidx.work.BackoffPolicy +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequest +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.OutOfQuotaPolicy +import androidx.work.WorkManager +import androidx.work.workDataOf +import com.quranapp.android.BuildConfig +import com.quranapp.android.db.searchindex.SEARCH_INDEX_DB_VERSION +import com.quranapp.android.utils.workers.TranslationSearchIndexWorker +import java.util.concurrent.TimeUnit + +object SearchIndexScheduler { + private const val UNIQUE_SYNC = "translation_search_index_sync_all" + + private fun workManager(context: Context): WorkManager = + WorkManager.getInstance(context.applicationContext) + + private fun buildRequest( + mode: String, + slug: String? = null, + backoffMs: Long? = null, + ): OneTimeWorkRequest { + val data = if (slug != null) { + workDataOf( + TranslationSearchIndexWorker.KEY_MODE to mode, + TranslationSearchIndexWorker.KEY_SLUG to slug, + ) + } else { + workDataOf(TranslationSearchIndexWorker.KEY_MODE to mode) + } + + return OneTimeWorkRequestBuilder() + .setInputData(data) + .apply { + backoffMs?.let { + setBackoffCriteria(BackoffPolicy.LINEAR, it, TimeUnit.MILLISECONDS) + } + + setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + } + .build() + } + + fun enqueueSlug(context: Context, slug: String) { + workManager(context).enqueue( + buildRequest( + TranslationSearchIndexWorker.MODE_SLUG, + slug = slug, + backoffMs = 30_000L, + ), + ) + } + + fun enqueueRemoveSlug(context: Context, slug: String) { + workManager(context).enqueue( + buildRequest( + TranslationSearchIndexWorker.MODE_REMOVE, + slug = slug, + ), + ) + } + + fun enqueueSyncAll(context: Context) { + workManager(context).enqueueUniqueWork( + UNIQUE_SYNC, + ExistingWorkPolicy.APPEND_OR_REPLACE, + buildRequest( + TranslationSearchIndexWorker.MODE_ALL, + backoffMs = 60_000L, + ), + ) + } + + + fun scheduleTranslationSearchIndexIfNeeded(context: Context) { + val sp = context.getSharedPreferences("search_index_prefs", MODE_PRIVATE) + val lastSchema = sp.getInt("translation_fts_schema", 0) + val lastVc = sp.getInt("translation_fts_vc", 0) + + if (lastSchema != SEARCH_INDEX_DB_VERSION || lastVc != BuildConfig.VERSION_CODE) { + sp.edit { + putInt( + "translation_fts_schema", + SEARCH_INDEX_DB_VERSION + ) + + putInt("translation_fts_vc", BuildConfig.VERSION_CODE) + } + + enqueueSyncAll(context) + } + } +} diff --git a/app/src/main/java/com/quranapp/android/search/SearchNormalizer.kt b/app/src/main/java/com/quranapp/android/search/SearchNormalizer.kt new file mode 100644 index 000000000..e30a34954 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/search/SearchNormalizer.kt @@ -0,0 +1,53 @@ +package com.quranapp.android.search + +import java.text.Normalizer +import java.util.Locale + +enum class ScriptType { + LATIN, + ARABIC, + OTHER, +} + + +object SearchNormalizer { + fun scriptForLang(langCode: String): ScriptType { + val code = langCode.lowercase(Locale.ROOT) + + return when (code) { + "en", "fr", "es", "id", "tr", "de", "nl", "sv", "no", "da", "it", "pt", "ms" -> ScriptType.LATIN + "ar", "ur", "fa" -> ScriptType.ARABIC + else -> ScriptType.OTHER + } + } + + fun normalize(input: String, script: ScriptType): String { + var text = Normalizer.normalize(input, Normalizer.Form.NFKC) + + text = text.replace("\\s+".toRegex(), " ").trim() + + text = when (script) { + ScriptType.LATIN -> latinNormalize(text) + ScriptType.ARABIC -> arabicNormalize(text) + ScriptType.OTHER -> text + } + + return text.replace("\\s+".toRegex(), " ").trim() + } + + private fun latinNormalize(s: String): String { + return s + .lowercase(Locale.ROOT) + .replace("[\\p{Punct}]".toRegex(), " ") + } + + private fun arabicNormalize(s: String): String { + return s + .replace("[\\u064B-\\u065F\\u0670]".toRegex(), "") + .replace("ـ", "") + .replace("[أإآٱ]".toRegex(), "ا") + .replace("ى", "ي") + .replace("\\s+".toRegex(), " ") + .trim() + } +} diff --git a/app/src/main/java/com/quranapp/android/search/SearchPagingSource.kt b/app/src/main/java/com/quranapp/android/search/SearchPagingSource.kt new file mode 100644 index 000000000..d2410ba47 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/search/SearchPagingSource.kt @@ -0,0 +1,317 @@ +package com.quranapp.android.search + +import android.app.Application +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.quranapp.android.db.DatabaseProvider +import com.quranapp.android.utils.quran.QuranUtils +import com.quranapp.android.utils.reader.factory.QuranTranslationFactory +import com.quranapp.android.utils.univ.StringUtils + +data class SearchResult( + val chapterNo: Int, + val verseNo: Int, + val matches: List, +) + +sealed class SearchResultMatch { + data class TranslationMatch( + val slug: String, + val displayName: String, + val preview: AnnotatedString, + ) : SearchResultMatch() + + data class QuranTextMatch( + val preview: AnnotatedString, + ) : SearchResultMatch() +} + +class SearchPagingSource( + private val application: Application, + private val query: String, +) : PagingSource() { + + override suspend fun load( + params: LoadParams + ): LoadResult { + return try { + val offset = params.key ?: 0 + val limit = params.loadSize + + val isArabic = query.any { ch -> + when (Character.UnicodeBlock.of(ch)) { + Character.UnicodeBlock.ARABIC, + Character.UnicodeBlock.ARABIC_SUPPLEMENT, + Character.UnicodeBlock.ARABIC_EXTENDED_A, + Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_A, + Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_B -> true + else -> false + } + } + + if (isArabic) { + val normalized = SearchNormalizer.normalize( + query, + ScriptType.ARABIC + ) + + val fts = FtsQueryBuilder.toPrefixAndQuery(normalized) + ?: return LoadResult.Page( + data = emptyList(), + prevKey = null, + nextKey = null + ) + + val quranRepo = DatabaseProvider.getQuranRepository(application) + val arabicRows = quranRepo.arabicTextSearch(fts, limit, offset) + + if (arabicRows.isEmpty()) { + return LoadResult.Page( + data = emptyList(), + prevKey = if (offset == 0) null else maxOf(0, offset - limit), + nextKey = null + ) + } + + val data = arabicRows.map { row -> + val ayahId = row.ayahId + val pair = QuranUtils.getVerseNoFromAyahId(ayahId) + val surahNo = pair.first + val ayahNo = pair.second + + SearchResult( + chapterNo = surahNo, + verseNo = ayahNo, + matches = listOf( + SearchResultMatch.QuranTextMatch( + preview = highlightMatches(row.text, query) + ) + ) + ) + } + + return LoadResult.Page( + data = data, + prevKey = if (offset == 0) null + else maxOf(0, offset - limit), + nextKey = + if (arabicRows.size < limit) null + else offset + arabicRows.size + ) + } + + val normalized = SearchNormalizer.normalize( + query, + ScriptType.OTHER + ) + + val fts = FtsQueryBuilder.toPrefixAndQuery(normalized) + ?: return LoadResult.Page( + data = emptyList(), + prevKey = null, + nextKey = null + ) + + QuranTranslationFactory(application).use { factory -> + val dao = DatabaseProvider + .getSearchIndexDatabase(application) + .searchIndexDao() + + val versePage = dao.pageMatchedVerses( + ftsQuery = fts, + limit = limit, + offset = offset + ) + + if (versePage.isEmpty()) { + return LoadResult.Page( + data = emptyList(), + prevKey = if (offset == 0) null else maxOf(0, offset - limit), + nextKey = null + ) + } + + val verseKeys = versePage.map { + it.surahNo to it.ayahNo + } + + val rows = dao.rowsForPagedVerses( + ftsQuery = fts, + keys = verseKeys.map { "${it.first}:${it.second}" } + ) + + val slugs = rows + .map { it.slug } + .toSet() + + val bulkTranslations = + factory.getTranslationsBulkForSearch( + slugs = slugs, + verseKeys = verseKeys + ) + + val books = + factory.getAvailableTranslationBooksInfo() + + val grouped = rows + .groupBy { it.surahNo to it.ayahNo } + .map { (coord, matches) -> + + SearchResult( + chapterNo = coord.first, + verseNo = coord.second, + matches = matches + .sortedBy { it.slug } + .map { row -> + + val text = + bulkTranslations[row.slug] + ?.get(coord) + ?.text + ?: row.text + + SearchResultMatch.TranslationMatch( + slug = row.slug, + displayName = + books[row.slug]?.displayName + ?: row.slug, + preview = highlightMatches( + StringUtils.removeHTML( + text, + false + ), + query + ) + ) + } + ) + } + + return LoadResult.Page( + data = grouped, + prevKey = if (offset == 0) null + else maxOf(0, offset - limit), + + nextKey = + if (versePage.size < limit) null + else offset + versePage.size + ) + } + + } catch (e: Exception) { + LoadResult.Error(e) + } + } + + override fun getRefreshKey( + state: PagingState + ): Int? { + + val anchor = state.anchorPosition ?: return null + val page = state.closestPageToPosition(anchor) + ?: return null + + return page.prevKey?.plus(state.config.pageSize) + ?: page.nextKey?.minus(state.config.pageSize) + } + + private fun highlightMatches(text: String, rawQuery: String): AnnotatedString { + val contextWindow = 180 + val sidePadding = 48 + val ellipsis = "…" + + val tokens = rawQuery + .trim() + .split(Regex("\\s+")) + .map { it.trim() } + .filter { it.length >= 2 } + .distinctBy { it.lowercase() } + + if (tokens.isEmpty()) { + if (text.length <= contextWindow) return buildAnnotatedString { append(text) } + + return buildAnnotatedString { + append(text.take(contextWindow).trimEnd()) + append(ellipsis) + } + } + + val source = text + val lower = source.lowercase() + val spans = mutableListOf() + for (token in tokens.sortedByDescending { it.length }) { + val q = token.lowercase() + var idx = 0 + while (idx < lower.length) { + val at = lower.indexOf(q, idx) + if (at < 0) break + spans += at until (at + q.length) + idx = at + q.length + } + } + + if (spans.isEmpty()) { + if (text.length <= contextWindow) return buildAnnotatedString { append(text) } + return buildAnnotatedString { + append(text.take(contextWindow).trimEnd()) + append(ellipsis) + } + } + + val merged = spans + .sortedBy { it.first } + .fold(mutableListOf()) { acc, range -> + val last = acc.lastOrNull() + if (last == null || range.first > last.last + 1) { + acc.add(range) + } else { + acc[acc.lastIndex] = last.first..maxOf(last.last, range.last) + } + acc + } + + val firstHit = merged.first() + val sliceStart = maxOf(0, firstHit.first - sidePadding) + val sliceEndExclusive = minOf(source.length, sliceStart + contextWindow) + + val prefix = if (sliceStart > 0) ellipsis else "" + val suffix = if (sliceEndExclusive < source.length) ellipsis else "" + + val rawSlice = source.substring(sliceStart, sliceEndExclusive) + val leadingTrimCount = rawSlice.length - rawSlice.trimStart().length + val visibleText = rawSlice.trimStart().trimEnd() + val contentStartInSource = sliceStart + leadingTrimCount + + val highlightStyle = SpanStyle(background = Color(0x66FFD858)) + + return buildAnnotatedString { + append(prefix) + append(visibleText) + append(suffix) + + val textOffset = prefix.length + + for (range in merged) { + val clippedStart = maxOf(range.first, sliceStart) + val clippedEndExclusive = minOf(range.last + 1, sliceEndExclusive) + if (clippedStart >= clippedEndExclusive) continue + + val startInVisible = clippedStart - contentStartInSource + val endInVisible = clippedEndExclusive - contentStartInSource + val styleStart = textOffset + maxOf(0, startInVisible) + val styleEnd = textOffset + minOf(visibleText.length, endInVisible) + if (styleStart >= styleEnd) continue + + addStyle( + style = highlightStyle, + start = styleStart, + end = styleEnd, + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/search/SearchQuickLinksParser.kt b/app/src/main/java/com/quranapp/android/search/SearchQuickLinksParser.kt new file mode 100644 index 000000000..f9a383b1a --- /dev/null +++ b/app/src/main/java/com/quranapp/android/search/SearchQuickLinksParser.kt @@ -0,0 +1,124 @@ +package com.quranapp.android.search + +import com.quranapp.android.db.relations.SurahWithLocalizations +import com.quranapp.android.repository.QuranRepository +import com.quranapp.android.utils.quran.QuranMeta +import com.quranapp.android.utils.univ.RegexPattern + +sealed class QuickLinkItem { + data class Chapter(val surah: SurahWithLocalizations) : QuickLinkItem() + data class Verse(val chapterNo: Int, val verseNo: Int) : QuickLinkItem() + data class Juz(val juzNo: Int) : QuickLinkItem() + data class Hizb(val hizbNo: Int) : QuickLinkItem() + data class Tafsir(val chapterNo: Int, val verseNo: Int) : QuickLinkItem() +} + +object SearchQuickLinksParser { + + private suspend fun chapterQuickLink( + repository: QuranRepository, + chapterNo: Int, + ): QuickLinkItem.Chapter? = + repository.getSurahWithLocalizations(chapterNo)?.let { QuickLinkItem.Chapter(it) } + + /** + * Parses quick navigation patterns from the search query (verse refs, chapter/juz numbers, + * and surah name/alias substring matches. + */ + suspend fun parse(repository: QuranRepository, rawQuery: String): List { + val q = rawQuery.trim() + if (q.isEmpty()) return emptyList() + + val out = ArrayList() + val mtchrVRangeJump = RegexPattern.VERSE_RANGE_JUMP_PATTERN.matcher(q) + val mtchrVJump = RegexPattern.VERSE_JUMP_PATTERN.matcher(q) + val mtchrChapOrJuzNo = RegexPattern.CHAPTER_OR_JUZ_PATTERN.matcher(q) + + if (mtchrVRangeJump.find()) { + val r = mtchrVRangeJump.toMatchResult() + + if (r.groupCount() >= 3) { + val chapNo = r.group(1).toInt() + + if (!QuranMeta.isChapterValid(chapNo)) { + return out + } + + var fromVerse = r.group(2).toInt() + var toVerse = r.group(3).toInt() + + if (fromVerse > toVerse) { + val tmp = fromVerse + fromVerse = toVerse + toVerse = tmp + } + + if (repository.isVerseValid4Chapter(chapNo, fromVerse)) { + out += QuickLinkItem.Verse(chapNo, fromVerse) + } + + val isFromValid = repository.isVerseValid4Chapter(chapNo, fromVerse) + val isToValid = repository.isVerseValid4Chapter(chapNo, toVerse) + + if (isFromValid) { + out += QuickLinkItem.Verse(chapNo, fromVerse) + out += QuickLinkItem.Tafsir(chapNo, fromVerse) + } + + if (isToValid && toVerse != fromVerse) { + out += QuickLinkItem.Verse(chapNo, toVerse) + out += QuickLinkItem.Tafsir(chapNo, toVerse) + } + + chapterQuickLink(repository, chapNo)?.let { out += it } + } + } else if (mtchrVJump.find()) { + val r = mtchrVJump.toMatchResult() + + if (r.groupCount() >= 2) { + val chapNo = r.group(1).toInt() + val verseNo = r.group(2).toInt() + + if (!QuranMeta.isChapterValid(chapNo)) { + return out + } + + if (repository.isVerseValid4Chapter(chapNo, verseNo)) { + out += QuickLinkItem.Verse(chapNo, verseNo) + out += QuickLinkItem.Tafsir(chapNo, verseNo) + } + + chapterQuickLink(repository, chapNo)?.let { out += it } + } + } else if (mtchrChapOrJuzNo.find()) { + val r = mtchrChapOrJuzNo.toMatchResult() + + if (r.groupCount() >= 1) { + val chapOrJuz = r.group(1).toInt() + + if (QuranMeta.isChapterValid(chapOrJuz)) { + chapterQuickLink(repository, chapOrJuz)?.let { out += it } + } + + if (QuranMeta.isJuzValid(chapOrJuz)) { + out += QuickLinkItem.Juz(chapOrJuz) + } + + if (QuranMeta.isHizbValid(chapOrJuz)) { + out += QuickLinkItem.Hizb(chapOrJuz) + } + } + } + + return out.distinctBy { it.stableKey() } + } +} + +fun QuickLinkItem.stableKey(): String = + when (this) { + is QuickLinkItem.Chapter -> "c${surah.surah.surahNo}" + is QuickLinkItem.Juz -> "j$juzNo" + is QuickLinkItem.Hizb -> "h$hizbNo" + is QuickLinkItem.Tafsir -> "t$chapterNo:$verseNo" + is QuickLinkItem.Verse -> "v$chapterNo:$verseNo" + } diff --git a/app/src/main/java/com/quranapp/android/utils/managers/TranslationDownloadManager.kt b/app/src/main/java/com/quranapp/android/utils/managers/TranslationDownloadManager.kt index 7235af261..f746773a8 100644 --- a/app/src/main/java/com/quranapp/android/utils/managers/TranslationDownloadManager.kt +++ b/app/src/main/java/com/quranapp/android/utils/managers/TranslationDownloadManager.kt @@ -1,6 +1,6 @@ package com.quranapp.android.utils.managers -import TranslationDownloadWorker +import com.quranapp.android.utils.workers.TranslationDownloadWorker import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer diff --git a/app/src/main/java/com/quranapp/android/utils/reader/TextDecorator.kt b/app/src/main/java/com/quranapp/android/utils/reader/TextDecorator.kt index d2dbecd90..838c96980 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/TextDecorator.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/TextDecorator.kt @@ -17,6 +17,7 @@ import com.alfaazplus.sunnah.ui.theme.fontUrdu import com.quranapp.android.compose.components.reader.MushafLineLayout import com.quranapp.android.db.entities.quran.AyahWordEntity import com.quranapp.android.utils.extensions.getDimension +import com.quranapp.android.utils.univ.StringUtils data class TextBuilderParams( val context: Context, @@ -76,11 +77,12 @@ fun getTranslationTextStyle( params: TranslationTextStyleParams, baseLineHeightMultiplier: Float = 1.5f ): TextStyle { + val isRtl = StringUtils.isRtlLanguage(params.slug) val isUrdu = TranslUtils.isUrdu(params.slug) val resolvedFontSize = 16.sp * params.sizeMultiplier return TextStyle( - textDirection = if (isUrdu) TextDirection.Rtl else TextDirection.Ltr, + textDirection = if (isRtl) TextDirection.Rtl else TextDirection.Ltr, fontFamily = if (isUrdu) fontUrdu else fontFamily, platformStyle = PlatformTextStyle( includeFontPadding = true diff --git a/app/src/main/java/com/quranapp/android/utils/reader/TranslUtils.java b/app/src/main/java/com/quranapp/android/utils/reader/TranslUtils.java index 568eb180f..3380c22d0 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/TranslUtils.java +++ b/app/src/main/java/com/quranapp/android/utils/reader/TranslUtils.java @@ -189,10 +189,6 @@ public static boolean isUrdu(String slug) { return Objects.equals(slug.split("_")[0], "ur"); } - public static boolean isRtl(String slug) { - return isUrdu(slug); - } - public static boolean isTransliteration(String slug) { return slug.contains(TRANSL_TRANSLITERATION_SLUG_PART); } diff --git a/app/src/main/java/com/quranapp/android/utils/reader/factory/QuranTranslationFactory.kt b/app/src/main/java/com/quranapp/android/utils/reader/factory/QuranTranslationFactory.kt index 32dbcf258..c322bcbbf 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/factory/QuranTranslationFactory.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/factory/QuranTranslationFactory.kt @@ -24,7 +24,6 @@ import com.quranapp.android.db.translation.QuranTranslDBHelper import com.quranapp.android.db.translation.QuranTranslInfoContract.QuranTranslInfoEntry import com.quranapp.android.utils.quran.QuranConstants import com.quranapp.android.utils.reader.TranslUtils -import com.quranapp.android.utils.search.SearchFilters import org.json.JSONArray import java.io.Closeable import java.util.Collections @@ -459,75 +458,64 @@ class QuranTranslationFactory(private val context: Context) : Closeable { } } - fun prepareQuerySingle( - filters: SearchFilters, - query: String, - slug: String, - limit: Int, - offset: Int - ): String { - val nQuery = query.replace(Regex("'", RegexOption.LITERAL), "''") - val tableName = QuranTranslDBHelper.escapeTableName(slug) - val sqlQuery = StringBuilder( - "SELECT $COL_CHAPTER_NO, $COL_VERSE_NO, $COL_TEXT FROM $tableName" - ) - sqlQuery.append(" WHERE") - if (filters.searchWordPart) { - sqlQuery.append(" $COL_TEXT LIKE '%$nQuery%'") - } else { - val likes = arrayOf( - "$nQuery %", "% $nQuery", - "% $nQuery %", - "% $nQuery.%", - "% $nQuery,%", "$nQuery,%", - "% $nQuery!%", "$nQuery!%", - "% $nQuery?%", "$nQuery?%", - "% $nQuery:%", "$nQuery:%", - "% $nQuery;%", "$nQuery;%", - "% $nQuery''%", "%''$nQuery''%", - "% $nQuery\"%", "%\"$nQuery\"%", - "% $nQuery`%", "%`$nQuery`%", - "% $nQuery)%", "%($nQuery)%", - "% $nQuery]%", "%[$nQuery]%", - "% $nQuery˺%", "%˹$nQuery˺%" - ) + fun getTranslationsBulkForSearch( + slugs: Set, + verseKeys: List> + ): Map, Translation>> { + + if (slugs.isEmpty() || verseKeys.isEmpty()) return emptyMap() + + val ids = verseKeys.map { "${it.first}:${it.second}" } - for ((index, like) in likes.withIndex()) { - if (index > 0) sqlQuery.append(" OR") - sqlQuery.append(" $COL_TEXT LIKE '$like'") + val sql = buildString { + slugs.forEachIndexed { index, slug -> + + if (index > 0) append(" UNION ALL ") + + append( + """ + SELECT + '$slug' AS slug, + chapterNo, + verseNo, + text, + footnotes + FROM `${slug}` + WHERE _ID IN (${ids.joinToString(",") { "?" }}) + """.trimIndent() + ) } + + append(" ORDER BY chapterNo, verseNo") } -// sqlQuery.append(" limit $limit offset $offset") - return sqlQuery.toString() - } - /** - * WARNING: NOT WORKING PROPERLY - * */ - fun prepareQuery(slugs: Set): String { - val firstTableName = QuranTranslDBHelper.escapeTableName(slugs.first()) - val sqlQuery = StringBuilder("SELECT ${getSearchColumns(slugs)} FROM $firstTableName") - for (slug in slugs.drop(1)) { - val tableName = QuranTranslDBHelper.escapeTableName(slug) - sqlQuery.append(" LEFT JOIN $tableName") - sqlQuery.append(" ON $tableName.$COL_CHAPTER_NO = $firstTableName.$COL_CHAPTER_NO") - sqlQuery.append(" AND $tableName.$COL_VERSE_NO = $firstTableName.$COL_VERSE_NO") - sqlQuery.append(" AND $tableName.$COL_TEXT LIKE ?") + val args = Array(ids.size * slugs.size) { i -> + ids[i % ids.size] } - sqlQuery.append(" WHERE $firstTableName.$COL_TEXT LIKE ?") -// sqlQuery.append(" limit 20") - return sqlQuery.toString() - } + val result = + mutableMapOf, Translation>>() + + dbHelper.readableDatabase.rawQuery(sql, args).use { cursor -> + + while (cursor.moveToNext()) { - private fun getSearchColumns(slugs: Set): String { - val cols = ArrayList() - for ((index, slug) in slugs.withIndex()) { - val tableName = QuranTranslDBHelper.escapeTableName(slug) - cols.add("$tableName.$COL_CHAPTER_NO AS $COL_CHAPTER_NO$index") - cols.add("$tableName.$COL_VERSE_NO AS $COL_VERSE_NO$index") - cols.add("$tableName.$COL_TEXT AS $COL_TEXT$index") + val slug = cursor.getString(0) + val surahNo = cursor.getInt(1) + val ayahNo = cursor.getInt(2) + val text = cursor.getString(3) + + val map = result.getOrPut(slug) { mutableMapOf() } + + map[surahNo to ayahNo] = Translation().apply { + chapterNo = surahNo + verseNo = ayahNo + this.text = text + bookSlug = slug + } + } } - return cols.joinToString(",") + + return result } } diff --git a/app/src/main/java/com/quranapp/android/utils/reader/factory/ReaderFactory.kt b/app/src/main/java/com/quranapp/android/utils/reader/factory/ReaderFactory.kt index a0b0530c4..0cc13c574 100644 --- a/app/src/main/java/com/quranapp/android/utils/reader/factory/ReaderFactory.kt +++ b/app/src/main/java/com/quranapp/android/utils/reader/factory/ReaderFactory.kt @@ -8,6 +8,7 @@ import com.quranapp.android.activities.reference.ActivityReference import com.quranapp.android.components.ReferenceVerseModel import com.quranapp.android.components.reader.ChapterVersePair import com.quranapp.android.compose.components.reader.ReaderMode +import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.db.entities.ReadHistoryEntity import com.quranapp.android.utils.quran.QuranMeta import com.quranapp.android.utils.reader.QuranScriptVariant @@ -39,6 +40,22 @@ object ReaderFactory { ) } + fun startMushafPage(context: Context, pageNo: Int) { + val mushafCode = ReaderPreferences.getQuranScript() + val variant = ReaderPreferences.getQuranScriptVariant() + + context.startActivity( + ReaderLaunchParams( + data = ReaderIntentData.MushafPage( + mushafCode = mushafCode, + mushafVariant = variant, + pageNo = pageNo, + ), + readerMode = ReaderMode.Reading, + ).toIntent().setClass(context, ActivityReader::class.java) + ) + } + fun startChapter(context: Context, chapterNo: Int) { context.startActivity( prepareChapterIntent(chapterNo).setClass(context, ActivityReader::class.java) diff --git a/app/src/main/java/com/quranapp/android/utils/search/SearchFilters.java b/app/src/main/java/com/quranapp/android/utils/search/SearchFilters.java deleted file mode 100644 index be1b0f913..000000000 --- a/app/src/main/java/com/quranapp/android/utils/search/SearchFilters.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.quranapp.android.utils.search; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; - -import com.peacedesign.android.widget.dialog.base.PeaceDialog; -import com.quranapp.android.R; -import com.quranapp.android.activities.ActivitySearch; -import com.quranapp.android.databinding.LytSearchFiltersBinding; - -import kotlin.Unit; - -public class SearchFilters { - private final ActivitySearch mActivitySearch; - private PeaceDialog mFiltersDialog; - private LytSearchFiltersBinding mBinding; - - public boolean searchWordPart; - public boolean searchInArText; - public boolean showQuickLinks = true; - - public String selectedTranslSlug; - - private boolean mTmpSearchWordPart; - private boolean mTmpSearchInArText; - - public SearchFilters(ActivitySearch activitySearch, String initiallySelectedSlug) { - mActivitySearch = activitySearch; - selectedTranslSlug = initiallySelectedSlug; - - initFilters(mActivitySearch); - } - - - private void initFilters(Context context) { - PeaceDialog.Builder builder = PeaceDialog.newBuilder(context); - builder.setTitle(R.string.strTitleFilters); - builder.setNegativeButton(R.string.strLabelCancel, null); - builder.setPositiveButton(R.string.strLabelApply, (dialog, which) -> { - if (commitChanges()) { - mActivitySearch.applyFilters(); - } - }); - builder.setFocusOnPositive(true); - builder.setView(prepareFiltersLayout(context)); - mFiltersDialog = builder.create(); - } - - private View prepareFiltersLayout(Context context) { - mBinding = LytSearchFiltersBinding.inflate(LayoutInflater.from(context)); - /*setupTranslsSpinner(mBinding.translsSpinner);*/ - setupCheckBoxes(); - return mBinding.getRoot(); - } - - private void setupCheckBoxes() { - mBinding.searchWordPart.setOnCheckChangedListener((buttonView, isChecked) -> { - mTmpSearchWordPart = isChecked; - return Unit.INSTANCE; - }); - } - - private boolean commitChanges() { - if (searchWordPart == mTmpSearchWordPart && searchInArText == mTmpSearchInArText) { - return false; - } - - searchWordPart = mTmpSearchWordPart; - searchInArText = mTmpSearchInArText; - - return true; - } - - private void preShow() { - mBinding.searchWordPart.setChecked(searchWordPart); - } - - public void show() { - preShow(); - mFiltersDialog.show(); - } -} diff --git a/app/src/main/java/com/quranapp/android/utils/search/SearchLocalHistoryManager.java b/app/src/main/java/com/quranapp/android/utils/search/SearchLocalHistoryManager.java deleted file mode 100644 index b41cf0b43..000000000 --- a/app/src/main/java/com/quranapp/android/utils/search/SearchLocalHistoryManager.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.quranapp.android.utils.search; - -import android.text.TextUtils; - -import java.util.LinkedList; - -public class SearchLocalHistoryManager { - private final LinkedList mQueries = new LinkedList<>(); - private String mLastQuery = ""; - - public void onTravelForward(String query) { - if (!TextUtils.isEmpty(mLastQuery) && !mLastQuery.equalsIgnoreCase(query)) { - mQueries.remove(mLastQuery); - mQueries.add(mLastQuery); - } - - setLastQuery(query); - } - - public String onTravelBackward() { - if (!hasHistories()) { - return null; - } - - return mQueries.removeLast(); - } - - public boolean hasHistories() { - return !mQueries.isEmpty(); - } - - public boolean isCurrentQuery(String query) { - return query.equalsIgnoreCase(getLastQuery()); - } - - public String getLastQuery() { - return mLastQuery; - } - - public void setLastQuery(String query) { - mLastQuery = query; - } -} diff --git a/app/src/main/java/com/quranapp/android/utils/univ/StringUtils.java b/app/src/main/java/com/quranapp/android/utils/univ/StringUtils.java index 3cbb981f1..acf0f37af 100644 --- a/app/src/main/java/com/quranapp/android/utils/univ/StringUtils.java +++ b/app/src/main/java/com/quranapp/android/utils/univ/StringUtils.java @@ -159,11 +159,13 @@ public static boolean isRTL(char c) { public static boolean isRtlLanguage(String langCode) { String[] rtlLangs = {"ar", "fa", "ur", "ps", "sd", "ug", "dv", "he", "yi", "ku", "syr", "az-Arab", "ckb"}; + for (String rtlLang : rtlLangs) { if (rtlLang.equalsIgnoreCase(langCode)) { return true; } } + return false; } } \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/utils/workers/TranslationDownloadWorker.kt b/app/src/main/java/com/quranapp/android/utils/workers/TranslationDownloadWorker.kt index 1e72abf63..f71b023cb 100644 --- a/app/src/main/java/com/quranapp/android/utils/workers/TranslationDownloadWorker.kt +++ b/app/src/main/java/com/quranapp/android/utils/workers/TranslationDownloadWorker.kt @@ -1,3 +1,5 @@ +package com.quranapp.android.utils.workers + import android.app.PendingIntent import android.content.Context import android.content.Intent @@ -18,6 +20,7 @@ import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.utils.Logger import com.quranapp.android.utils.app.AppActions import com.quranapp.android.utils.app.NotificationUtils +import com.quranapp.android.search.SearchIndexScheduler import com.quranapp.android.utils.reader.factory.QuranTranslationFactory import com.quranapp.android.utils.sharedPrefs.SPAppActions.removeFromPendingAction import com.quranapp.android.utils.univ.Keys @@ -112,6 +115,8 @@ class TranslationDownloadWorker( it.dbHelper.storeTranslation(bookInfo, tmpFile.readText()) } + SearchIndexScheduler.enqueueSlug(ctx.applicationContext, bookInfo.slug) + removeFromPendingAction(ctx, AppActions.APP_ACTION_TRANSL_UPDATE, bookInfo.slug) val savedTranslations = ReaderPreferences.getTranslations().toMutableSet() if (savedTranslations.remove(bookInfo.slug)) { diff --git a/app/src/main/java/com/quranapp/android/utils/workers/TranslationSearchIndexWorker.kt b/app/src/main/java/com/quranapp/android/utils/workers/TranslationSearchIndexWorker.kt new file mode 100644 index 000000000..81f26a674 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/utils/workers/TranslationSearchIndexWorker.kt @@ -0,0 +1,137 @@ +package com.quranapp.android.utils.workers + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.quranapp.android.db.DatabaseProvider +import com.quranapp.android.db.searchindex.SEARCH_INDEX_DB_VERSION +import com.quranapp.android.db.searchindex.TranslationSearchContentEntity +import com.quranapp.android.db.translation.QuranTranslContract.QuranTranslEntry.COL_CHAPTER_NO +import com.quranapp.android.db.translation.QuranTranslContract.QuranTranslEntry.COL_TEXT +import com.quranapp.android.db.translation.QuranTranslContract.QuranTranslEntry.COL_VERSE_NO +import com.quranapp.android.db.translation.QuranTranslDBHelper +import com.quranapp.android.search.SearchNormalizer +import com.quranapp.android.utils.reader.factory.QuranTranslationFactory +import com.quranapp.android.utils.univ.StringUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class TranslationSearchIndexWorker( + appContext: Context, + params: WorkerParameters, +) : CoroutineWorker(appContext, params) { + companion object { + const val KEY_MODE = "mode" + const val KEY_SLUG = "slug" + const val MODE_SLUG = "slug" + const val MODE_ALL = "all" + const val MODE_REMOVE = "remove" + } + + override suspend fun doWork(): Result = withContext(Dispatchers.IO) { + when (val mode = inputData.getString(KEY_MODE)) { + MODE_REMOVE -> { + val slug = inputData.getString(KEY_SLUG) ?: return@withContext Result.failure() + + removeSlug(applicationContext, slug) + + Result.success() + } + + MODE_SLUG -> { + val slug = inputData.getString(KEY_SLUG) ?: return@withContext Result.failure() + + buildIndexForSlugIfNeeded(applicationContext, slug) + + Result.success() + } + + MODE_ALL -> { + QuranTranslationFactory(applicationContext).use { factory -> + val slugs = factory.getAvailableTranslationBooksInfo().keys + + for (slug in slugs) { + if (isStopped) break + + if (factory.isTranslationDownloaded(slug)) { + buildIndexForSlugIfNeeded( + applicationContext, + slug, + ) + } else { + removeSlug(applicationContext, slug) + } + } + } + + Result.success() + } + + else -> Result.failure() + } + } + + private suspend fun buildIndexForSlugIfNeeded(context: Context, slug: String): Unit = + withContext(Dispatchers.IO) { + val dao = DatabaseProvider.getSearchIndexDatabase(context).searchIndexDao() + + QuranTranslationFactory(context).use { factory -> + if (!factory.isTranslationDownloaded(slug)) { + dao.replaceSlugIndex(slug, emptyList(), "") + return@use + } + + val book = factory.getTranslationBookInfo(slug) + val script = SearchNormalizer.scriptForLang(book.langCode) + val rowCount = countRows(factory, slug) + val fingerprint = "${book.lastUpdated}|$rowCount|${SEARCH_INDEX_DB_VERSION}" + + val existing = dao.getMeta(slug)?.fingerprint + if (existing == fingerprint) return@use + + val rows = ArrayList(rowCount.coerceAtMost(7000)) + val table = QuranTranslDBHelper.escapeTableName(slug) + + val q = + "SELECT $COL_CHAPTER_NO, $COL_VERSE_NO, $COL_TEXT FROM $table ORDER BY $COL_CHAPTER_NO ASC, $COL_VERSE_NO ASC" + + factory.dbHelper.readableDatabase.rawQuery(q, null).use { c -> + while (c.moveToNext()) { + val surah = c.getInt(0) + val ayah = c.getInt(1) + val raw = c.getString(2) ?: "" + val plain = StringUtils.removeHTML(raw, false) + + val norm = SearchNormalizer.normalize(plain, script) + if (norm.isBlank()) continue + + rows.add( + TranslationSearchContentEntity( + slug = slug, + surahNo = surah, + ayahNo = ayah, + text = norm, + ) + ) + } + } + + dao.replaceSlugIndex(slug, rows, fingerprint) + } + } + + private suspend fun removeSlug(context: Context, slug: String) = withContext(Dispatchers.IO) { + val dao = DatabaseProvider.getSearchIndexDatabase(context).searchIndexDao() + dao.replaceSlugIndex(slug, emptyList(), "") + } + + private fun countRows(factory: QuranTranslationFactory, slug: String): Int { + val table = QuranTranslDBHelper.escapeTableName(slug) + + factory.dbHelper.readableDatabase.rawQuery("SELECT COUNT(*) FROM $table", null).use { c -> + if (c.moveToFirst()) return c.getInt(0) + } + + return 0 + } +} diff --git a/app/src/main/java/com/quranapp/android/vh/search/VHChapterJump.kt b/app/src/main/java/com/quranapp/android/vh/search/VHChapterJump.kt deleted file mode 100644 index 14226bb07..000000000 --- a/app/src/main/java/com/quranapp/android/vh/search/VHChapterJump.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.quranapp.android.vh.search - -import com.quranapp.android.components.search.ChapterJumpModel -import com.quranapp.android.components.search.SearchResultModelBase -import com.quranapp.android.utils.reader.factory.ReaderFactory -import com.quranapp.android.widgets.chapterCard.ChapterCard - -class VHChapterJump(private val chapterCard: ChapterCard, applyMargins: Boolean) : - VHSearchResultBase(chapterCard) { - init { - setupJumperView(chapterCard, applyMargins) - } - - override fun bind(model: SearchResultModelBase, pos: Int) { - if (model is ChapterJumpModel) { - chapterCard.apply { - chapterNumber = model.chapterNo - setName(model.name, model.nameTranslation) - setOnClickListener { ReaderFactory.startChapter(it.context, model.chapterNo) } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/vh/search/VHJuzJump.kt b/app/src/main/java/com/quranapp/android/vh/search/VHJuzJump.kt deleted file mode 100644 index fc26faff8..000000000 --- a/app/src/main/java/com/quranapp/android/vh/search/VHJuzJump.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.quranapp.android.vh.search - -import com.quranapp.android.components.search.JuzJumpModel -import com.quranapp.android.components.search.SearchResultModelBase -import com.quranapp.android.databinding.LytReaderJuzSpinnerItemBinding -import com.quranapp.android.utils.reader.factory.ReaderFactory.startJuz - -class VHJuzJump(private val mBinding: LytReaderJuzSpinnerItemBinding, applyMargins: Boolean) : VHSearchResultBase( - mBinding.root -) { - init { - setupJumperView(mBinding.root, applyMargins) - } - - override fun bind(model: SearchResultModelBase, pos: Int) { - (model as JuzJumpModel).apply { - mBinding.juzSerial.text = model.juzSerial - mBinding.root.setOnClickListener { startJuz(it.context, model.juzNo) } - } - } -} diff --git a/app/src/main/java/com/quranapp/android/vh/search/VHSearchResultBase.kt b/app/src/main/java/com/quranapp/android/vh/search/VHSearchResultBase.kt deleted file mode 100644 index 9fa19a508..000000000 --- a/app/src/main/java/com/quranapp/android/vh/search/VHSearchResultBase.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.quranapp.android.vh.search - -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.MarginLayoutParams -import androidx.core.view.updateMarginsRelative -import androidx.recyclerview.widget.RecyclerView -import com.peacedesign.android.utils.Dimen -import com.quranapp.android.R -import com.quranapp.android.components.search.SearchResultModelBase - -open class VHSearchResultBase(itemView: View) : RecyclerView.ViewHolder(itemView) { - open fun bind(model: SearchResultModelBase, pos: Int) {} - - protected fun setupJumperView(view: View, applyMargins: Boolean) { - view.layoutParams = MarginLayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ).apply { - if (applyMargins) { - val marg = Dimen.dp2px(view.context, 10f) - updateMarginsRelative(start = marg, end = marg, bottom = 10) - } - } - view.setBackgroundResource(R.drawable.dr_bg_chapter_card_bordered_onlylight) - } -} diff --git a/app/src/main/java/com/quranapp/android/vh/search/VHTafsirJump.kt b/app/src/main/java/com/quranapp/android/vh/search/VHTafsirJump.kt deleted file mode 100644 index b309fce58..000000000 --- a/app/src/main/java/com/quranapp/android/vh/search/VHTafsirJump.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.quranapp.android.vh.search - -import android.content.Context -import android.graphics.Typeface -import android.text.SpannableString -import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.TextUtils -import android.text.style.TextAppearanceSpan -import androidx.core.content.ContextCompat -import com.peacedesign.android.utils.Dimen -import com.quranapp.android.R -import com.quranapp.android.components.search.SearchResultModelBase -import com.quranapp.android.components.search.TafsirJumpModel -import com.quranapp.android.utils.extensions.colorStateList -import com.quranapp.android.utils.extensions.drawable -import com.quranapp.android.utils.extensions.getDimenPx -import com.quranapp.android.utils.extensions.updatePaddings -import com.quranapp.android.utils.reader.factory.ReaderFactory.startTafsir -import com.quranapp.android.widgets.IconedTextView - -class VHTafsirJump(private val mTextView: IconedTextView, applyMargins: Boolean) : VHSearchResultBase(mTextView) { - init { - mTextView.updatePaddings(Dimen.dp2px(mTextView.context, 15f), Dimen.dp2px(mTextView.context, 10f)) - setupJumperView(mTextView, applyMargins) - } - - override fun bind(model: SearchResultModelBase, pos: Int) { - if(model is TafsirJumpModel){ - mTextView.apply { - setDrawables(mTextView.context.drawable(R.drawable.dr_icon_tafsir), null, null, null) - text = makeName(itemView.context, model.titleText, model.chapterNameText) - setOnClickListener { startTafsir(it.context, model.chapterNo, model.verseNo) } - } - } - } - - private fun makeName(ctx: Context, text: String, subtext: String): CharSequence { - val ssb = SpannableStringBuilder() - - val SANS_SERIF = "sans-serif" - val nameClr = ContextCompat.getColorStateList(ctx, R.color.color_reader_spinner_item_label) - - val nameSS = SpannableString(text) - setSpan(nameSS, TextAppearanceSpan(SANS_SERIF, Typeface.BOLD, -1, nameClr, null)) - ssb.append(nameSS) - - if (!TextUtils.isEmpty(subtext)) { - val translClr = ctx.colorStateList(R.color.color_reader_spinner_item_label2) - val dimen2 = ctx.getDimenPx(R.dimen.dmnCommonSize2) - - val translSS = SpannableString(subtext) - setSpan(translSS, TextAppearanceSpan(SANS_SERIF, Typeface.NORMAL, dimen2, translClr, null)) - ssb.append("\n").append(translSS) - } - return ssb - } - - private fun setSpan(ss: SpannableString, obj: Any) { - ss.setSpan(obj, 0, ss.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/quranapp/android/vh/search/VHVerseJump.kt b/app/src/main/java/com/quranapp/android/vh/search/VHVerseJump.kt deleted file mode 100644 index c516c39e0..000000000 --- a/app/src/main/java/com/quranapp/android/vh/search/VHVerseJump.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.quranapp.android.vh.search - -import android.content.Context -import android.graphics.Typeface -import android.text.SpannableString -import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.TextUtils -import android.text.style.TextAppearanceSpan -import android.view.View -import androidx.appcompat.widget.AppCompatTextView -import androidx.core.content.ContextCompat -import com.peacedesign.android.utils.Dimen -import com.quranapp.android.R -import com.quranapp.android.components.search.SearchResultModelBase -import com.quranapp.android.components.search.VerseJumpModel -import com.quranapp.android.utils.extensions.colorStateList -import com.quranapp.android.utils.extensions.getDimenPx -import com.quranapp.android.utils.extensions.updatePaddings -import com.quranapp.android.utils.reader.factory.ReaderFactory.startVerseRange - -class VHVerseJump(private val mTextView: AppCompatTextView, applyMargins: Boolean) : VHSearchResultBase(mTextView) { - init { - mTextView.updatePaddings(Dimen.dp2px(mTextView.context, 15f), Dimen.dp2px(mTextView.context, 10f)) - setupJumperView(mTextView, applyMargins) - } - - override fun bind(model: SearchResultModelBase, pos: Int) { - if(model is VerseJumpModel){ - mTextView.apply { - text = makeName(itemView.context, model.titleText, model.chapterNameText) - setOnClickListener { startVerseRange(it.context, model.chapterNo, model.fromVerseNo, model.toVerseNo) } - } - } - } - - private fun makeName(ctx: Context, text: String, subtext: String): CharSequence { - val ssb = SpannableStringBuilder() - - val SANS_SERIF = "sans-serif" - val nameClr = ContextCompat.getColorStateList(ctx, R.color.color_reader_spinner_item_label) - - val nameSS = SpannableString(text) - setSpan(nameSS, TextAppearanceSpan(SANS_SERIF, Typeface.BOLD, -1, nameClr, null)) - ssb.append(nameSS) - - if (!TextUtils.isEmpty(subtext)) { - val translClr = ctx.colorStateList(R.color.color_reader_spinner_item_label2) - val dimen2 = ctx.getDimenPx(R.dimen.dmnCommonSize2) - - val translSS = SpannableString(subtext) - setSpan(translSS, TextAppearanceSpan(SANS_SERIF, Typeface.NORMAL, dimen2, translClr, null)) - ssb.append("\n").append(translSS) - } - return ssb - } - - private fun setSpan(ss: SpannableString, obj: Any) { - ss.setSpan(obj, 0, ss.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } -} diff --git a/app/src/main/java/com/quranapp/android/viewModels/QuranSearchViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/QuranSearchViewModel.kt new file mode 100644 index 000000000..40fab6e97 --- /dev/null +++ b/app/src/main/java/com/quranapp/android/viewModels/QuranSearchViewModel.kt @@ -0,0 +1,149 @@ +package com.quranapp.android.viewModels + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import com.quranapp.android.db.DatabaseProvider +import com.quranapp.android.db.relations.SurahWithLocalizations +import com.quranapp.android.db.search.SearchHistoryEntry +import com.quranapp.android.db.search.SearchHistoryStore +import com.quranapp.android.search.QuickLinkItem +import com.quranapp.android.search.SearchPagingSource +import com.quranapp.android.search.SearchQuickLinksParser +import com.quranapp.android.search.SearchResult +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn + + +@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) +class QuranSearchViewModel(application: Application) : AndroidViewModel(application) { + private val repository = DatabaseProvider.getQuranRepository(application) + private val searchHistoryStore = SearchHistoryStore(application) + + private val _searchQuery = MutableStateFlow("") + val searchQuery: StateFlow = _searchQuery + + private val _searchHistory = MutableStateFlow>(emptyList()) + val searchHistory: StateFlow> = _searchHistory + + private val debouncedQuery = _searchQuery + .debounce(200) + .distinctUntilChanged() + .shareIn(viewModelScope, started = SharingStarted.Lazily, replay = 1) + + val quickLinks: StateFlow> = debouncedQuery + .mapLatest { query -> + SearchQuickLinksParser.parse(repository, query) + } + .stateIn( + viewModelScope, + started = SharingStarted.Lazily, + initialValue = emptyList(), + ) + + val surahResults: StateFlow?> = debouncedQuery + .mapLatest { query -> + repository.searchSurahs(query) + } + .stateIn( + viewModelScope, + started = SharingStarted.Lazily, + initialValue = null, + ) + + val searchResults: Flow> = debouncedQuery + .flatMapLatest { query -> + if (query.isBlank()) { + return@flatMapLatest flowOf(PagingData.empty()) + } + + Pager( + config = PagingConfig( + pageSize = 50, + enablePlaceholders = false, + ) + ) { + SearchPagingSource( + application = getApplication(), + query = query, + ) + }.flow + } + .cachedIn(viewModelScope) + .stateIn( + viewModelScope, + started = SharingStarted.Lazily, + initialValue = PagingData.empty(), + ) + + fun onQueryChange(value: String) { + _searchQuery.value = value + } + + fun refreshSearchHistory() { + viewModelScope.launch { + _searchHistory.value = searchHistoryStore.loadAll() + } + } + + // Call when the user commits a search outcome + fun recordSearchQuery(text: String) { + val trimmed = text.trim() + if (trimmed.isEmpty()) return + viewModelScope.launch { + searchHistoryStore.add(trimmed) + _searchHistory.value = searchHistoryStore.loadAll() + } + } + + fun recordCurrentSearchQuery() { + recordSearchQuery(_searchQuery.value) + } + + fun removeSearchHistory(id: Int) { + viewModelScope.launch { + searchHistoryStore.remove(id) + _searchHistory.value = searchHistoryStore.loadAll() + } + } + + fun clearSearchHistory() { + viewModelScope.launch { + searchHistoryStore.clear() + _searchHistory.value = emptyList() + } + } + + fun historySuggestionsForDisplay( + query: String, + quickLinks: List, + ): List { + val q = query.trim() + if (q.isEmpty() || quickLinks.isNotEmpty()) return emptyList() + val ql = q.lowercase() + val filtered = _searchHistory.value + .asSequence() + .filter { it.text.lowercase() != ql } + .filter { it.text.contains(q, ignoreCase = true) } + .toList() + val prefix = filtered.filter { it.text.startsWith(q, ignoreCase = true) } + val rest = filtered.filter { !it.text.startsWith(q, ignoreCase = true) } + return (prefix + rest).distinctBy { it.id }.take(5) + } +} diff --git a/app/src/main/java/com/quranapp/android/viewModels/TranslationViewModel.kt b/app/src/main/java/com/quranapp/android/viewModels/TranslationViewModel.kt index b0cb7f734..60e08821c 100644 --- a/app/src/main/java/com/quranapp/android/viewModels/TranslationViewModel.kt +++ b/app/src/main/java/com/quranapp/android/viewModels/TranslationViewModel.kt @@ -10,6 +10,7 @@ import com.quranapp.android.components.transls.TranslationGroupModel import com.quranapp.android.compose.utils.DataLoadError import com.quranapp.android.compose.utils.preferences.ReaderPreferences import com.quranapp.android.utils.reader.TranslUtils +import com.quranapp.android.search.SearchIndexScheduler import com.quranapp.android.utils.reader.factory.QuranTranslationFactory import com.quranapp.android.utils.univ.FileUtils import com.quranapp.android.views.reader.updateAllVotdWidgets @@ -135,6 +136,7 @@ class TranslationViewModel(application: Application) : AndroidViewModel(applicat private fun deleteTranslation(slug: String) { QuranTranslationFactory(application).use { it.deleteTranslation(slug) + SearchIndexScheduler.enqueueRemoveSlug(application.applicationContext, slug) _uiState.update { current -> val updatedGroups = current.translationGroups.map { group -> diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml deleted file mode 100644 index 78fe29725..000000000 --- a/app/src/main/res/layout/activity_search.xml +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/frag_search_results.xml b/app/src/main/res/layout/frag_search_results.xml deleted file mode 100644 index 3a120bea1..000000000 --- a/app/src/main/res/layout/frag_search_results.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/frag_search_suggestions.xml b/app/src/main/res/layout/frag_search_suggestions.xml deleted file mode 100644 index 51b7e69c1..000000000 --- a/app/src/main/res/layout/frag_search_suggestions.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/lyt_reader_juz_spinner_item.xml b/app/src/main/res/layout/lyt_reader_juz_spinner_item.xml deleted file mode 100644 index 5a92d3cfc..000000000 --- a/app/src/main/res/layout/lyt_reader_juz_spinner_item.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout/lyt_search_filters.xml b/app/src/main/res/layout/lyt_search_filters.xml deleted file mode 100644 index d9f4f7b6f..000000000 --- a/app/src/main/res/layout/lyt_search_filters.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/lyt_search_result_item.xml b/app/src/main/res/layout/lyt_search_result_item.xml deleted file mode 100644 index abc8cb628..000000000 --- a/app/src/main/res/layout/lyt_search_result_item.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d18fc19e0..fcd60ef6c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,6 +4,7 @@ QuranApp is a free and open-source app to read and explore the Noble Qur\'an with translations. This app has a nice and clean interface with features like tafsir, recitation, reminder and many more. \n\nDOWNLOAD NOW:\n%s Download SunnahApp Close + Clear Got it Cancel Apply @@ -23,6 +24,7 @@ Open in Reader Read » View all + All Skip Next Previous @@ -79,6 +81,7 @@ Go to Verse %d Read Verses %1$d - %2$d Read tafsir of verse %d + Tafsir for verse %1$d:%2$d Text sizes Arabic text size Translation text size @@ -189,6 +192,7 @@ Include footnotes Quick links Later + Global Search Search Type a term e.g., “Life” Search prophet @@ -300,6 +304,7 @@ %d item %d items No items + No results Verse sync : ON Verse sync : OFF %d place @@ -322,6 +327,7 @@ In the Name of Allâh, the Most Gracious, the Most Merciful Clear read history Clear search history + All search history will be removed. Resource download source Use this option to download Quran data from a different source if one is not working Show Arabic Verse Text @@ -408,4 +414,12 @@ Play word Previous word Next word + Results + Recent searches + Search tips + Read a verse or tafsir directly + Search chapters + Jump to “Chapter 30”, “Juz 30” or “Hizb 30” + Search in the translations + Search in the Quran text diff --git a/app/src/test/java/com/quranapp/android/search/FtsQueryBuilderTest.kt b/app/src/test/java/com/quranapp/android/search/FtsQueryBuilderTest.kt new file mode 100644 index 000000000..225ded420 --- /dev/null +++ b/app/src/test/java/com/quranapp/android/search/FtsQueryBuilderTest.kt @@ -0,0 +1,23 @@ +package com.quranapp.android.search + +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test + +class FtsQueryBuilderTest { + + @Test + fun prefixAndQuery_joinsWithAnd() { + val q = FtsQueryBuilder.toPrefixAndQuery("mercy lord") + assertNotNull(q) + assertTrue(q!!.contains("mercy*")) + assertTrue(q.contains("lord*")) + assertTrue(q.contains(" AND ")) + } + + @Test + fun blank_returnsNull() { + assertNull(FtsQueryBuilder.toPrefixAndQuery(" ")) + } +} diff --git a/app/src/test/java/com/quranapp/android/search/SearchNormalizerTest.kt b/app/src/test/java/com/quranapp/android/search/SearchNormalizerTest.kt new file mode 100644 index 000000000..86a30eab8 --- /dev/null +++ b/app/src/test/java/com/quranapp/android/search/SearchNormalizerTest.kt @@ -0,0 +1,32 @@ +package com.quranapp.android.search + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class SearchNormalizerTest { + + @Test + fun latin_mercyVariantsCollapse() { + val a = SearchNormalizer.normalize("Mercy", ScriptType.LATIN) + val b = SearchNormalizer.normalize("mercy!", ScriptType.LATIN) + val c = SearchNormalizer.normalize("MERCY", ScriptType.LATIN) + assertEquals(a, b) + assertEquals(b, c) + assertEquals("mercy", a) + } + + @Test + fun scriptForLang_mapsKnownCodes() { + assertEquals(ScriptType.LATIN, SearchNormalizer.scriptForLang("en")) + assertEquals(ScriptType.ARABIC, SearchNormalizer.scriptForLang("ar")) + assertEquals(ScriptType.OTHER, SearchNormalizer.scriptForLang("hi")) + } + + @Test + fun arabic_stripsTashkeel() { + val s = SearchNormalizer.normalize("رَحْمَةٍ", ScriptType.ARABIC) + assertTrue(s.isNotEmpty()) + assertTrue(!s.contains("\u064B")) + } +}