From 65cf10266c466eb2178845c66acecfb03fc6e18b Mon Sep 17 00:00:00 2001 From: Ahmed El-Helw Date: Fri, 8 Dec 2023 22:26:19 +0400 Subject: [PATCH] Use Sqldelight for translations --- app/build.gradle | 1 + .../common/LocalTranslationDisplaySort.kt | 2 + .../dao/translation/Translation.kt | 3 +- .../dao/translation/TranslationItem.kt | 21 ++- .../androidquran/data/QuranDataProvider.java | 7 +- .../database/TranslationsDBAdapter.kt | 176 ++++++------------ .../database/TranslationsDBHelper.kt | 112 ----------- .../quran/ayahtracker/AyahTrackerItem.kt | 2 +- .../quran/ayahtracker/AyahTrackerPresenter.kt | 2 +- .../ayahtracker/AyahTranslationTrackerItem.kt | 2 +- .../translation/BaseTranslationPresenter.kt | 49 +++-- .../InlineTranslationPresenter.java | 7 +- .../TranslationManagerPresenter.java | 13 +- .../translation/TranslationPresenter.kt | 4 +- .../TranslationListCallback.kt | 10 + .../TranslationListPresenter.kt | 22 +++ .../labs/androidquran/ui/PagerActivity.java | 4 +- .../ui/TranslationManagerActivity.java | 15 +- .../ui/fragment/AyahTranslationFragment.kt | 28 +-- .../ui/fragment/TabletFragment.java | 2 +- .../ui/fragment/TranslationFragment.java | 2 +- .../androidquran/ui/helpers/AyahTracker.kt | 2 +- .../ui/translation/TranslationView.java | 5 +- .../ui/util/TranslationsSpinnerAdapter.java | 5 +- .../quran/labs/androidquran/util/ShareUtil.kt | 3 +- .../view/InlineTranslationView.kt | 5 +- .../BaseTranslationPresenterTest.kt | 4 +- common/translation/build.gradle.kts | 37 ++++ .../data/TranslationsDataSource.kt | 64 +++++++ .../translation/di/TranslationDataModule.kt | 29 +++ .../mapper/LocalTranslationMapper.kt | 33 ++++ .../translation/model}/LocalTranslation.kt | 4 +- .../quran/mobile/translation/databases/1.db | Bin 0 -> 8192 bytes .../quran/mobile/translation/databases/2.db | Bin 0 -> 8192 bytes .../quran/mobile/translation/databases/3.db | Bin 0 -> 8192 bytes .../quran/mobile/translation/databases/4.db | Bin 0 -> 8192 bytes .../quran/mobile/translation/databases/5.db | Bin 0 -> 8192 bytes .../quran/mobile/translation/migrations/1.sqm | 26 +++ .../quran/mobile/translation/migrations/2.sqm | 18 ++ .../quran/mobile/translation/migrations/3.sqm | 19 ++ .../quran/mobile/translation/migrations/4.sqm | 20 ++ .../quran/mobile/translation/translations.sq | 29 +++ settings.gradle.kts | 1 + 43 files changed, 465 insertions(+), 323 deletions(-) delete mode 100644 app/src/main/java/com/quran/labs/androidquran/database/TranslationsDBHelper.kt create mode 100644 app/src/main/java/com/quran/labs/androidquran/presenter/translationlist/TranslationListCallback.kt create mode 100644 app/src/main/java/com/quran/labs/androidquran/presenter/translationlist/TranslationListPresenter.kt create mode 100644 common/translation/build.gradle.kts create mode 100644 common/translation/src/main/kotlin/com/quran/mobile/translation/data/TranslationsDataSource.kt create mode 100644 common/translation/src/main/kotlin/com/quran/mobile/translation/di/TranslationDataModule.kt create mode 100644 common/translation/src/main/kotlin/com/quran/mobile/translation/mapper/LocalTranslationMapper.kt rename {app/src/main/java/com/quran/labs/androidquran/common => common/translation/src/main/kotlin/com/quran/mobile/translation/model}/LocalTranslation.kt (89%) create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/1.db create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/2.db create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/3.db create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/4.db create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/5.db create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/1.sqm create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/2.sqm create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/3.sqm create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/4.sqm create mode 100644 common/translation/src/main/sqldelight/com/quran/mobile/translation/translations.sq diff --git a/app/build.gradle b/app/build.gradle index 6961ea4929..4cd569585d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -122,6 +122,7 @@ dependencies { implementation project(path: ':common:recitation') implementation project(path: ':common:search') implementation project(path: ':common:toolbar') + implementation project(path: ':common:translation') implementation project(path: ':common:upgrade') implementation project(path: ':common:ui:core') diff --git a/app/src/main/java/com/quran/labs/androidquran/common/LocalTranslationDisplaySort.kt b/app/src/main/java/com/quran/labs/androidquran/common/LocalTranslationDisplaySort.kt index 08870eefb2..65413ca1e9 100644 --- a/app/src/main/java/com/quran/labs/androidquran/common/LocalTranslationDisplaySort.kt +++ b/app/src/main/java/com/quran/labs/androidquran/common/LocalTranslationDisplaySort.kt @@ -1,5 +1,7 @@ package com.quran.labs.androidquran.common +import com.quran.mobile.translation.model.LocalTranslation + class LocalTranslationDisplaySort : Comparator { override fun compare(first: LocalTranslation, second: LocalTranslation): Int { return first.displayOrder.compareTo(second.displayOrder) diff --git a/app/src/main/java/com/quran/labs/androidquran/dao/translation/Translation.kt b/app/src/main/java/com/quran/labs/androidquran/dao/translation/Translation.kt index 1d01051d1a..bb155f5755 100644 --- a/app/src/main/java/com/quran/labs/androidquran/dao/translation/Translation.kt +++ b/app/src/main/java/com/quran/labs/androidquran/dao/translation/Translation.kt @@ -14,8 +14,7 @@ data class Translation(val id: Int, val saveTo: String, val languageCode: String, val translator: String? = "", - @Json(name = "translatorForeign") val translatorNameLocalized: String? = "", - val displayOrder: Int = -1) { + @Json(name = "translatorForeign") val translatorNameLocalized: String? = "") { fun withSchema(schema: Int) = copy(minimumVersion = schema) } diff --git a/app/src/main/java/com/quran/labs/androidquran/dao/translation/TranslationItem.kt b/app/src/main/java/com/quran/labs/androidquran/dao/translation/TranslationItem.kt index b5ce16c63a..ecb9ef06d7 100644 --- a/app/src/main/java/com/quran/labs/androidquran/dao/translation/TranslationItem.kt +++ b/app/src/main/java/com/quran/labs/androidquran/dao/translation/TranslationItem.kt @@ -1,5 +1,7 @@ package com.quran.labs.androidquran.dao.translation +import com.quran.mobile.translation.model.LocalTranslation + data class TranslationItem @JvmOverloads constructor(val translation: Translation, val localVersion: Int = 0, val displayOrder: Int = -1) : TranslationRowData { @@ -16,7 +18,22 @@ data class TranslationItem @JvmOverloads constructor(val translation: Translatio fun withTranslationRemoved() = this.copy(localVersion = 0) - fun withTranslationVersion(version: Int) = this.copy(localVersion = version) - fun withDisplayOrder(newDisplayOrder: Int) = this.copy(displayOrder = newDisplayOrder) + + fun withLocalVersionAndDisplayOrder(newVersion: Int, displayOrder: Int) = this.copy(localVersion = newVersion, displayOrder = displayOrder) + + fun asLocalTranslation(): LocalTranslation { + return LocalTranslation( + id = translation.id.toLong(), + filename = translation.fileName, + name = translation.displayName, + translator = translation.translator, + translatorForeign = translation.translatorNameLocalized, + url = translation.fileUrl, + languageCode = translation.languageCode, + version = localVersion, + minimumVersion = translation.minimumVersion, + displayOrder = displayOrder + ) + } } diff --git a/app/src/main/java/com/quran/labs/androidquran/data/QuranDataProvider.java b/app/src/main/java/com/quran/labs/androidquran/data/QuranDataProvider.java index e7da9e8bb6..bd246f8b2a 100644 --- a/app/src/main/java/com/quran/labs/androidquran/data/QuranDataProvider.java +++ b/app/src/main/java/com/quran/labs/androidquran/data/QuranDataProvider.java @@ -11,22 +11,23 @@ import android.net.Uri; import android.provider.BaseColumns; +import androidx.annotation.NonNull; + import com.quran.data.core.QuranInfo; import com.quran.labs.androidquran.BuildConfig; import com.quran.labs.androidquran.QuranApplication; import com.quran.labs.androidquran.R; -import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.database.DatabaseHandler; import com.quran.labs.androidquran.database.DatabaseUtils; import com.quran.labs.androidquran.database.TranslationsDBAdapter; import com.quran.labs.androidquran.util.QuranFileUtils; import com.quran.labs.androidquran.util.QuranUtils; +import com.quran.mobile.translation.model.LocalTranslation; import java.util.List; import javax.inject.Inject; -import androidx.annotation.NonNull; import timber.log.Timber; public class QuranDataProvider extends ContentProvider { @@ -107,7 +108,7 @@ private Cursor search(String query) { } private List getAvailableTranslations() { - return translationsDBAdapter.getTranslations(); + return translationsDBAdapter.legacyGetTranslations(); } private Cursor getSuggestions(String query) { diff --git a/app/src/main/java/com/quran/labs/androidquran/database/TranslationsDBAdapter.kt b/app/src/main/java/com/quran/labs/androidquran/database/TranslationsDBAdapter.kt index 7f02559cda..8d7984ea25 100644 --- a/app/src/main/java/com/quran/labs/androidquran/database/TranslationsDBAdapter.kt +++ b/app/src/main/java/com/quran/labs/androidquran/database/TranslationsDBAdapter.kt @@ -1,163 +1,91 @@ package com.quran.labs.androidquran.database -import android.content.ContentValues import android.content.Context -import android.database.Cursor -import android.database.sqlite.SQLiteDatabase import android.util.SparseArray import androidx.annotation.WorkerThread -import com.quran.labs.androidquran.common.LocalTranslation import com.quran.labs.androidquran.dao.translation.TranslationItem -import com.quran.labs.androidquran.database.TranslationsDBHelper.TranslationsTable import com.quran.labs.androidquran.util.QuranFileUtils import com.quran.mobile.di.qualifier.ApplicationContext -import timber.log.Timber -import java.util.Collections +import com.quran.mobile.translation.data.TranslationsDataSource +import com.quran.mobile.translation.model.LocalTranslation +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.runBlocking import javax.inject.Inject import javax.inject.Singleton @Singleton class TranslationsDBAdapter @Inject constructor( @ApplicationContext private val context: Context, - adapter: TranslationsDBHelper, + private val dataSource: TranslationsDataSource, private val quranFileUtils: QuranFileUtils ) { - private val db: SQLiteDatabase = adapter.writableDatabase - - @Volatile - private var cachedTranslations: List? = null + fun getTranslations(): Flow> { + return dataSource.translations() + .filterNotNull() + .map { translations -> + translations.filter { quranFileUtils.hasTranslation(context, it.filename) } + } + } - var lastWriteTime: Long = 0 - private set + @WorkerThread + fun legacyGetTranslations(): List { + return runBlocking { getTranslations().first() } + } + @WorkerThread fun getTranslationsHash(): SparseArray { val result = SparseArray() - for (item in getTranslations()) { - result.put(item.id, item) + for (item in legacyGetTranslations()) { + result.put(item.id.toInt(), item) } return result } - // intentional, since cachedTranslations can be replaced by another thread, causing the check - // to be true, but the cached object returned to be null (or to change). - @WorkerThread - fun getTranslations(): List { - // intentional, since cachedTranslations can be replaced by another thread, causing the check - // to be true, but the cached object returned to be null (or to change). - val cached = cachedTranslations - if (!cached.isNullOrEmpty()) { - return cached - } - var items: MutableList = ArrayList() - var cursor: Cursor? = null - try { - cursor = db.query(TranslationsTable.TABLE_NAME, - null, null, null, null, null, - TranslationsTable.ID + " ASC") - - while (cursor.moveToNext()) { - val id = cursor.getInt(0) - val name = cursor.getString(1) - val translator = cursor.getString(2) - val translatorForeign = cursor.getString(3) - val filename = cursor.getString(4) - val url = cursor.getString(5) - val languageCode = cursor.getString(6) - val version = cursor.getInt(7) - val minimumVersion = cursor.getInt(8) - val displayOrder = cursor.getInt(9) - - if (quranFileUtils.hasTranslation(context, filename)) { - items.add( - LocalTranslation( - id, filename, name, translator, - translatorForeign, url, languageCode, version, minimumVersion, displayOrder - ) - ) - } - } - } finally { - cursor?.close() - } + suspend fun deleteTranslationByFileName(filename: String) { + dataSource.removeTranslation(filename) + } - items = Collections.unmodifiableList(items) - if (items.size > 0) { - cachedTranslations = items + @WorkerThread + fun legacyDeleteTranslationByFileName(filename: String) { + runBlocking { + deleteTranslationByFileName(filename) } - return items } - fun deleteTranslationByFile(filename: String) { - db.execSQL("DELETE FROM " + TranslationsTable.TABLE_NAME + " WHERE " + - TranslationsTable.FILENAME + " = ?", arrayOf(filename)) + @WorkerThread + fun legacyWriteTranslationUpdates(updates: List): Boolean { + return runBlocking { writeTranslationUpdates(updates) } } - fun writeTranslationUpdates(updates: List): Boolean { - var result = true - db.beginTransaction() + suspend fun writeTranslationUpdates(updates: List): Boolean { + val (available, unavailable) = updates.partition { it.exists() } - try { - var cachedNextOrder = -1 - for (item in updates) { - if (item.exists()) { - var displayOrder = 0 - val translation = item.translation - if (item.displayOrder > -1) { - displayOrder = item.displayOrder - } else { - var cursor: Cursor? = null - if (cachedNextOrder == -1) { - try { - // get next highest display order - cursor = db.query( - TranslationsTable.TABLE_NAME, arrayOf(TranslationsTable.DISPLAY_ORDER), - null, null, null, null, - TranslationsTable.DISPLAY_ORDER + " DESC", - "1" - ) - if (cursor != null && cursor.moveToFirst()) { - cachedNextOrder = cursor.getInt(0) + 1 - displayOrder = cachedNextOrder++ - } - } finally { - cursor?.close() - } - } else { - displayOrder = cachedNextOrder++ - } - } - - val values = ContentValues() - values.put(TranslationsTable.ID, translation.id) - values.put(TranslationsTable.NAME, translation.displayName) - values.put(TranslationsTable.TRANSLATOR, translation.translator) - values.put(TranslationsTable.TRANSLATOR_FOREIGN, - translation.translatorNameLocalized) - values.put(TranslationsTable.FILENAME, translation.fileName) - values.put(TranslationsTable.URL, translation.fileUrl) - values.put(TranslationsTable.LANGUAGE_CODE, translation.languageCode) - values.put(TranslationsTable.VERSION, item.localVersion) - values.put(TranslationsTable.MINIMUM_REQUIRED_VERSION, translation.minimumVersion) - values.put(TranslationsTable.DISPLAY_ORDER, displayOrder) + val needNextOrder = available.any { it.displayOrder == -1 } + val nextOrder = if (needNextOrder) { + dataSource.maximumDisplayOrder().toInt() + 1 + } else { + available.maxOf { it.displayOrder } + 1 + } - db.replace(TranslationsTable.TABLE_NAME, null, values) + val items = if (needNextOrder) { + var nextOrderNumber = nextOrder + available.map { item -> + if (item.displayOrder == -1) { + item.copy(displayOrder = nextOrderNumber++) } else { - db.delete(TranslationsTable.TABLE_NAME, - TranslationsTable.ID + " = " + item.translation.id, null) + item } } - db.setTransactionSuccessful() - - lastWriteTime = System.currentTimeMillis() - // clear the cached translations - cachedTranslations = null - } catch (e: Exception) { - result = false - Timber.d(e, "error writing translation updates") - } finally { - db.endTransaction() + } else { + available } - return result + dataSource.updateTranslations(items.map { it.asLocalTranslation() }) + dataSource.removeTranslationsById(unavailable.map { it.translation.id.toLong() }) + + return true } } diff --git a/app/src/main/java/com/quran/labs/androidquran/database/TranslationsDBHelper.kt b/app/src/main/java/com/quran/labs/androidquran/database/TranslationsDBHelper.kt deleted file mode 100644 index 89dc574b4d..0000000000 --- a/app/src/main/java/com/quran/labs/androidquran/database/TranslationsDBHelper.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.quran.labs.androidquran.database - -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper -import com.quran.mobile.di.qualifier.ApplicationContext -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TranslationsDBHelper @Inject constructor(@ApplicationContext context: Context) : - SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { - - companion object { - private const val DB_NAME = "translations.db" - private const val DB_VERSION = 5 - private const val CREATE_TRANSLATIONS_TABLE = - "CREATE TABLE " + TranslationsTable.TABLE_NAME + "(" + - TranslationsTable.ID + " integer primary key, " + - TranslationsTable.NAME + " varchar not null, " + - TranslationsTable.TRANSLATOR + " varchar, " + - TranslationsTable.TRANSLATOR_FOREIGN + " varchar, " + - TranslationsTable.FILENAME + " varchar not null, " + - TranslationsTable.URL + " varchar, " + - TranslationsTable.LANGUAGE_CODE + " varchar, " + - TranslationsTable.VERSION + " integer not null default 0," + - TranslationsTable.MINIMUM_REQUIRED_VERSION + " integer not null default 0, " + - TranslationsTable.DISPLAY_ORDER + " integer not null default -1 " + - ");" - } - - override fun onCreate(db: SQLiteDatabase) { - db.execSQL(CREATE_TRANSLATIONS_TABLE) - } - - @Suppress("LocalVariableName") - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - if (oldVersion < 4) { - // a new column is added and columns are re-arranged - val BACKUP_TABLE = TranslationsTable.TABLE_NAME + "_backup" - db.beginTransaction() - try { - db.execSQL("ALTER TABLE " + TranslationsTable.TABLE_NAME + " RENAME TO " + BACKUP_TABLE) - db.execSQL(CREATE_TRANSLATIONS_TABLE) - db.execSQL("INSERT INTO " + TranslationsTable.TABLE_NAME + " (" + - TranslationsTable.ID + ", " + - TranslationsTable.NAME + ", " + - TranslationsTable.TRANSLATOR + ", " + - (if (oldVersion < 2) "" else (TranslationsTable.TRANSLATOR_FOREIGN + ", ")) + - TranslationsTable.FILENAME + ", " + - TranslationsTable.URL + ", " + - (if (oldVersion < 3) "" else (TranslationsTable.LANGUAGE_CODE + ",")) + - TranslationsTable.VERSION + ", " + - TranslationsTable.DISPLAY_ORDER + ") " + - "SELECT " + TranslationsTable.ID + ", " + - TranslationsTable.NAME + ", " + - TranslationsTable.TRANSLATOR + ", " + - (if (oldVersion < 2) "" else "translator_foreign, ") + - TranslationsTable.FILENAME + ", " + - TranslationsTable.URL + ", " + - (if (oldVersion < 3) "" else (TranslationsTable.LANGUAGE_CODE + ",")) + - TranslationsTable.VERSION + ", " + - TranslationsTable.ID + - " FROM " + BACKUP_TABLE) - db.execSQL("DROP TABLE $BACKUP_TABLE") - db.execSQL("UPDATE " + TranslationsTable.TABLE_NAME + " SET " + - TranslationsTable.MINIMUM_REQUIRED_VERSION + " = 2") - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } else if (oldVersion < 5) { - // the v3 and below update also updates to v5. - // this code is called for updating from v4. - upgradeToV5(db) - } - } - - private fun upgradeToV5(db: SQLiteDatabase) { - // Add display order column and add arbitrary order to existing translations - db.beginTransaction() - try { - db.execSQL("ALTER TABLE " - + TranslationsTable.TABLE_NAME - + " ADD COLUMN " - + TranslationsTable.DISPLAY_ORDER - + " integer not null default -1" - ) - - // for now, set the order to be the translation id - db.execSQL("UPDATE " + TranslationsTable.TABLE_NAME + " SET " + - TranslationsTable.DISPLAY_ORDER + " = " + TranslationsTable.ID) - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - internal object TranslationsTable { - const val TABLE_NAME = "translations" - const val ID = "id" - const val NAME = "name" - const val TRANSLATOR = "translator" - const val TRANSLATOR_FOREIGN = "translatorForeign" - const val FILENAME = "filename" - const val URL = "url" - const val LANGUAGE_CODE = "languageCode" - const val VERSION = "version" - const val MINIMUM_REQUIRED_VERSION = "minimumRequiredVersion" - const val DISPLAY_ORDER = "userDisplayOrder" - } -} diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTrackerItem.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTrackerItem.kt index 8f88d2643c..8ddaa83442 100644 --- a/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTrackerItem.kt +++ b/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTrackerItem.kt @@ -7,8 +7,8 @@ import com.quran.data.model.SuraAyah import com.quran.data.model.highlight.HighlightInfo import com.quran.data.model.highlight.HighlightType import com.quran.data.model.selection.SelectionIndicator -import com.quran.labs.androidquran.common.LocalTranslation import com.quran.labs.androidquran.common.QuranAyahInfo +import com.quran.mobile.translation.model.LocalTranslation import com.quran.page.common.data.AyahCoordinates import com.quran.page.common.data.PageCoordinates diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTrackerPresenter.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTrackerPresenter.kt index 38763f56fb..eba86fea10 100644 --- a/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTrackerPresenter.kt +++ b/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTrackerPresenter.kt @@ -16,7 +16,6 @@ import com.quran.data.model.highlight.HighlightType import com.quran.data.model.selection.AyahSelection import com.quran.data.model.selection.SelectionIndicator import com.quran.data.model.selection.startSuraAyah -import com.quran.labs.androidquran.common.LocalTranslation import com.quran.labs.androidquran.common.QuranAyahInfo import com.quran.labs.androidquran.data.QuranDisplayData import com.quran.labs.androidquran.data.SuraAyahIterator @@ -32,6 +31,7 @@ import com.quran.labs.androidquran.ui.helpers.HighlightTypes import com.quran.labs.androidquran.util.QuranFileUtils import com.quran.labs.androidquran.util.QuranSettings import com.quran.mobile.bookmark.model.BookmarkModel +import com.quran.mobile.translation.model.LocalTranslation import com.quran.page.common.data.AyahCoordinates import com.quran.page.common.data.PageCoordinates import com.quran.reading.common.AudioEventPresenter diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTranslationTrackerItem.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTranslationTrackerItem.kt index 170d05447d..f18e67f09b 100644 --- a/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTranslationTrackerItem.kt +++ b/app/src/main/java/com/quran/labs/androidquran/presenter/quran/ayahtracker/AyahTranslationTrackerItem.kt @@ -4,7 +4,7 @@ import com.quran.data.core.QuranInfo import com.quran.data.model.SuraAyah import com.quran.data.model.highlight.HighlightType import com.quran.data.model.selection.SelectionIndicator -import com.quran.labs.androidquran.common.LocalTranslation +import com.quran.mobile.translation.model.LocalTranslation import com.quran.labs.androidquran.common.QuranAyahInfo import com.quran.labs.androidquran.ui.translation.TranslationView diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/translation/BaseTranslationPresenter.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/translation/BaseTranslationPresenter.kt index 0fd2229669..d21637b747 100644 --- a/app/src/main/java/com/quran/labs/androidquran/presenter/translation/BaseTranslationPresenter.kt +++ b/app/src/main/java/com/quran/labs/androidquran/presenter/translation/BaseTranslationPresenter.kt @@ -5,7 +5,6 @@ import com.quran.data.model.QuranText import com.quran.data.model.SuraAyah import com.quran.data.model.SuraAyahIterator import com.quran.data.model.VerseRange -import com.quran.labs.androidquran.common.LocalTranslation import com.quran.labs.androidquran.common.LocalTranslationDisplaySort import com.quran.labs.androidquran.common.QuranAyahInfo import com.quran.labs.androidquran.common.TranslationMetadata @@ -14,6 +13,7 @@ import com.quran.labs.androidquran.model.translation.TranslationModel import com.quran.labs.androidquran.presenter.Presenter import com.quran.labs.androidquran.util.QuranSettings import com.quran.labs.androidquran.util.TranslationUtil +import com.quran.mobile.translation.model.LocalTranslation import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single @@ -27,7 +27,6 @@ internal open class BaseTranslationPresenter internal constructor( private val translationUtil: TranslationUtil, private val quranInfo: QuranInfo ) : Presenter { - private var lastCacheTime: Long = 0 private val translationMap: MutableMap = HashMap() var translationScreen: T? = null @@ -38,8 +37,7 @@ internal open class BaseTranslationPresenter internal constructor( verseRange: VerseRange ): Single { - val translations = translationsAdapter.getTranslations() - + val translations = translationsAdapter.legacyGetTranslations() val sortedTranslations: List = ArrayList(translations) Collections.sort(sortedTranslations, LocalTranslationDisplaySort()) @@ -51,26 +49,27 @@ internal open class BaseTranslationPresenter internal constructor( val source = Observable.fromIterable(orderedTranslationsFileNames) val translationsObservable = - source.concatMapEager { db -> - translationModel.getTranslationFromDatabase(verseRange, db) - .map { texts -> ensureProperTranslations(verseRange, texts) } - .onErrorReturnItem(ArrayList()) - .toObservable() - } - .toList() + source.concatMapEager { db -> + translationModel.getTranslationFromDatabase(verseRange, db) + .map { texts -> ensureProperTranslations(verseRange, texts) } + .onErrorReturnItem(ArrayList()) + .toObservable() + } + .toList() val arabicObservable = if (!getArabic) Single.just(ArrayList()) else translationModel.getArabicFromDatabase(verseRange).onErrorReturnItem(ArrayList()) - return Single.zip(arabicObservable, translationsObservable, getTranslationMapSingle(), - { arabic: List, - texts: List>, - map: Map -> - val translationInfos = getTranslations(orderedTranslationsFileNames, map) - val ayahInfo = combineAyahData(verseRange, arabic, texts, translationInfos) - ResultHolder(translationInfos, ayahInfo) - }) - .subscribeOn(Schedulers.io()) + return Single.zip( + arabicObservable, translationsObservable, getTranslationMapSingle() + ) { arabic: List, + texts: List>, + map: Map -> + val translationInfos = getTranslations(orderedTranslationsFileNames, map) + val ayahInfo = combineAyahData(verseRange, arabic, texts, translationInfos) + ResultHolder(translationInfos, ayahInfo) + } + .subscribeOn(Schedulers.io()) } fun getTranslations(quranSettings: QuranSettings): List { @@ -115,9 +114,9 @@ internal open class BaseTranslationPresenter internal constructor( translationMinVersion >= TranslationUtil.MINIMUM_PROCESSING_VERSION val text = quranText ?: QuranText(element.sura, element.ayah, "") if (shouldProcess) { - translationUtil.parseTranslationText(text, translationId) + translationUtil.parseTranslationText(text, translationId.toInt()) } else { - TranslationMetadata(element.sura, element.ayah, text.text, translationId) + TranslationMetadata(element.sura, element.ayah, text.text, translationId.toInt()) } } @@ -176,14 +175,12 @@ internal open class BaseTranslationPresenter internal constructor( } private fun getTranslationMapSingle(): Single> { - return if (this.translationMap.isEmpty() || - this.lastCacheTime != translationsAdapter.lastWriteTime) { - Single.fromCallable { translationsAdapter.getTranslations() } + return if (this.translationMap.isEmpty()) { + Single.fromCallable { translationsAdapter.legacyGetTranslations() } .map { translations -> translations.associateBy { it.filename } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess { map -> - this.lastCacheTime = translationsAdapter.lastWriteTime this.translationMap.clear() this.translationMap.putAll(map) } diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/translation/InlineTranslationPresenter.java b/app/src/main/java/com/quran/labs/androidquran/presenter/translation/InlineTranslationPresenter.java index 07fbaea239..ed82443041 100644 --- a/app/src/main/java/com/quran/labs/androidquran/presenter/translation/InlineTranslationPresenter.java +++ b/app/src/main/java/com/quran/labs/androidquran/presenter/translation/InlineTranslationPresenter.java @@ -1,19 +1,20 @@ package com.quran.labs.androidquran.presenter.translation; +import androidx.annotation.NonNull; + import com.quran.data.core.QuranInfo; -import com.quran.labs.androidquran.common.LocalTranslation; -import com.quran.labs.androidquran.common.QuranAyahInfo; import com.quran.data.model.VerseRange; +import com.quran.labs.androidquran.common.QuranAyahInfo; import com.quran.labs.androidquran.database.TranslationsDBAdapter; import com.quran.labs.androidquran.model.translation.TranslationModel; import com.quran.labs.androidquran.util.QuranSettings; import com.quran.labs.androidquran.util.TranslationUtil; +import com.quran.mobile.translation.model.LocalTranslation; import java.util.List; import javax.inject.Inject; -import androidx.annotation.NonNull; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.observers.DisposableSingleObserver; diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/translation/TranslationManagerPresenter.java b/app/src/main/java/com/quran/labs/androidquran/presenter/translation/TranslationManagerPresenter.java index 74e16c69be..f301894aea 100644 --- a/app/src/main/java/com/quran/labs/androidquran/presenter/translation/TranslationManagerPresenter.java +++ b/app/src/main/java/com/quran/labs/androidquran/presenter/translation/TranslationManagerPresenter.java @@ -7,7 +7,6 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.dao.translation.Translation; import com.quran.labs.androidquran.dao.translation.TranslationItem; import com.quran.labs.androidquran.dao.translation.TranslationList; @@ -19,6 +18,7 @@ import com.quran.labs.androidquran.util.QuranFileUtils; import com.quran.labs.androidquran.util.QuranSettings; import com.quran.mobile.di.qualifier.ApplicationContext; +import com.quran.mobile.translation.model.LocalTranslation; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; @@ -140,16 +140,16 @@ public void updateItem(final TranslationItem item) { // old file needs to be removed from the database explicitly final Translation translation = item.getTranslation(); if (translation.getMinimumVersion() >= 5) { - translationsDBAdapter.deleteTranslationByFile(translation.getFileName()); + translationsDBAdapter.legacyDeleteTranslationByFileName(translation.getFileName()); } - return translationsDBAdapter.writeTranslationUpdates(Collections.singletonList(item)); + return translationsDBAdapter.legacyWriteTranslationUpdates(Collections.singletonList(item)); } ).subscribeOn(Schedulers.io()) .subscribe(); } public void updateItemOrdering(final List items) { - Observable.fromCallable(() -> translationsDBAdapter.writeTranslationUpdates(items)) + Observable.fromCallable(() -> translationsDBAdapter.legacyWriteTranslationUpdates(items)) .subscribeOn(Schedulers.io()) .subscribe(); } @@ -247,6 +247,7 @@ private List mergeWithServerTranslations(List serv TranslationItem override = null; if (exists) { if (local == null) { + // text version, schema version final Pair versions = getVersionFromDatabase(translation.getFileName()); item = new TranslationItem(translation, versions.first); if (versions.second != translation.getMinimumVersion()) { @@ -272,7 +273,7 @@ private List mergeWithServerTranslations(List serv // certain schema changes, especially those going to v5, keep the same filename while // changing the database entry id. this could cause duplicate entries in the database. // work around it by removing the existing entries before doing the updates. - translationsDBAdapter.deleteTranslationByFile(override.getTranslation().getFileName()); + translationsDBAdapter.legacyDeleteTranslationByFileName(override.getTranslation().getFileName()); } updates.add(override == null ? item : override); } else if (local != null && local.getLanguageCode() == null) { @@ -283,7 +284,7 @@ private List mergeWithServerTranslations(List serv } if (!updates.isEmpty()) { - translationsDBAdapter.writeTranslationUpdates(updates); + translationsDBAdapter.legacyWriteTranslationUpdates(updates); } return results; } diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/translation/TranslationPresenter.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/translation/TranslationPresenter.kt index e0cd8c6747..8b7f3a9b95 100644 --- a/app/src/main/java/com/quran/labs/androidquran/presenter/translation/TranslationPresenter.kt +++ b/app/src/main/java/com/quran/labs/androidquran/presenter/translation/TranslationPresenter.kt @@ -2,14 +2,14 @@ package com.quran.labs.androidquran.presenter.translation import com.quran.data.core.QuranInfo import com.quran.data.di.QuranPageScope -import com.quran.labs.androidquran.common.LocalTranslation import com.quran.labs.androidquran.common.QuranAyahInfo import com.quran.labs.androidquran.database.TranslationsDBAdapter import com.quran.labs.androidquran.model.translation.TranslationModel import com.quran.labs.androidquran.util.QuranSettings import com.quran.labs.androidquran.util.TranslationUtil -import io.reactivex.rxjava3.core.Observable +import com.quran.mobile.translation.model.LocalTranslation import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.observers.DisposableObserver import javax.inject.Inject diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/translationlist/TranslationListCallback.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/translationlist/TranslationListCallback.kt new file mode 100644 index 0000000000..0641ca4ffd --- /dev/null +++ b/app/src/main/java/com/quran/labs/androidquran/presenter/translationlist/TranslationListCallback.kt @@ -0,0 +1,10 @@ +package com.quran.labs.androidquran.presenter.translationlist + +import com.quran.mobile.translation.model.LocalTranslation + +interface TranslationListCallback { + fun onTranslationsUpdated( + titles: Array, + translations: List + ) +} diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/translationlist/TranslationListPresenter.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/translationlist/TranslationListPresenter.kt new file mode 100644 index 0000000000..d47dd4a6c3 --- /dev/null +++ b/app/src/main/java/com/quran/labs/androidquran/presenter/translationlist/TranslationListPresenter.kt @@ -0,0 +1,22 @@ +package com.quran.labs.androidquran.presenter.translationlist + +import com.quran.mobile.translation.data.TranslationsDataSource +import com.quran.mobile.translation.model.LocalTranslation +import kotlinx.coroutines.Job +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +class TranslationListPresenter @Inject constructor( + private val dataSource: TranslationsDataSource +) { + fun translations(): Flow> { + return dataSource.translations() + .filterNotNull() + .map { translations -> translations.sortedBy { it.displayOrder } } + } +} diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java index 155a4dbb18..8de8d34960 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java +++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java @@ -68,7 +68,6 @@ import com.quran.labs.androidquran.SearchActivity; import com.quran.labs.androidquran.bridge.AudioEventPresenterBridge; import com.quran.labs.androidquran.bridge.ReadingEventPresenterBridge; -import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.common.LocalTranslationDisplaySort; import com.quran.labs.androidquran.common.QuranAyahInfo; import com.quran.labs.androidquran.common.audio.model.QariItem; @@ -123,6 +122,7 @@ import com.quran.mobile.di.QuranReadingPageComponentProvider; import com.quran.mobile.feature.qarilist.QariListWrapper; import com.quran.mobile.feature.qarilist.di.QariListWrapperInjector; +import com.quran.mobile.translation.model.LocalTranslation; import com.quran.page.common.factory.PageViewFactoryProvider; import com.quran.page.common.toolbar.AyahToolBar; import com.quran.page.common.toolbar.di.AyahToolBarInjector; @@ -1374,7 +1374,7 @@ private void ensurePage(int sura, int ayah) { private void requestTranslationsList() { compositeDisposable.add( Single.fromCallable(() -> - translationsDBAdapter.getTranslations()) + translationsDBAdapter.legacyGetTranslations()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(new DisposableSingleObserver>() { diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/TranslationManagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/TranslationManagerActivity.java index 3136b1e8bf..60f51e2648 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/TranslationManagerActivity.java +++ b/app/src/main/java/com/quran/labs/androidquran/ui/TranslationManagerActivity.java @@ -162,14 +162,15 @@ public void handleDownloadSuccess() { } } - List sortedItems = sortedDownloadedItems(); - int lastDisplayOrder = sortedItems.isEmpty() ? 1 : - sortedItems.get(sortedItems.size() - 1).getDisplayOrder(); - + // TODO: we can avoid the cost of sorting once we can listen to db updates + // in which case we'd set the local version as -1 so it gets properly assigned after. + final List sortedItems = sortedDownloadedItems(); + int lastDisplayOrder = sortedItems.isEmpty() ? 0 : + sortedItems.get(sortedItems.size() - 1).getDisplayOrder(); final Translation translation = downloadingItem.getTranslation(); - TranslationItem updated = new TranslationItem(translation, - translation.getCurrentVersion(), lastDisplayOrder + 1); - updateTranslationItem(updated); + updateTranslationItem(downloadingItem.withLocalVersionAndDisplayOrder( + translation.getCurrentVersion(), lastDisplayOrder + 1) + ); // update active translations and add this item to it QuranSettings settings = QuranSettings.getInstance(this); diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/fragment/AyahTranslationFragment.kt b/app/src/main/java/com/quran/labs/androidquran/ui/fragment/AyahTranslationFragment.kt index 4f3a80f7a8..452105050e 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/fragment/AyahTranslationFragment.kt +++ b/app/src/main/java/com/quran/labs/androidquran/ui/fragment/AyahTranslationFragment.kt @@ -1,28 +1,28 @@ package com.quran.labs.androidquran.ui.fragment -import com.quran.labs.androidquran.presenter.translation.InlineTranslationPresenter -import android.widget.ProgressBar -import com.quran.labs.androidquran.view.InlineTranslationView -import com.quran.labs.androidquran.view.QuranSpinner -import com.quran.labs.androidquran.ui.util.TranslationsSpinnerAdapter -import javax.inject.Inject -import com.quran.data.core.QuranInfo -import com.quran.labs.androidquran.util.QuranSettings -import com.quran.labs.androidquran.ui.PagerActivity -import android.view.LayoutInflater -import android.view.ViewGroup -import android.os.Bundle -import com.quran.labs.androidquran.R import android.app.Activity import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import android.widget.Button -import com.quran.labs.androidquran.common.LocalTranslation +import android.widget.ProgressBar +import com.quran.data.core.QuranInfo import com.quran.data.model.VerseRange +import com.quran.labs.androidquran.R import com.quran.labs.androidquran.common.QuranAyahInfo +import com.quran.labs.androidquran.presenter.translation.InlineTranslationPresenter import com.quran.labs.androidquran.presenter.translation.InlineTranslationPresenter.TranslationScreen +import com.quran.labs.androidquran.ui.PagerActivity import com.quran.labs.androidquran.ui.helpers.SlidingPagerAdapter +import com.quran.labs.androidquran.ui.util.TranslationsSpinnerAdapter +import com.quran.labs.androidquran.util.QuranSettings +import com.quran.labs.androidquran.view.InlineTranslationView +import com.quran.labs.androidquran.view.QuranSpinner import com.quran.mobile.di.AyahActionFragmentProvider +import com.quran.mobile.translation.model.LocalTranslation +import javax.inject.Inject import kotlin.math.abs class AyahTranslationFragment : AyahActionFragment(), TranslationScreen { diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/fragment/TabletFragment.java b/app/src/main/java/com/quran/labs/androidquran/ui/fragment/TabletFragment.java index c3dd63f5c9..3a57d8347b 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/fragment/TabletFragment.java +++ b/app/src/main/java/com/quran/labs/androidquran/ui/fragment/TabletFragment.java @@ -18,7 +18,6 @@ import com.quran.data.core.QuranInfo; import com.quran.data.model.SuraAyah; import com.quran.data.model.selection.AyahSelection; -import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.common.QuranAyahInfo; import com.quran.labs.androidquran.data.QuranDisplayData; import com.quran.labs.androidquran.presenter.quran.QuranPagePresenter; @@ -42,6 +41,7 @@ import com.quran.labs.androidquran.view.QuranImagePageLayout; import com.quran.labs.androidquran.view.QuranTranslationPageLayout; import com.quran.labs.androidquran.view.TabletView; +import com.quran.mobile.translation.model.LocalTranslation; import com.quran.page.common.data.AyahCoordinates; import com.quran.page.common.data.PageCoordinates; import com.quran.page.common.draw.ImageDrawHelper; diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/fragment/TranslationFragment.java b/app/src/main/java/com/quran/labs/androidquran/ui/fragment/TranslationFragment.java index 6918e434fe..5227d9f671 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/fragment/TranslationFragment.java +++ b/app/src/main/java/com/quran/labs/androidquran/ui/fragment/TranslationFragment.java @@ -14,7 +14,6 @@ import com.quran.data.core.QuranInfo; import com.quran.data.model.SuraAyah; import com.quran.data.model.selection.AyahSelection; -import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.common.QuranAyahInfo; import com.quran.labs.androidquran.data.QuranDisplayData; import com.quran.labs.androidquran.presenter.quran.ayahtracker.AyahTrackerItem; @@ -29,6 +28,7 @@ import com.quran.labs.androidquran.ui.util.PageController; import com.quran.labs.androidquran.util.QuranSettings; import com.quran.labs.androidquran.view.QuranTranslationPageLayout; +import com.quran.mobile.translation.model.LocalTranslation; import com.quran.reading.common.ReadingEventPresenter; import java.util.List; diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/helpers/AyahTracker.kt b/app/src/main/java/com/quran/labs/androidquran/ui/helpers/AyahTracker.kt index e22db2ede3..ef648a02f4 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/helpers/AyahTracker.kt +++ b/app/src/main/java/com/quran/labs/androidquran/ui/helpers/AyahTracker.kt @@ -1,8 +1,8 @@ package com.quran.labs.androidquran.ui.helpers import com.quran.data.model.selection.SelectionIndicator -import com.quran.labs.androidquran.common.LocalTranslation import com.quran.labs.androidquran.common.QuranAyahInfo +import com.quran.mobile.translation.model.LocalTranslation interface AyahTracker { fun getToolBarPosition(sura: Int, ayah: Int): SelectionIndicator diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/translation/TranslationView.java b/app/src/main/java/com/quran/labs/androidquran/ui/translation/TranslationView.java index e7edc5ebf4..2bdf9b149d 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/translation/TranslationView.java +++ b/app/src/main/java/com/quran/labs/androidquran/ui/translation/TranslationView.java @@ -18,7 +18,6 @@ import com.quran.data.model.SuraAyah; import com.quran.data.model.highlight.HighlightType; import com.quran.data.model.selection.SelectionIndicator; -import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.common.LocalTranslationDisplaySort; import com.quran.labs.androidquran.common.QuranAyahInfo; import com.quran.labs.androidquran.common.TranslationMetadata; @@ -27,6 +26,8 @@ import com.quran.labs.androidquran.ui.helpers.HighlightTypes; import com.quran.labs.androidquran.ui.util.PageController; import com.quran.labs.androidquran.util.QuranSettings; +import com.quran.mobile.translation.model.LocalTranslation; + import dev.chrisbanes.insetter.Insetter; import java.util.ArrayList; import java.util.Arrays; @@ -137,7 +138,7 @@ public void setVerses(@NonNull QuranDisplayData quranDisplayData, Arrays.sort(sortedTranslations, new LocalTranslationDisplaySort()); for (int j = 0; j < sortedTranslations.length; j++) { - final TranslationMetadata metadata = findText(verse.texts, sortedTranslations[j].getId()); + final TranslationMetadata metadata = findText(verse.texts, (int) sortedTranslations[j].getId()); CharSequence text = metadata != null ? metadata.getText() : ""; if (!TextUtils.isEmpty(text)) { if (wantTranslationHeaders) { diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/util/TranslationsSpinnerAdapter.java b/app/src/main/java/com/quran/labs/androidquran/ui/util/TranslationsSpinnerAdapter.java index 41b3f89d44..bcd71fc881 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/util/TranslationsSpinnerAdapter.java +++ b/app/src/main/java/com/quran/labs/androidquran/ui/util/TranslationsSpinnerAdapter.java @@ -2,7 +2,6 @@ import android.content.Context; import android.content.res.Resources; -import androidx.annotation.NonNull; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; @@ -11,9 +10,11 @@ import android.widget.CheckBox; import android.widget.TextView; +import androidx.annotation.NonNull; + import com.quran.labs.androidquran.R; -import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.ui.PagerActivity; +import com.quran.mobile.translation.model.LocalTranslation; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/quran/labs/androidquran/util/ShareUtil.kt b/app/src/main/java/com/quran/labs/androidquran/util/ShareUtil.kt index 592dd8326e..811567969f 100644 --- a/app/src/main/java/com/quran/labs/androidquran/util/ShareUtil.kt +++ b/app/src/main/java/com/quran/labs/androidquran/util/ShareUtil.kt @@ -11,12 +11,11 @@ import android.widget.Toast import androidx.annotation.StringRes import com.quran.data.model.QuranText import com.quran.labs.androidquran.R -import com.quran.labs.androidquran.common.LocalTranslation import com.quran.labs.androidquran.common.QuranAyahInfo import com.quran.labs.androidquran.data.QuranDisplayData import com.quran.labs.androidquran.model.translation.ArabicDatabaseUtils import com.quran.labs.androidquran.ui.util.ToastCompat -import com.quran.labs.androidquran.ui.util.TypefaceManager +import com.quran.mobile.translation.model.LocalTranslation import dagger.Reusable import java.text.NumberFormat import java.util.Locale diff --git a/app/src/main/java/com/quran/labs/androidquran/view/InlineTranslationView.kt b/app/src/main/java/com/quran/labs/androidquran/view/InlineTranslationView.kt index 2c4b225e72..6fd1e06fac 100644 --- a/app/src/main/java/com/quran/labs/androidquran/view/InlineTranslationView.kt +++ b/app/src/main/java/com/quran/labs/androidquran/view/InlineTranslationView.kt @@ -10,7 +10,6 @@ import android.text.TextUtils import android.text.style.ForegroundColorSpan import android.text.style.RelativeSizeSpan import android.text.style.StyleSpan -import android.text.style.TypefaceSpan import android.util.AttributeSet import android.view.View import android.widget.LinearLayout @@ -18,15 +17,13 @@ import android.widget.ScrollView import android.widget.TextView import androidx.annotation.StyleRes import androidx.core.content.ContextCompat -import com.quran.common.search.SearchTextUtil import com.quran.labs.androidquran.R -import com.quran.labs.androidquran.common.LocalTranslation import com.quran.labs.androidquran.common.QuranAyahInfo import com.quran.labs.androidquran.common.TranslationMetadata import com.quran.labs.androidquran.ui.helpers.TypefaceWrappingSpan -import com.quran.labs.androidquran.ui.translation.TranslationAdapter import com.quran.labs.androidquran.ui.util.TypefaceManager import com.quran.labs.androidquran.util.QuranSettings +import com.quran.mobile.translation.model.LocalTranslation class InlineTranslationView @JvmOverloads constructor( context: Context, diff --git a/app/src/test/java/com/quran/labs/androidquran/presenter/translation/BaseTranslationPresenterTest.kt b/app/src/test/java/com/quran/labs/androidquran/presenter/translation/BaseTranslationPresenterTest.kt index 6033a99a08..1d2efaa0f3 100644 --- a/app/src/test/java/com/quran/labs/androidquran/presenter/translation/BaseTranslationPresenterTest.kt +++ b/app/src/test/java/com/quran/labs/androidquran/presenter/translation/BaseTranslationPresenterTest.kt @@ -5,12 +5,12 @@ import com.quran.data.core.QuranInfo import com.quran.data.model.QuranText import com.quran.data.model.VerseRange import com.quran.data.pageinfo.common.MadaniDataSource -import com.quran.labs.androidquran.common.LocalTranslation import com.quran.labs.androidquran.common.TranslationMetadata import com.quran.labs.androidquran.database.TranslationsDBAdapter import com.quran.labs.androidquran.model.translation.TranslationModel import com.quran.labs.androidquran.presenter.Presenter import com.quran.labs.androidquran.util.TranslationUtil +import com.quran.mobile.translation.model.LocalTranslation import org.junit.Before import org.junit.Test import org.mockito.Mockito @@ -53,7 +53,7 @@ class BaseTranslationPresenterTest { @Test fun testHashlessGetTranslationNames() { val databases = listOf("one.db", "two.db") - val map = HashMap() + val map = HashMap() val translations = presenter.getTranslations(databases, map) assertThat(translations).hasLength(2) diff --git a/common/translation/build.gradle.kts b/common/translation/build.gradle.kts new file mode 100644 index 0000000000..fcff42da2f --- /dev/null +++ b/common/translation/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("quran.android.library.android") + id("app.cash.sqldelight") + id("com.squareup.anvil") +} + +anvil { generateDaggerFactories = true } + +android.namespace = "com.quran.mobile.translation" + +sqldelight { + databases { + create("TranslationsDatabase") { + packageName.set("com.quran.mobile.translation.data") + schemaOutputDirectory.set(file("src/main/sqldelight/databases")) + verifyMigrations.set(true) + generateAsync.set(true) + } + } +} + +dependencies { + implementation(project(":common:di")) + implementation(project(":common:data")) + + // dagger + implementation(libs.dagger.runtime) + + // coroutines + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.coroutines.android) + + // sqldelight + implementation(libs.sqldelight.android.driver) + implementation(libs.sqldelight.coroutines.extensions) + implementation(libs.sqldelight.primitive.adapters) +} diff --git a/common/translation/src/main/kotlin/com/quran/mobile/translation/data/TranslationsDataSource.kt b/common/translation/src/main/kotlin/com/quran/mobile/translation/data/TranslationsDataSource.kt new file mode 100644 index 0000000000..9c77d3fb6c --- /dev/null +++ b/common/translation/src/main/kotlin/com/quran/mobile/translation/data/TranslationsDataSource.kt @@ -0,0 +1,64 @@ +package com.quran.mobile.translation.data + +import app.cash.sqldelight.async.coroutines.awaitAsOne +import app.cash.sqldelight.coroutines.asFlow +import app.cash.sqldelight.coroutines.mapToList +import com.quran.mobile.translation.mapper.LocalTranslationMapper +import com.quran.mobile.translation.model.LocalTranslation +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TranslationsDataSource @Inject constructor(translationsDatabase: TranslationsDatabase) { + private val translationsQueries = translationsDatabase.translationsQueries + + private val scope = MainScope() + private val translations by lazy { + translationsQueries.all(LocalTranslationMapper.mapper) + .asFlow() + .mapToList(Dispatchers.IO) + .stateIn(scope, SharingStarted.Lazily, null) + } + + fun translations(): StateFlow?> = translations + + suspend fun updateTranslations(items: List) { + translationsQueries.transaction { + items.forEach { + translationsQueries.update( + id = it.id, + name = it.name, + translator = it.translator, + translatorForeign = it.translatorForeign, + filename = it.filename, + url = it.url, + languageCode = it.languageCode, + version = it.version.toLong(), + minimumRequiredVersion = it.minimumVersion.toLong(), + userDisplayOrder = it.displayOrder.toLong() + ) + } + } + } + + suspend fun removeTranslation(filename: String) { + translationsQueries.deleteByFileName(filename) + } + + suspend fun removeTranslationsById(ids: List) { + translationsQueries.transaction { + ids.forEach { + translationsQueries.deleteById(it) + } + } + } + + suspend fun maximumDisplayOrder(): Long { + return translationsQueries.greatestDisplayOrder().awaitAsOne().MAX ?: 0 + } +} diff --git a/common/translation/src/main/kotlin/com/quran/mobile/translation/di/TranslationDataModule.kt b/common/translation/src/main/kotlin/com/quran/mobile/translation/di/TranslationDataModule.kt new file mode 100644 index 0000000000..a922400461 --- /dev/null +++ b/common/translation/src/main/kotlin/com/quran/mobile/translation/di/TranslationDataModule.kt @@ -0,0 +1,29 @@ +package com.quran.mobile.translation.di + +import android.content.Context +import app.cash.sqldelight.async.coroutines.synchronous +import app.cash.sqldelight.driver.android.AndroidSqliteDriver +import com.quran.data.di.AppScope +import com.quran.mobile.di.qualifier.ApplicationContext +import com.quran.mobile.translation.data.TranslationsDatabase +import com.squareup.anvil.annotations.ContributesTo +import dagger.Module +import dagger.Provides +import javax.inject.Singleton + +@Module +@ContributesTo(AppScope::class) +class TranslationDataModule { + + @Singleton + @Provides + fun provideTranslationDatabase(@ApplicationContext context: Context): TranslationsDatabase { + return TranslationsDatabase( + AndroidSqliteDriver( + schema = TranslationsDatabase.Schema.synchronous(), + context = context, + name = "translations.db" + ) + ) + } +} diff --git a/common/translation/src/main/kotlin/com/quran/mobile/translation/mapper/LocalTranslationMapper.kt b/common/translation/src/main/kotlin/com/quran/mobile/translation/mapper/LocalTranslationMapper.kt new file mode 100644 index 0000000000..e75996a5ab --- /dev/null +++ b/common/translation/src/main/kotlin/com/quran/mobile/translation/mapper/LocalTranslationMapper.kt @@ -0,0 +1,33 @@ +package com.quran.mobile.translation.mapper + +import com.quran.mobile.translation.model.LocalTranslation + +object LocalTranslationMapper { + + val mapper: (( + id: Long, + name: String, + translator: String?, + translatorForeign: String?, + filename: String, + url: String, + languageCode: String?, + version: Long, + minimumRequiredVersion: Long, + userDisplayOrder: Long, + ) -> LocalTranslation) = + { id, name, translator, translatorForeign, filename, url, languageCode, version, minimumRequiredVersion, displayOrder -> + LocalTranslation( + id = id, + name = name, + translator = translator, + translatorForeign = translatorForeign, + filename = filename, + url = url, + languageCode = languageCode, + version = version.toInt(), + minimumVersion = minimumRequiredVersion.toInt(), + displayOrder = displayOrder.toInt(), + ) + } +} diff --git a/app/src/main/java/com/quran/labs/androidquran/common/LocalTranslation.kt b/common/translation/src/main/kotlin/com/quran/mobile/translation/model/LocalTranslation.kt similarity index 89% rename from app/src/main/java/com/quran/labs/androidquran/common/LocalTranslation.kt rename to common/translation/src/main/kotlin/com/quran/mobile/translation/model/LocalTranslation.kt index d541bf54f5..d567e02266 100644 --- a/app/src/main/java/com/quran/labs/androidquran/common/LocalTranslation.kt +++ b/common/translation/src/main/kotlin/com/quran/mobile/translation/model/LocalTranslation.kt @@ -1,7 +1,7 @@ -package com.quran.labs.androidquran.common +package com.quran.mobile.translation.model data class LocalTranslation( - val id: Int = -1, + val id: Long = -1, val filename: String, val name: String = "", val translator: String? = "", diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/1.db b/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/1.db new file mode 100644 index 0000000000000000000000000000000000000000..38d0fcbe1b96d6609a1c88f2f228cbe47065e9df GIT binary patch literal 8192 zcmeI#QES355C`x?27(~&ZLjw*VZs=LeSuXMMV9GSBdm`ZMkv%4(zy5eP5h=_1{Glg zU&r_#J<`jCYx%up@uu_OXkGb@4fG%`g_PovNC+W0TbxbA`l2a#jrF;Z;?D28d`kAN z1wRM~KmY;|fB*y_009U<00Izzz@HWP%4GYh-upfmB-j;P?Ltd5j>3eIGs>CQV z$xtgg?a)n!C|}WZX4FKb^qNjzk~E`dmECfuv>Qi8y&Ia%IhR_;-DW`Lzk6|Oofq!+ z!&_hcobc&<%|ChkY%`sn)HqpcL-(CQyqoi-9{~XfKmY;|fB*y_009U<00Izzz&{o^ E0O(LjYXATM literal 0 HcmV?d00001 diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/2.db b/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/2.db new file mode 100644 index 0000000000000000000000000000000000000000..425d1490681708f114bd890d6e46a6574d1a2dc9 GIT binary patch literal 8192 zcmeI#%}N6?5C`z22*QHs&FfqW7DR-;fU9PuG`4n|1$&H`N?<=A8@-E9;+r}vY^hZ6 zSdjm)!|r6lZ1~->TxcI1t(#EUNMms#q!d>~LI}w@;OwKT5Bh@NUHw-`aS=X`c$Yj* z1V0D}KmY;|fB*y_009U<00Izzz@7!(TX}do9LkSTv`?jr!PaeQqi^bV`+IPctJEkm z>9tn0-J!FD$UoC$X4IX^=^>xor}>JeYIV+?+E$K?dNeefa~4{6tNnl`eD{7XWleDY zrQUSbzI4Bjc@5?6>^B!$KK|xF{eS6JWoe-eT_&UMZqAo}1Oy-e0SG_<0uX=z1Rwwb L2tWV=|5)G)_#9F7 literal 0 HcmV?d00001 diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/3.db b/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/3.db new file mode 100644 index 0000000000000000000000000000000000000000..76b6421a8432b80fd1ca10e82e20b12b46a9555f GIT binary patch literal 8192 zcmeI#!Ab)$5C-6+2uh*i&FfqW3L=6p;965Du2r`w*kh#W5=axsrapi^i4W=BMA=eN z=*@%t!w$QXS!T1}Et7}RL`@4HmMYRYJ7Ao%3nIoCPuWS?My_|-BHeSnEjT+3Z@u&u z-+yQ6LO=il5P$##AOHafKmY;|fB*#kPT*ya?;iI1{B;u5lhrXO*H{&ecg^N=XHbbk z3X;WTDQL4pM_nQ_r_or7Yf;gCHM%XT8QqB4N$NPY)Fj2Dr14$KX<6pgc7P9`z3)py zAGE36y0b7=|2}3F?9bU&xq78)J@E6dFX!pcIyC8@*1K#sLsw#0OiM{;-Q#>VPltX4 a1Rwwb2tWV=5P$##AOHafKmY>&Sl|PpRazqe literal 0 HcmV?d00001 diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/4.db b/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/4.db new file mode 100644 index 0000000000000000000000000000000000000000..8900301f84df99443291e6d0c984a79b2027ab2e GIT binary patch literal 8192 zcmeI#K}*9h6bJBR2*N<|=Jj0$3L=8%fpumwj8khS?iguS0&R+E>jx0Oh~LDIwnXWe zC=5Kx|IyICy!18w-Ew~?ZDVLr`BFD@%JvxN?2L#p#>41^(M8O+x+L6Vz9~36_U}7k z72o|};X*(F0uX=z1Rwwb2tWV=5P$##{v_}^4d`l&FD*RC#fV=Gs$Kletd zNR%LzoJ&FL6*}k>*^DMrB`!rux9Q|MN#}GW=0~C8bZJP5M@7?{FlJK5*{(t5KWpEo z#+5fVcdN?67UuUhtuKD|F0{*AotsgWeSJB0e=)ue|Fqg=w;8$+<76fkoy1k8b+&BF r)I7J=o9toZOZ~%mHV=n>1Oy-e0SG_<0uX=z1Rwwb2tWV=|5#uN_#tY6 literal 0 HcmV?d00001 diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/5.db b/common/translation/src/main/sqldelight/com/quran/mobile/translation/databases/5.db new file mode 100644 index 0000000000000000000000000000000000000000..5e91a4d4fcb3b688b4bd1f88571e913da97d8e52 GIT binary patch literal 8192 zcmeI#K}*9h6bJBR2*N<|=1F*$fr5(S7qD)b4C|&f6L*X>8-b?Pv=zJ#y!#dWrY4h) ziHe@(|7b{)m%gOGTW+pnQ)!wO&Z>$|*cRiQof0v|xNj}rnh4{yX5jlU{w+8=bZ;Ae z72kYi{y{(h0uX=z1Rwwb2tWV=5P$##76Nn2*S9+z{<2f4`%G6( zND7kCSuAKZL%VGvlhR-)#kol6Dj8fx$%HP%WZ!3UWi?50CuunHZ5+p8wkc4!Meg%d zzi`^j@?~afGW~s-+GSsBXDXl7YNoqI`muAU{-j;$|7m&3<}lO~{b(FZItr_-$&IaT zq91GHbb9-zkLpspo+%$P^*nN^c0ccNaNOPtcXNN~M?e4q5P$##AOHafKmY;|fB*y_ I@Q(%F0c*f`b^rhX literal 0 HcmV?d00001 diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/1.sqm b/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/1.sqm new file mode 100644 index 0000000000..29d9684960 --- /dev/null +++ b/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/1.sqm @@ -0,0 +1,26 @@ +CREATE TABLE IF NOT EXISTS translations ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + translator TEXT, + filename TEXT NOT NULL, + url TEXT NOT NULL, + version INTEGER NOT NULL DEFAULT 0 +); + +-- adds translatorForeign +CREATE TABLE translations_migration ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + translator TEXT, + translatorForeign TEXT, + filename TEXT NOT NULL, + url TEXT NOT NULL, + version INTEGER NOT NULL DEFAULT 0 +); + +INSERT INTO translations_migration +SELECT id, name, translator, "", filename, url, version +FROM translations; + +DROP TABLE translations; +ALTER TABLE translations_migration RENAME TO translations; diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/2.sqm b/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/2.sqm new file mode 100644 index 0000000000..69e50d0f6d --- /dev/null +++ b/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/2.sqm @@ -0,0 +1,18 @@ +-- adds languageCode +CREATE TABLE translations_migration ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + translator TEXT, + translatorForeign TEXT, + filename TEXT NOT NULL, + url TEXT NOT NULL, + languageCode TEXT, + version INTEGER NOT NULL DEFAULT 0 +); + +INSERT INTO translations_migration +SELECT id, name, translator, translatorForeign, filename, url, "", version +FROM translations; + +DROP TABLE translations; +ALTER TABLE translations_migration RENAME TO translations; diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/3.sqm b/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/3.sqm new file mode 100644 index 0000000000..311c357b41 --- /dev/null +++ b/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/3.sqm @@ -0,0 +1,19 @@ +-- adds minimumRequiredVersion +CREATE TABLE translations_migration ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + translator TEXT, + translatorForeign TEXT, + filename TEXT NOT NULL, + url TEXT NOT NULL, + languageCode TEXT, + version INTEGER NOT NULL DEFAULT 0, + minimumRequiredVersion INTEGER NOT NULL DEFAULT 0 +); + +INSERT INTO translations_migration +SELECT id, name, translator, translatorForeign, filename, url, languageCode, version, 2 +FROM translations; + +DROP TABLE translations; +ALTER TABLE translations_migration RENAME TO translations; diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/4.sqm b/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/4.sqm new file mode 100644 index 0000000000..da84f45122 --- /dev/null +++ b/common/translation/src/main/sqldelight/com/quran/mobile/translation/migrations/4.sqm @@ -0,0 +1,20 @@ +-- adds userDisplayOrder +CREATE TABLE translations_migration ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + translator TEXT, + translatorForeign TEXT, + filename TEXT NOT NULL, + url TEXT NOT NULL, + languageCode TEXT, + version INTEGER NOT NULL DEFAULT 0, + minimumRequiredVersion INTEGER NOT NULL DEFAULT 0, + userDisplayOrder INTEGER NOT NULL DEFAULT -1 +); + +INSERT INTO translations_migration +SELECT id, name, translator, translatorForeign, filename, url, languageCode, version, minimumRequiredVersion, id +FROM translations; + +DROP TABLE translations; +ALTER TABLE translations_migration RENAME TO translations; diff --git a/common/translation/src/main/sqldelight/com/quran/mobile/translation/translations.sq b/common/translation/src/main/sqldelight/com/quran/mobile/translation/translations.sq new file mode 100644 index 0000000000..4450734d17 --- /dev/null +++ b/common/translation/src/main/sqldelight/com/quran/mobile/translation/translations.sq @@ -0,0 +1,29 @@ +CREATE TABLE translations ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + translator TEXT, + translatorForeign TEXT, + filename TEXT NOT NULL, + url TEXT NOT NULL, + languageCode TEXT, + version INTEGER NOT NULL DEFAULT 0, + minimumRequiredVersion INTEGER NOT NULL DEFAULT 0, + userDisplayOrder INTEGER NOT NULL DEFAULT -1 +); + +all: +SELECT * FROM translations ORDER BY id ASC; + +update: +REPLACE INTO translations( + id, name, translator, translatorForeign, filename, url, languageCode, version, minimumRequiredVersion, userDisplayOrder) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + +greatestDisplayOrder: +SELECT MAX(userDisplayOrder) FROM translations; + +deleteById: +DELETE FROM translations WHERE id = ?; + +deleteByFileName: +DELETE FROM translations WHERE filename = ?; diff --git a/settings.gradle.kts b/settings.gradle.kts index 6fbbf78e96..a260c5eb2a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,6 +30,7 @@ include(":common:recitation") include(":common:preference") include(":common:search") include(":common:toolbar") +include(":common:translation") include(":common:upgrade") include(":common:ui:core") include(":feature:analytics-noop")