From 7cbcbad40fd31cfe0f783136c4fd0912e7fddb3d Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Wed, 27 Oct 2021 23:22:14 +0200 Subject: [PATCH 01/67] Add: TODOs for (5/5 in replacing SQLite with Room) --- .../com/looker/droidify/database/Database.kt | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index 61aef765..b514bfea 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -34,6 +34,7 @@ object Database { private lateinit var db: SQLiteDatabase + // not needed with Room private interface Table { val memory: Boolean val innerName: String @@ -61,6 +62,7 @@ object Database { } private object Schema { + // implemented object Repository : Table { const val ROW_ID = "_id" const val ROW_ENABLED = "enabled" @@ -77,6 +79,7 @@ object Database { """ } + // implemented object Product : Table { const val ROW_REPOSITORY_ID = "repository_id" const val ROW_PACKAGE_NAME = "package_name" @@ -111,6 +114,7 @@ object Database { override val createIndex = ROW_PACKAGE_NAME } + // implemented object Category : Table { const val ROW_REPOSITORY_ID = "repository_id" const val ROW_PACKAGE_NAME = "package_name" @@ -127,6 +131,7 @@ object Database { override val createIndex = "$ROW_PACKAGE_NAME, $ROW_NAME" } + // implemented object Installed : Table { const val ROW_PACKAGE_NAME = "package_name" const val ROW_VERSION = "version" @@ -143,6 +148,7 @@ object Database { """ } + // implemented object Lock : Table { const val ROW_PACKAGE_NAME = "package_name" const val ROW_VERSION_CODE = "version_code" @@ -155,12 +161,14 @@ object Database { """ } + // TODO find a class to include them as constants object Synthetic { const val ROW_CAN_UPDATE = "can_update" const val ROW_MATCH_RANK = "match_rank" } } + // not needed remove after migration private class Helper(context: Context) : SQLiteOpenHelper(context, "droidify", null, 1) { var created = false private set @@ -198,6 +206,7 @@ object Database { } } + // not needed remove after migration private fun handleTables(db: SQLiteDatabase, recreate: Boolean, vararg tables: Table): Boolean { val shouldRecreate = recreate || tables.any { val sql = db.query( @@ -220,6 +229,7 @@ object Database { } } + // not needed remove after migration private fun handleIndexes(db: SQLiteDatabase, vararg tables: Table) { val shouldVacuum = tables.map { val sqls = db.query( @@ -248,6 +258,7 @@ object Database { } } + // TODO not needed, remove after migration private fun dropOldTables(db: SQLiteDatabase, vararg neededTables: Table) { val tables = db.query( "sqlite_master", columns = arrayOf("name"), @@ -274,6 +285,8 @@ object Database { private val observers = mutableMapOf Unit>>() + + // TODO not needed remove after migration (replaced by LiveData) private fun dataObservable(subject: Subject): (Boolean, () -> Unit) -> Unit = { register, observer -> synchronized(observers) { @@ -290,6 +303,7 @@ object Database { } } + // TODO not needed remove after migration (replaced by LiveData) fun observable(subject: Subject): Observable { return Observable.create { val callback: () -> Unit = { it.onNext(Unit) } @@ -299,12 +313,14 @@ object Database { } } + // TODO not needed remove after migration (replaced by LiveData) private fun notifyChanged(vararg subjects: Subject) { synchronized(observers) { subjects.asSequence().mapNotNull { observers[it] }.flatten().forEach { it() } } } + // TODO Done through inserts/replace of DAOs, only temporary still not finished private fun SQLiteDatabase.insertOrReplace( replace: Boolean, table: String, @@ -350,6 +366,7 @@ object Database { return outputStream.toByteArray() } + // Partially done, only object RepositoryAdapter { // Done in put internal fun putWithoutNotification(repository: Repository, shouldReplace: Boolean): Long { @@ -498,7 +515,7 @@ object Database { .use { it.firstOrNull()?.getInt(0) ?: 0 } } - // Complex left to wiring phase + // TODO Too complex left to wiring phase fun query( installed: Boolean, updates: Boolean, searchQuery: String, section: ProductItem.Section, order: ProductItem.Order, signal: CancellationSignal? @@ -596,7 +613,7 @@ object Database { } } - // Unnecessary with Room + // TODO should be taken care of with extra functions that build on Room's queries fun transformItem(cursor: Cursor): ProductItem { return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA_ITEM)) .jsonParse { @@ -623,6 +640,7 @@ object Database { } } + // Done object CategoryAdapter { // Done fun getAll(signal: CancellationSignal?): Set { @@ -642,6 +660,7 @@ object Database { } } + // Done object InstalledAdapter { // Done fun get(packageName: String, signal: CancellationSignal?): InstalledItem? { @@ -707,6 +726,7 @@ object Database { } } + // Done with some changes in DAOS object LockAdapter { // Done in insert (Lock object instead of pair) private fun put(lock: Pair, notify: Boolean) { From 1ec70dd1a86916e09d5352824ee29d7cbb3def3e Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Wed, 27 Oct 2021 23:22:45 +0200 Subject: [PATCH 02/67] Update: Abstract part of the DAOs --- .../com/looker/droidify/database/DAOs.kt | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index d332a6a0..5cf589de 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -1,17 +1,20 @@ package com.looker.droidify.database -import android.database.SQLException import androidx.room.* -@Dao -interface RepositoryDao { +interface BaseDao { @Insert - @Throws(SQLException::class) - fun insert(vararg repository: Repository) + fun insert(vararg product: T) @Update(onConflict = OnConflictStrategy.REPLACE) - fun update(vararg repository: Repository?) + fun update(vararg obj: T) + @Delete + fun delete(obj: T) +} + +@Dao +interface RepositoryDao : BaseDao { fun put(repository: Repository) { if (repository.id >= 0L) update(repository) else insert(repository) } @@ -25,20 +28,18 @@ interface RepositoryDao { @get:Query("SELECT _id, deleted FROM repository WHERE deleted != 0 and enabled == 0 ORDER BY _id ASC") val allDisabledDeleted: List - @Delete - fun delete(repository: Repository) - @Query("DELETE FROM repository WHERE _id = :id") fun deleteById(vararg id: Long): Int + // TODO optimize @Update(onConflict = OnConflictStrategy.REPLACE) fun markAsDeleted(id: Long) { - update(get(id).apply { this?.deleted = 1 }) + get(id).apply { this?.deleted = 1 }?.let { update(it) } } } @Dao -interface ProductDao { +interface ProductDao : BaseDao { @Query("SELECT COUNT(*) FROM product WHERE repository_id = :id") fun countForRepository(id: Long): Long @@ -50,7 +51,7 @@ interface ProductDao { } @Dao -interface CategoryDao { +interface CategoryDao : BaseDao { @Query( """SELECT DISTINCT category.name FROM category AS category @@ -66,11 +67,7 @@ interface CategoryDao { } @Dao -interface InstalledDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - @Throws(SQLException::class) - fun insert(vararg installed: Installed) - +interface InstalledDao : BaseDao { @Query("SELECT * FROM installed WHERE package_name = :packageName") fun get(packageName: String): Installed? @@ -79,11 +76,7 @@ interface InstalledDao { } @Dao -interface LockDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - @Throws(SQLException::class) - fun insert(vararg lock: Lock) - +interface LockDao : BaseDao { @Query("DELETE FROM lock WHERE package_name = :packageName") fun delete(packageName: String) } \ No newline at end of file From 4b39477b36275a050eeb6a84084300a583e85689 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Thu, 28 Oct 2021 00:42:54 +0200 Subject: [PATCH 03/67] Update: Make Common a file --- src/main/kotlin/com/looker/droidify/Common.kt | 17 +++++++------- .../com/looker/droidify/MainApplication.kt | 6 ++--- .../droidify/service/DownloadService.kt | 21 ++++++++--------- .../looker/droidify/service/SyncService.kt | 23 ++++++++----------- 4 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/Common.kt b/src/main/kotlin/com/looker/droidify/Common.kt index a943ba42..d520b4d4 100644 --- a/src/main/kotlin/com/looker/droidify/Common.kt +++ b/src/main/kotlin/com/looker/droidify/Common.kt @@ -1,13 +1,12 @@ package com.looker.droidify -object Common { - const val NOTIFICATION_CHANNEL_SYNCING = "syncing" - const val NOTIFICATION_CHANNEL_UPDATES = "updates" - const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading" +const val NOTIFICATION_CHANNEL_SYNCING = "syncing" +const val NOTIFICATION_CHANNEL_UPDATES = "updates" +const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading" - const val NOTIFICATION_ID_SYNCING = 1 - const val NOTIFICATION_ID_UPDATES = 2 - const val NOTIFICATION_ID_DOWNLOADING = 3 +const val NOTIFICATION_ID_SYNCING = 1 +const val NOTIFICATION_ID_UPDATES = 2 +const val NOTIFICATION_ID_DOWNLOADING = 3 + +const val JOB_ID_SYNC = 1 - const val JOB_ID_SYNC = 1 -} diff --git a/src/main/kotlin/com/looker/droidify/MainApplication.kt b/src/main/kotlin/com/looker/droidify/MainApplication.kt index f332473e..f6c10af3 100644 --- a/src/main/kotlin/com/looker/droidify/MainApplication.kt +++ b/src/main/kotlin/com/looker/droidify/MainApplication.kt @@ -104,19 +104,19 @@ class MainApplication : Application(), ImageLoaderFactory { private fun updateSyncJob(force: Boolean) { val jobScheduler = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler - val reschedule = force || !jobScheduler.allPendingJobs.any { it.id == Common.JOB_ID_SYNC } + val reschedule = force || !jobScheduler.allPendingJobs.any { it.id == JOB_ID_SYNC } if (reschedule) { val autoSync = Preferences[Preferences.Key.AutoSync] when (autoSync) { Preferences.AutoSync.Never -> { - jobScheduler.cancel(Common.JOB_ID_SYNC) + jobScheduler.cancel(JOB_ID_SYNC) } Preferences.AutoSync.Wifi, Preferences.AutoSync.Always -> { val period = 12 * 60 * 60 * 1000L // 12 hours val wifiOnly = autoSync == Preferences.AutoSync.Wifi jobScheduler.schedule(JobInfo .Builder( - Common.JOB_ID_SYNC, + JOB_ID_SYNC, ComponentName(this, SyncService.Job::class.java) ) .setRequiredNetworkType(if (wifiOnly) JobInfo.NETWORK_TYPE_UNMETERED else JobInfo.NETWORK_TYPE_ANY) diff --git a/src/main/kotlin/com/looker/droidify/service/DownloadService.kt b/src/main/kotlin/com/looker/droidify/service/DownloadService.kt index f9dd05c0..2c25b39c 100644 --- a/src/main/kotlin/com/looker/droidify/service/DownloadService.kt +++ b/src/main/kotlin/com/looker/droidify/service/DownloadService.kt @@ -9,10 +9,7 @@ import android.content.Intent import android.net.Uri import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat -import com.looker.droidify.BuildConfig -import com.looker.droidify.Common -import com.looker.droidify.MainActivity -import com.looker.droidify.R +import com.looker.droidify.* import com.looker.droidify.content.Cache import com.looker.droidify.content.Preferences import com.looker.droidify.entity.Release @@ -119,7 +116,7 @@ class DownloadService : ConnectionService() { } else { cancelTasks(packageName) cancelCurrentTask(packageName) - notificationManager.cancel(task.notificationTag, Common.NOTIFICATION_ID_DOWNLOADING) + notificationManager.cancel(task.notificationTag, NOTIFICATION_ID_DOWNLOADING) tasks += task if (currentTask == null) { handleDownload() @@ -151,7 +148,7 @@ class DownloadService : ConnectionService() { if (Android.sdk(26)) { NotificationChannel( - Common.NOTIFICATION_CHANNEL_DOWNLOADING, + NOTIFICATION_CHANNEL_DOWNLOADING, getString(R.string.downloading), NotificationManager.IMPORTANCE_LOW ) .apply { setShowBadge(false) } @@ -208,9 +205,9 @@ class DownloadService : ConnectionService() { private fun showNotificationError(task: Task, errorType: ErrorType) { notificationManager.notify(task.notificationTag, - Common.NOTIFICATION_ID_DOWNLOADING, + NOTIFICATION_ID_DOWNLOADING, NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING) + .Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING) .setAutoCancel(true) .setSmallIcon(android.R.drawable.stat_sys_warning) .setColor( @@ -275,8 +272,8 @@ class DownloadService : ConnectionService() { private fun showNotificationInstall(task: Task) { notificationManager.notify( - task.notificationTag, Common.NOTIFICATION_ID_DOWNLOADING, NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING) + task.notificationTag, NOTIFICATION_ID_DOWNLOADING, NotificationCompat + .Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING) .setAutoCancel(true) .setSmallIcon(android.R.drawable.stat_sys_download_done) .setColor( @@ -373,7 +370,7 @@ class DownloadService : ConnectionService() { private val stateNotificationBuilder by lazy { NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING) + .Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING) .setSmallIcon(android.R.drawable.stat_sys_download) .setColor( ContextThemeWrapper(this, R.style.Theme_Main_Light) @@ -395,7 +392,7 @@ class DownloadService : ConnectionService() { private fun publishForegroundState(force: Boolean, state: State) { if (force || currentTask != null) { currentTask = currentTask?.copy(lastState = state) - startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply { + startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply { when (state) { is State.Connecting -> { setContentTitle(getString(R.string.downloading_FORMAT, state.name)) diff --git a/src/main/kotlin/com/looker/droidify/service/SyncService.kt b/src/main/kotlin/com/looker/droidify/service/SyncService.kt index 4d997851..184ea04b 100644 --- a/src/main/kotlin/com/looker/droidify/service/SyncService.kt +++ b/src/main/kotlin/com/looker/droidify/service/SyncService.kt @@ -13,10 +13,7 @@ import android.text.style.ForegroundColorSpan import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat import androidx.fragment.app.Fragment -import com.looker.droidify.BuildConfig -import com.looker.droidify.Common -import com.looker.droidify.MainActivity -import com.looker.droidify.R +import com.looker.droidify.* import com.looker.droidify.content.Preferences import com.looker.droidify.database.Database import com.looker.droidify.entity.ProductItem @@ -116,7 +113,7 @@ class SyncService : ConnectionService() { fun setUpdateNotificationBlocker(fragment: Fragment?) { updateNotificationBlockerFragment = fragment?.let(::WeakReference) if (fragment != null) { - notificationManager.cancel(Common.NOTIFICATION_ID_UPDATES) + notificationManager.cancel(NOTIFICATION_ID_UPDATES) } } @@ -159,13 +156,13 @@ class SyncService : ConnectionService() { if (Android.sdk(26)) { NotificationChannel( - Common.NOTIFICATION_CHANNEL_SYNCING, + NOTIFICATION_CHANNEL_SYNCING, getString(R.string.syncing), NotificationManager.IMPORTANCE_LOW ) .apply { setShowBadge(false) } .let(notificationManager::createNotificationChannel) NotificationChannel( - Common.NOTIFICATION_CHANNEL_UPDATES, + NOTIFICATION_CHANNEL_UPDATES, getString(R.string.updates), NotificationManager.IMPORTANCE_LOW ) .let(notificationManager::createNotificationChannel) @@ -213,8 +210,8 @@ class SyncService : ConnectionService() { private fun showNotificationError(repository: Repository, exception: Exception) { notificationManager.notify( - "repository-${repository.id}", Common.NOTIFICATION_ID_SYNCING, NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING) + "repository-${repository.id}", NOTIFICATION_ID_SYNCING, NotificationCompat + .Builder(this, NOTIFICATION_CHANNEL_SYNCING) .setSmallIcon(android.R.drawable.stat_sys_warning) .setColor( ContextThemeWrapper(this, R.style.Theme_Main_Light) @@ -240,7 +237,7 @@ class SyncService : ConnectionService() { private val stateNotificationBuilder by lazy { NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING) + .Builder(this, NOTIFICATION_CHANNEL_SYNCING) .setSmallIcon(R.drawable.ic_sync) .setColor( ContextThemeWrapper(this, R.style.Theme_Main_Light) @@ -263,7 +260,7 @@ class SyncService : ConnectionService() { if (force || currentTask?.lastState != state) { currentTask = currentTask?.copy(lastState = state) if (started == Started.MANUAL) { - startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply { + startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply { when (state) { is State.Connecting -> { setContentTitle(getString(R.string.syncing_FORMAT, state.name)) @@ -420,8 +417,8 @@ class SyncService : ConnectionService() { val maxUpdates = 5 fun T.applyHack(callback: T.() -> Unit): T = apply(callback) notificationManager.notify( - Common.NOTIFICATION_ID_UPDATES, NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_UPDATES) + NOTIFICATION_ID_UPDATES, NotificationCompat + .Builder(this, NOTIFICATION_CHANNEL_UPDATES) .setSmallIcon(R.drawable.ic_new_releases) .setContentTitle(getString(R.string.new_updates_available)) .setContentText( From c99c1be8dd0a058d25de742188b184fceaf651e6 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Thu, 28 Oct 2021 01:12:16 +0200 Subject: [PATCH 04/67] Update: Migrate Product.query (4.2/5 in replacing SQLite with Room) --- src/main/kotlin/com/looker/droidify/Common.kt | 25 +++++ .../com/looker/droidify/database/DAOs.kt | 98 +++++++++++++++++++ .../com/looker/droidify/database/Database.kt | 2 +- .../looker/droidify/database/QueryBuilder.kt | 4 +- 4 files changed, 127 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/Common.kt b/src/main/kotlin/com/looker/droidify/Common.kt index d520b4d4..6dca68af 100644 --- a/src/main/kotlin/com/looker/droidify/Common.kt +++ b/src/main/kotlin/com/looker/droidify/Common.kt @@ -8,5 +8,30 @@ const val NOTIFICATION_ID_SYNCING = 1 const val NOTIFICATION_ID_UPDATES = 2 const val NOTIFICATION_ID_DOWNLOADING = 3 +const val ROW_REPOSITORY_ID = "repository_id" +const val ROW_PACKAGE_NAME = "package_name" +const val ROW_NAME = "name" +const val ROW_SUMMARY = "summary" +const val ROW_DESCRIPTION = "description" +const val ROW_ADDED = "added" +const val ROW_UPDATED = "updated" +const val ROW_VERSION_CODE = "version_code" +const val ROW_SIGNATURES = "signatures" +const val ROW_COMPATIBLE = "compatible" +const val ROW_DATA = "data" +const val ROW_DATA_ITEM = "data_item" +const val ROW_VERSION = "version" +const val ROW_SIGNATURE = "signature" +const val ROW_ID = "_id" +const val ROW_ENABLED = "enabled" +const val ROW_DELETED = "deleted" +const val ROW_CAN_UPDATE = "can_update" +const val ROW_MATCH_RANK = "match_rank" +const val ROW_REPOSITORY_NAME = "repository" +const val ROW_PRODUCT_NAME = "product" +const val ROW_CATEGORY_NAME = "category" +const val ROW_INSTALLED_NAME = "memory.installed" +const val ROW_LOCK_NAME = "memory.lock" + const val JOB_ID_SYNC = 1 diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 5cf589de..e43a48da 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -1,6 +1,13 @@ package com.looker.droidify.database +import android.database.Cursor +import android.os.CancellationSignal import androidx.room.* +import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.sqlite.db.SupportSQLiteQuery +import com.looker.droidify.* +import com.looker.droidify.entity.ProductItem + interface BaseDao { @Insert @@ -48,6 +55,97 @@ interface ProductDao : BaseDao { @Query("DELETE FROM product WHERE repository_id = :id") fun deleteById(vararg id: Long): Int + + @RawQuery + fun query( + query: SupportSQLiteQuery + ): Cursor + + // TODO optimize and simplify + @Transaction + fun query( + installed: Boolean, updates: Boolean, searchQuery: String, + section: ProductItem.Section, order: ProductItem.Order, signal: CancellationSignal? + ): Cursor { + val builder = QueryBuilder() + + val signatureMatches = """installed.${ROW_SIGNATURE} IS NOT NULL AND + product.${ROW_SIGNATURES} LIKE ('%.' || installed.${ROW_SIGNATURE} || '.%') AND + product.${ROW_SIGNATURES} != ''""" + + builder += """SELECT product.rowid AS _id, product.${ROW_REPOSITORY_ID}, + product.${ROW_PACKAGE_NAME}, product.${ROW_NAME}, + product.${ROW_SUMMARY}, installed.${ROW_VERSION}, + (COALESCE(lock.${ROW_VERSION_CODE}, -1) NOT IN (0, product.${ROW_VERSION_CODE}) AND + product.${ROW_COMPATIBLE} != 0 AND product.${ROW_VERSION_CODE} > + COALESCE(installed.${ROW_VERSION_CODE}, 0xffffffff) AND $signatureMatches) + AS ${ROW_CAN_UPDATE}, product.${ROW_COMPATIBLE}, + product.${ROW_DATA_ITEM},""" + + if (searchQuery.isNotEmpty()) { + builder += """(((product.${ROW_NAME} LIKE ? OR + product.${ROW_SUMMARY} LIKE ?) * 7) | + ((product.${ROW_PACKAGE_NAME} LIKE ?) * 3) | + (product.${ROW_DESCRIPTION} LIKE ?)) AS ${ROW_MATCH_RANK},""" + builder %= List(4) { "%$searchQuery%" } + } else { + builder += "0 AS ${ROW_MATCH_RANK}," + } + + builder += """MAX((product.${ROW_COMPATIBLE} AND + (installed.${ROW_SIGNATURE} IS NULL OR $signatureMatches)) || + PRINTF('%016X', product.${ROW_VERSION_CODE})) FROM $ROW_PRODUCT_NAME AS product""" + builder += """JOIN $ROW_REPOSITORY_NAME AS repository + ON product.${ROW_REPOSITORY_ID} = repository.${ROW_ID}""" + builder += """LEFT JOIN $ROW_LOCK_NAME AS lock + ON product.${ROW_PACKAGE_NAME} = lock.${ROW_PACKAGE_NAME}""" + + if (!installed && !updates) { + builder += "LEFT" + } + builder += """JOIN $ROW_INSTALLED_NAME AS installed + ON product.${ROW_PACKAGE_NAME} = installed.${ROW_PACKAGE_NAME}""" + + if (section is ProductItem.Section.Category) { + builder += """JOIN $ROW_CATEGORY_NAME AS category + ON product.${ROW_PACKAGE_NAME} = category.${ROW_PACKAGE_NAME}""" + } + + builder += """WHERE repository.${ROW_ENABLED} != 0 AND + repository.${ROW_DELETED} == 0""" + + if (section is ProductItem.Section.Category) { + builder += "AND category.${ROW_NAME} = ?" + builder %= section.name + } else if (section is ProductItem.Section.Repository) { + builder += "AND product.${ROW_REPOSITORY_ID} = ?" + builder %= section.id.toString() + } + + if (searchQuery.isNotEmpty()) { + builder += """AND $ROW_MATCH_RANK > 0""" + } + + builder += "GROUP BY product.${ROW_PACKAGE_NAME} HAVING 1" + + if (updates) { + builder += "AND $ROW_CAN_UPDATE" + } + builder += "ORDER BY" + + if (searchQuery.isNotEmpty()) { + builder += """$ROW_MATCH_RANK DESC,""" + } + + when (order) { + ProductItem.Order.NAME -> Unit + ProductItem.Order.DATE_ADDED -> builder += "product.${ROW_ADDED} DESC," + ProductItem.Order.LAST_UPDATE -> builder += "product.${ROW_UPDATED} DESC," + }::class + builder += "product.${ROW_NAME} COLLATE LOCALIZED ASC" + + return query(SimpleSQLiteQuery(builder.build())) + } } @Dao diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index b514bfea..e266832c 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -515,7 +515,7 @@ object Database { .use { it.firstOrNull()?.getInt(0) ?: 0 } } - // TODO Too complex left to wiring phase + // Done fun query( installed: Boolean, updates: Boolean, searchQuery: String, section: ProductItem.Section, order: ProductItem.Order, signal: CancellationSignal? diff --git a/src/main/kotlin/com/looker/droidify/database/QueryBuilder.kt b/src/main/kotlin/com/looker/droidify/database/QueryBuilder.kt index c81618dd..86d3bcb1 100644 --- a/src/main/kotlin/com/looker/droidify/database/QueryBuilder.kt +++ b/src/main/kotlin/com/looker/droidify/database/QueryBuilder.kt @@ -33,6 +33,8 @@ class QueryBuilder { this.arguments += arguments } + fun build() = builder.toString() + fun query(db: SQLiteDatabase, signal: CancellationSignal?): Cursor { val query = builder.toString() val arguments = arguments.toTypedArray() @@ -47,4 +49,4 @@ class QueryBuilder { } return db.rawQuery(query, arguments, signal) } -} +} \ No newline at end of file From 78bb72c8e1f85ac785ee84d5e633f651c05449e0 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Thu, 28 Oct 2021 01:21:04 +0200 Subject: [PATCH 05/67] Update: Tables' names --- src/main/kotlin/com/looker/droidify/database/DAOs.kt | 6 +++--- .../kotlin/com/looker/droidify/database/Tables.kt | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index e43a48da..d65f1d3c 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -166,15 +166,15 @@ interface CategoryDao : BaseDao { @Dao interface InstalledDao : BaseDao { - @Query("SELECT * FROM installed WHERE package_name = :packageName") + @Query("SELECT * FROM `memory.installed` WHERE package_name = :packageName") fun get(packageName: String): Installed? - @Query("DELETE FROM installed WHERE package_name = :packageName") + @Query("DELETE FROM 'memory.installed' WHERE package_name = :packageName") fun delete(packageName: String) } @Dao interface LockDao : BaseDao { - @Query("DELETE FROM lock WHERE package_name = :packageName") + @Query("DELETE FROM 'memory.lock' WHERE package_name = :packageName") fun delete(packageName: String) } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index 930fd59b..c9acdc69 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -30,8 +30,8 @@ class Repository { } } -@Entity(primaryKeys = ["repository_id", "package_name"]) -class Product { +@Entity(tableName = "product", primaryKeys = ["repository_id", "package_name"]) +open class Product { var repository_id: Long = 0 var package_name = "" @@ -51,14 +51,14 @@ class Product { var data_item: ProductItem? = null } -@Entity(primaryKeys = ["repository_id", "package_name", "name"]) -class Category { +@Entity(tableName = "category", primaryKeys = ["repository_id", "package_name", "name"]) +open class Category { var repository_id: Long = 0 var package_name = "" var name = "" } -@Entity +@Entity(tableName = "memory.installed") class Installed { @PrimaryKey var package_name = "" @@ -68,7 +68,7 @@ class Installed { var signature = "" } -@Entity +@Entity(tableName = "memory.lock") class Lock { @PrimaryKey var package_name = "" From a59bd2bd8ad080c3f451dd2cbd999def0e60df03 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Thu, 28 Oct 2021 02:08:07 +0200 Subject: [PATCH 06/67] Update: Migrate transformers (4.3/5 in replacing SQLite with Room) --- .../com/looker/droidify/database/Database.kt | 9 ++--- .../com/looker/droidify/utility/Utils.kt | 36 ++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index e266832c..ffe45792 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -477,7 +477,7 @@ object Database { ).observable(Subject.Repositories) } - // Unnecessary with Room + // Done fun transform(cursor: Cursor): Repository { return cursor.getBlob(cursor.getColumnIndex(Schema.Repository.ROW_DATA)) .jsonParse { @@ -600,7 +600,7 @@ object Database { return builder.query(db, signal).observable(Subject.Products) } - // Unnecessary with Room + // Done private fun transform(cursor: Cursor): Product { return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA)) .jsonParse { @@ -613,7 +613,7 @@ object Database { } } - // TODO should be taken care of with extra functions that build on Room's queries + // Done fun transformItem(cursor: Cursor): ProductItem { return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA_ITEM)) .jsonParse { @@ -726,7 +726,7 @@ object Database { } } - // Done with some changes in DAOS + // Done with some changes in DAOs object LockAdapter { // Done in insert (Lock object instead of pair) private fun put(lock: Pair, notify: Boolean) { @@ -766,6 +766,7 @@ object Database { private val Table.temporaryName: String get() = "${name}_temporary" + // Done fun createTemporaryTable() { db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") diff --git a/src/main/kotlin/com/looker/droidify/utility/Utils.kt b/src/main/kotlin/com/looker/droidify/utility/Utils.kt index 657959f5..280886f1 100644 --- a/src/main/kotlin/com/looker/droidify/utility/Utils.kt +++ b/src/main/kotlin/com/looker/droidify/utility/Utils.kt @@ -3,11 +3,14 @@ package com.looker.droidify.utility import android.content.Context import android.content.pm.PackageInfo import android.content.pm.Signature +import android.database.Cursor import android.graphics.drawable.Drawable -import com.looker.droidify.R +import com.looker.droidify.* import com.looker.droidify.content.Preferences +import com.looker.droidify.database.Database.jsonParse import com.looker.droidify.entity.InstalledItem import com.looker.droidify.entity.Product +import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository import com.looker.droidify.service.Connection import com.looker.droidify.service.DownloadService @@ -109,3 +112,34 @@ object Utils { } else Unit } } + +fun Cursor.getProduct(): Product = getBlob(getColumnIndex(ROW_DATA)) + .jsonParse { + Product.deserialize(it).apply { + this.repositoryId = getLong(getColumnIndex(ROW_REPOSITORY_ID)) + this.description = getString(getColumnIndex(ROW_DESCRIPTION)) + } + } + + +fun Cursor.getProductItem(): ProductItem = getBlob(getColumnIndex(ROW_DATA_ITEM)) + .jsonParse { + ProductItem.deserialize(it).apply { + this.repositoryId = getLong(getColumnIndex(ROW_REPOSITORY_ID)) + this.packageName = getString(getColumnIndex(ROW_PACKAGE_NAME)) + this.name = getString(getColumnIndex(ROW_NAME)) + this.summary = getString(getColumnIndex(ROW_SUMMARY)) + this.installedVersion = getString(getColumnIndex(ROW_VERSION)) + .orEmpty() + this.compatible = getInt(getColumnIndex(ROW_COMPATIBLE)) != 0 + this.canUpdate = getInt(getColumnIndex(ROW_CAN_UPDATE)) != 0 + this.matchRank = getInt(getColumnIndex(ROW_MATCH_RANK)) + } + } + +fun Cursor.getRepository(): Repository = getBlob(getColumnIndex(ROW_DATA)) + .jsonParse { + Repository.deserialize(it).apply { + this.id = getLong(getColumnIndex(ROW_ID)) + } + } \ No newline at end of file From 6aab888c0fd9b32e775516e60a6725bb8fca52fd Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 29 Oct 2021 23:30:12 +0200 Subject: [PATCH 07/67] Update: Migrate temporary tables (4.5/5 in replacing SQLite with Room) --- .../com/looker/droidify/database/DAOs.kt | 52 +++++++++++++++++++ .../com/looker/droidify/database/Database.kt | 4 +- .../com/looker/droidify/database/DatabaseX.kt | 22 ++++++-- .../com/looker/droidify/database/Tables.kt | 22 +++++--- 4 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index d65f1d3c..fd689463 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -177,4 +177,56 @@ interface InstalledDao : BaseDao { interface LockDao : BaseDao { @Query("DELETE FROM 'memory.lock' WHERE package_name = :packageName") fun delete(packageName: String) +} + +@Dao +interface ProductTempDao : BaseDao { + @get:Query("SELECT * FROM `product.temporary`") + val all: Array + + @Query("DELETE FROM `product.temporary`") + fun emptyTable() + + @Insert + fun insertCategory(vararg product: CategoryTemp) + + @Transaction + fun putTemporary(products: List) { + products.forEach { + val signatures = it.signatures.joinToString { ".$it" } + .let { if (it.isNotEmpty()) "$it." else "" } + insert(it.let { + ProductTemp().apply { + repository_id = it.repositoryId + package_name = it.packageName + name = it.name + summary = it.summary + description = it.description + added = it.added + updated = it.updated + version_code = it.versionCode + this.signatures = signatures + compatible = if (it.compatible) 1 else 0 + data = it + data_item = it.item() + } + }) + it.categories.forEach { category -> + insertCategory(CategoryTemp().apply { + repository_id = it.repositoryId + package_name = it.packageName + name = category + }) + } + } + } +} + +@Dao +interface CategoryTempDao : BaseDao { + @get:Query("SELECT * FROM `category.temporary`") + val all: Array + + @Query("DELETE FROM `category.temporary`") + fun emptyTable() } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index ffe45792..271181db 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -761,7 +761,7 @@ object Database { } } - // TODO add temporary tables + // Done object UpdaterAdapter { private val Table.temporaryName: String get() = "${name}_temporary" @@ -774,6 +774,7 @@ object Database { db.execSQL(Schema.Category.formatCreateTable(Schema.Category.temporaryName)) } + // Done fun putTemporary(products: List) { db.beginTransaction() try { @@ -812,6 +813,7 @@ object Database { } } + // Done fun finishTemporary(repository: Repository, success: Boolean) { if (success) { db.beginTransaction() diff --git a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt index 2a8e02ea..bff2a514 100644 --- a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt +++ b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt @@ -1,16 +1,16 @@ package com.looker.droidify.database import android.content.Context +import androidx.room.* import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.room.TypeConverters @Database( entities = [ Repository::class, Product::class, + ProductTemp::class, Category::class, + CategoryTemp::class, Installed::class, Lock::class ], version = 1 @@ -19,7 +19,9 @@ import androidx.room.TypeConverters abstract class DatabaseX : RoomDatabase() { abstract val repositoryDao: RepositoryDao abstract val productDao: ProductDao + abstract val productTempDao: ProductTempDao abstract val categoryDao: CategoryDao + abstract val categoryTempDao: CategoryTempDao abstract val installedDao: InstalledDao abstract val lockDao: LockDao @@ -45,6 +47,7 @@ abstract class DatabaseX : RoomDatabase() { } } + @Transaction fun cleanUp(pairs: Set>) { val result = pairs.windowed(10, 10, true).map { val ids = it.map { it.first }.toLongArray() @@ -59,4 +62,17 @@ abstract class DatabaseX : RoomDatabase() { com.looker.droidify.database.Database.notifyChanged(com.looker.droidify.database.Database.Subject.Products) }*/ } + + @Transaction + fun finishTemporary(repository: com.looker.droidify.entity.Repository, success: Boolean) { + if (success) { + productDao.deleteById(repository.id) + categoryDao.deleteById(repository.id) + productDao.insert(*(productTempDao.all)) + categoryDao.insert(*(categoryTempDao.all)) + repositoryDao.put(repository) + } + productTempDao.emptyTable() + categoryTempDao.emptyTable() + } } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index c9acdc69..626550c8 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -6,7 +6,6 @@ import androidx.room.PrimaryKey import androidx.room.TypeConverter import com.looker.droidify.database.Database.jsonGenerate import com.looker.droidify.database.Database.jsonParse -import com.looker.droidify.entity.Product import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository @@ -32,25 +31,28 @@ class Repository { @Entity(tableName = "product", primaryKeys = ["repository_id", "package_name"]) open class Product { - var repository_id: Long = 0 + var repository_id = 0L var package_name = "" var name = "" var summary = "" var description = "" - var added = 0 - var updated = 0 - var version_code = 0 + var added = 0L + var updated = 0L + var version_code = 0L var signatures = "" var compatible = 0 @ColumnInfo(typeAffinity = ColumnInfo.BLOB) - var data: Product? = null + var data: com.looker.droidify.entity.Product? = null @ColumnInfo(typeAffinity = ColumnInfo.BLOB) var data_item: ProductItem? = null } +@Entity(tableName = "product.temporary") +class ProductTemp : Product() + @Entity(tableName = "category", primaryKeys = ["repository_id", "package_name", "name"]) open class Category { var repository_id: Long = 0 @@ -58,6 +60,9 @@ open class Category { var name = "" } +@Entity(tableName = "category.temporary") +class CategoryTemp : Category() + @Entity(tableName = "memory.installed") class Installed { @PrimaryKey @@ -87,11 +92,12 @@ object Converters { @TypeConverter @JvmStatic - fun toProduct(byteArray: ByteArray) = byteArray.jsonParse { Product.deserialize(it) } + fun toProduct(byteArray: ByteArray) = + byteArray.jsonParse { com.looker.droidify.entity.Product.deserialize(it) } @TypeConverter @JvmStatic - fun toByteArray(product: Product) = jsonGenerate(product::serialize) + fun toByteArray(product: com.looker.droidify.entity.Product) = jsonGenerate(product::serialize) @TypeConverter @JvmStatic From 73caf07ba1bac092fd2b9e3bfa95e8cbad5313bb Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 29 Oct 2021 23:33:58 +0200 Subject: [PATCH 08/67] Fix: Put of repositoryDao --- src/main/kotlin/com/looker/droidify/database/DAOs.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index fd689463..235666e8 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -22,8 +22,16 @@ interface BaseDao { @Dao interface RepositoryDao : BaseDao { - fun put(repository: Repository) { - if (repository.id >= 0L) update(repository) else insert(repository) + fun put(repository: com.looker.droidify.entity.Repository) { + repository.let { + val dbRepo = Repository().apply { + id = it.id + enabled = if (it.enabled) 1 else 0 + deleted = 0 + data = it + } + if (repository.id >= 0L) update(dbRepo) else insert(dbRepo) + } } @Query("SELECT * FROM repository WHERE _id = :id and deleted == 0") From acf3f0d1fd041a63adc7b36b95c885c5f62a9f3c Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 00:58:35 +0100 Subject: [PATCH 09/67] Update: Migrate Json utils --- .../com/looker/droidify/database/Tables.kt | 4 ++-- .../com/looker/droidify/utility/Utils.kt | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index 626550c8..18f6e3bf 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -4,10 +4,10 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.TypeConverter -import com.looker.droidify.database.Database.jsonGenerate -import com.looker.droidify.database.Database.jsonParse import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository +import com.looker.droidify.utility.jsonGenerate +import com.looker.droidify.utility.jsonParse @Entity class Repository { diff --git a/src/main/kotlin/com/looker/droidify/utility/Utils.kt b/src/main/kotlin/com/looker/droidify/utility/Utils.kt index 280886f1..b5bb6c88 100644 --- a/src/main/kotlin/com/looker/droidify/utility/Utils.kt +++ b/src/main/kotlin/com/looker/droidify/utility/Utils.kt @@ -5,9 +5,10 @@ import android.content.pm.PackageInfo import android.content.pm.Signature import android.database.Cursor import android.graphics.drawable.Drawable +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser import com.looker.droidify.* import com.looker.droidify.content.Preferences -import com.looker.droidify.database.Database.jsonParse import com.looker.droidify.entity.InstalledItem import com.looker.droidify.entity.Product import com.looker.droidify.entity.ProductItem @@ -17,9 +18,13 @@ import com.looker.droidify.service.DownloadService import com.looker.droidify.utility.extension.android.Android import com.looker.droidify.utility.extension.android.singleSignature import com.looker.droidify.utility.extension.android.versionCodeCompat +import com.looker.droidify.utility.extension.json.Json +import com.looker.droidify.utility.extension.json.parseDictionary +import com.looker.droidify.utility.extension.json.writeDictionary import com.looker.droidify.utility.extension.resources.getColorFromAttr import com.looker.droidify.utility.extension.resources.getDrawableCompat import com.looker.droidify.utility.extension.text.hex +import java.io.ByteArrayOutputStream import java.security.MessageDigest import java.security.cert.Certificate import java.security.cert.CertificateEncodingException @@ -142,4 +147,14 @@ fun Cursor.getRepository(): Repository = getBlob(getColumnIndex(ROW_DATA)) Repository.deserialize(it).apply { this.id = getLong(getColumnIndex(ROW_ID)) } - } \ No newline at end of file + } + +fun ByteArray.jsonParse(callback: (JsonParser) -> T): T { + return Json.factory.createParser(this).use { it.parseDictionary(callback) } +} + +fun jsonGenerate(callback: (JsonGenerator) -> Unit): ByteArray { + val outputStream = ByteArrayOutputStream() + Json.factory.createGenerator(outputStream).use { it.writeDictionary(callback) } + return outputStream.toByteArray() +} \ No newline at end of file From d875fc57c90a1342529ae202a7db75e279d0404f Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:00:16 +0100 Subject: [PATCH 10/67] Update: Migrate transform to InstalledItem method --- src/main/kotlin/com/looker/droidify/utility/Utils.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/kotlin/com/looker/droidify/utility/Utils.kt b/src/main/kotlin/com/looker/droidify/utility/Utils.kt index b5bb6c88..0027d995 100644 --- a/src/main/kotlin/com/looker/droidify/utility/Utils.kt +++ b/src/main/kotlin/com/looker/droidify/utility/Utils.kt @@ -149,6 +149,13 @@ fun Cursor.getRepository(): Repository = getBlob(getColumnIndex(ROW_DATA)) } } +fun Cursor.getInstalledItem(): InstalledItem = InstalledItem( + getString(getColumnIndex(ROW_PACKAGE_NAME)), + getString(getColumnIndex(ROW_VERSION)), + getLong(getColumnIndex(ROW_VERSION_CODE)), + getString(getColumnIndex(ROW_SIGNATURE)) +) + fun ByteArray.jsonParse(callback: (JsonParser) -> T): T { return Json.factory.createParser(this).use { it.parseDictionary(callback) } } From a07c017e21c32390cb69ef818e1a857baf9e45c8 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:00:52 +0100 Subject: [PATCH 11/67] Add: Room schema directory --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index 37f34db0..92521a8e 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,13 @@ android { targetSdk = 31 versionCode = 35 versionName = "0.3.5" + + javaCompileOptions { + annotationProcessorOptions { + arguments += ["room.schemaLocation": "$projectDir/schemas".toString()] + arguments += ["room.incremental": "true"] + } + } } sourceSets.all { From dedbaefef5e54c1ea6c44095e35fb4ed8505f677 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:01:39 +0100 Subject: [PATCH 12/67] Add: DatabaseX instance to ScreenActivity --- src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt b/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt index 518bb288..6e04d3ea 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt @@ -10,6 +10,7 @@ import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.google.android.material.circularreveal.CircularRevealFrameLayout +import com.looker.droidify.MainApplication import com.looker.droidify.R import com.looker.droidify.content.Preferences import com.looker.droidify.database.CursorOwner @@ -25,6 +26,9 @@ abstract class ScreenActivity : AppCompatActivity() { private const val STATE_FRAGMENT_STACK = "fragmentStack" } + val db + get() = (application as MainApplication).db + sealed class SpecialIntent { object Updates : SpecialIntent() class Install(val packageName: String?, val cacheFileName: String?) : SpecialIntent() From 9235ed6e0d19b221e40f382598cc3c8fe756ba2e Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:02:46 +0100 Subject: [PATCH 13/67] Update: Complete the DAOs --- .../com/looker/droidify/database/DAOs.kt | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 235666e8..91c05098 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -14,7 +14,7 @@ interface BaseDao { fun insert(vararg product: T) @Update(onConflict = OnConflictStrategy.REPLACE) - fun update(vararg obj: T) + fun update(vararg obj: T): Int @Delete fun delete(obj: T) @@ -22,21 +22,28 @@ interface BaseDao { @Dao interface RepositoryDao : BaseDao { - fun put(repository: com.looker.droidify.entity.Repository) { + fun put(repository: com.looker.droidify.entity.Repository): com.looker.droidify.entity.Repository { repository.let { val dbRepo = Repository().apply { id = it.id enabled = if (it.enabled) 1 else 0 - deleted = 0 + deleted = false data = it } - if (repository.id >= 0L) update(dbRepo) else insert(dbRepo) + val newId = if (repository.id >= 0L) update(dbRepo).toLong() else returnInsert(dbRepo) + return if (newId != repository.id) repository.copy(id = newId) else repository } } + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun returnInsert(product: Repository): Long + @Query("SELECT * FROM repository WHERE _id = :id and deleted == 0") fun get(id: Long): Repository? + @get:Query("SELECT * FROM repository WHERE deleted == 0 ORDER BY _id ASC") + val allCursor: Cursor + @get:Query("SELECT * FROM repository WHERE deleted == 0 ORDER BY _id ASC") val all: List @@ -49,7 +56,7 @@ interface RepositoryDao : BaseDao { // TODO optimize @Update(onConflict = OnConflictStrategy.REPLACE) fun markAsDeleted(id: Long) { - get(id).apply { this?.deleted = 1 }?.let { update(it) } + get(id).apply { this?.deleted = true }?.let { update(it) } } } @@ -59,7 +66,7 @@ interface ProductDao : BaseDao { fun countForRepository(id: Long): Long @Query("SELECT * FROM product WHERE package_name = :packageName") - fun get(packageName: String): Product? + fun get(packageName: String): List @Query("DELETE FROM product WHERE repository_id = :id") fun deleteById(vararg id: Long): Int @@ -158,7 +165,7 @@ interface ProductDao : BaseDao { @Dao interface CategoryDao : BaseDao { - @Query( + @get:Query( """SELECT DISTINCT category.name FROM category AS category JOIN repository AS repository @@ -166,7 +173,7 @@ interface CategoryDao : BaseDao { WHERE repository.enabled != 0 AND repository.deleted == 0""" ) - fun getAll(): List + val allNames: List @Query("DELETE FROM category WHERE repository_id = :id") fun deleteById(vararg id: Long): Int @@ -174,8 +181,18 @@ interface CategoryDao : BaseDao { @Dao interface InstalledDao : BaseDao { + fun put(vararg isntalled: com.looker.droidify.entity.InstalledItem) { + isntalled.forEach { + insert(Installed(it.packageName).apply { + version = it.version + version_code = it.versionCode + signature = it.signature + }) + } + } + @Query("SELECT * FROM `memory.installed` WHERE package_name = :packageName") - fun get(packageName: String): Installed? + fun get(packageName: String): Cursor @Query("DELETE FROM 'memory.installed' WHERE package_name = :packageName") fun delete(packageName: String) From dc65c060e754838619e6fd86cf0d8fe7a8e41945 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:05:15 +0100 Subject: [PATCH 14/67] Update: Migrate from Database to DatabaseX (4.7/5 in replacing SQLite with Room) --- .../com/looker/droidify/MainApplication.kt | 22 +++++----- .../droidify/content/ProductPreferences.kt | 32 +++++++++------ .../looker/droidify/database/CursorOwner.kt | 9 +++-- .../com/looker/droidify/database/DatabaseX.kt | 40 ++++++++++--------- .../com/looker/droidify/database/Tables.kt | 12 +++--- .../droidify/index/RepositoryUpdater.kt | 32 ++++++++------- .../droidify/screen/EditRepositoryFragment.kt | 10 ++--- .../looker/droidify/screen/ProductFragment.kt | 13 ++++-- .../looker/droidify/screen/ProductsAdapter.kt | 4 +- .../droidify/screen/ProductsFragment.kt | 4 +- .../droidify/screen/RepositoriesAdapter.kt | 4 +- .../droidify/screen/RepositoryFragment.kt | 5 +-- .../droidify/screen/ScreenshotsFragment.kt | 12 ++++-- .../looker/droidify/screen/TabsFragment.kt | 8 ++-- .../looker/droidify/service/SyncService.kt | 20 +++++----- 15 files changed, 128 insertions(+), 99 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/MainApplication.kt b/src/main/kotlin/com/looker/droidify/MainApplication.kt index f6c10af3..52bf688f 100644 --- a/src/main/kotlin/com/looker/droidify/MainApplication.kt +++ b/src/main/kotlin/com/looker/droidify/MainApplication.kt @@ -10,7 +10,7 @@ import coil.ImageLoaderFactory import com.looker.droidify.content.Cache import com.looker.droidify.content.Preferences import com.looker.droidify.content.ProductPreferences -import com.looker.droidify.database.Database +import com.looker.droidify.database.DatabaseX import com.looker.droidify.index.RepositoryUpdater import com.looker.droidify.network.CoilDownloader import com.looker.droidify.network.Downloader @@ -24,19 +24,21 @@ import java.net.Proxy @Suppress("unused") class MainApplication : Application(), ImageLoaderFactory { + lateinit var db: DatabaseX + override fun onCreate() { super.onCreate() - val databaseUpdated = Database.init(this) + db = DatabaseX.getInstance(applicationContext) Preferences.init(this) ProductPreferences.init(this) - RepositoryUpdater.init() + RepositoryUpdater.init(this) listenApplications() listenPreferences() - if (databaseUpdated) { + /*if (databaseUpdated) { forceSyncAll() - } + }*/ Cache.cleanup(this) updateSyncJob(false) @@ -60,9 +62,9 @@ class MainApplication : Application(), ImageLoaderFactory { null } if (packageInfo != null) { - Database.InstalledAdapter.put(packageInfo.toInstalledItem()) + db.installedDao.put(packageInfo.toInstalledItem()) } else { - Database.InstalledAdapter.delete(packageName) + db.installedDao.delete(packageName) } } } @@ -76,7 +78,7 @@ class MainApplication : Application(), ImageLoaderFactory { val installedItems = packageManager.getInstalledPackages(Android.PackageManager.signaturesFlag) .map { it.toInstalledItem() } - Database.InstalledAdapter.putAll(installedItems) + db.installedDao.put(*installedItems.toTypedArray()) } private fun listenPreferences() { @@ -160,9 +162,9 @@ class MainApplication : Application(), ImageLoaderFactory { } private fun forceSyncAll() { - Database.RepositoryAdapter.getAll(null).forEach { + db.repositoryDao.all.mapNotNull { it.data }.forEach { if (it.lastModified.isNotEmpty() || it.entityTag.isNotEmpty()) { - Database.RepositoryAdapter.put(it.copy(lastModified = "", entityTag = "")) + db.repositoryDao.put(it.copy(lastModified = "", entityTag = "")) } } Connection(SyncService::class.java, onBind = { connection, binder -> diff --git a/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt b/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt index fb6ef278..ee3ac97e 100644 --- a/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt +++ b/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt @@ -2,7 +2,8 @@ package com.looker.droidify.content import android.content.Context import android.content.SharedPreferences -import com.looker.droidify.database.Database +import com.looker.droidify.database.DatabaseX +import com.looker.droidify.database.Lock import com.looker.droidify.entity.ProductPreference import com.looker.droidify.utility.extension.json.Json import com.looker.droidify.utility.extension.json.parseDictionary @@ -16,25 +17,32 @@ object ProductPreferences { private val defaultProductPreference = ProductPreference(false, 0L) private lateinit var preferences: SharedPreferences private val subject = PublishSubject.create>() + lateinit var db: DatabaseX fun init(context: Context) { + db = DatabaseX.getInstance(context) preferences = context.getSharedPreferences("product_preferences", Context.MODE_PRIVATE) - Database.LockAdapter.putAll(preferences.all.keys - .mapNotNull { packageName -> - this[packageName].databaseVersionCode?.let { - Pair( - packageName, - it - ) + db.lockDao.insert(*preferences.all.keys + .mapNotNull { pName -> + this[pName].databaseVersionCode?.let { + Lock().apply { + package_name = pName + version_code = it + } } - }) + } + .toTypedArray() + ) subject .observeOn(Schedulers.io()) - .subscribe { (packageName, versionCode) -> + .subscribe { (pName, versionCode) -> if (versionCode != null) { - Database.LockAdapter.put(Pair(packageName, versionCode)) + db.lockDao.insert(Lock().apply { + package_name = pName + version_code = versionCode + }) } else { - Database.LockAdapter.delete(packageName) + db.lockDao.delete(pName) } } } diff --git a/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt b/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt index b1e47842..10f07d6a 100644 --- a/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt +++ b/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt @@ -87,9 +87,10 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks { override fun onCreateLoader(id: Int, args: Bundle?): Loader { val request = activeRequests[id]!!.request + val db = DatabaseX.getInstance(requireContext()) return QueryLoader(requireContext()) { when (request) { - is Request.ProductsAvailable -> Database.ProductAdapter + is Request.ProductsAvailable -> db.productDao .query( installed = false, updates = false, @@ -98,7 +99,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks { order = request.order, signal = it ) - is Request.ProductsInstalled -> Database.ProductAdapter + is Request.ProductsInstalled -> db.productDao .query( installed = true, updates = false, @@ -107,7 +108,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks { order = request.order, signal = it ) - is Request.ProductsUpdates -> Database.ProductAdapter + is Request.ProductsUpdates -> db.productDao .query( installed = true, updates = true, @@ -116,7 +117,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks { order = request.order, signal = it ) - is Request.Repositories -> Database.RepositoryAdapter.query(it) + is Request.Repositories -> db.repositoryDao.allCursor } } } diff --git a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt index bff2a514..72a10b27 100644 --- a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt +++ b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt @@ -1,8 +1,10 @@ package com.looker.droidify.database import android.content.Context -import androidx.room.* import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters @Database( entities = [ @@ -47,15 +49,16 @@ abstract class DatabaseX : RoomDatabase() { } } - @Transaction fun cleanUp(pairs: Set>) { - val result = pairs.windowed(10, 10, true).map { - val ids = it.map { it.first }.toLongArray() - val productsCount = productDao.deleteById(*ids) - val categoriesCount = categoryDao.deleteById(*ids) - val deleteIds = it.filter { it.second }.map { it.first }.toLongArray() - repositoryDao.deleteById(*deleteIds) - productsCount != 0 || categoriesCount != 0 + runInTransaction { + val result = pairs.windowed(10, 10, true).map { + val ids = it.map { it.first }.toLongArray() + val productsCount = productDao.deleteById(*ids) + val categoriesCount = categoryDao.deleteById(*ids) + val deleteIds = it.filter { it.second }.map { it.first }.toLongArray() + repositoryDao.deleteById(*deleteIds) + productsCount != 0 || categoriesCount != 0 + } } // Use live objects and observers instead /*if (result.any { it }) { @@ -63,16 +66,17 @@ abstract class DatabaseX : RoomDatabase() { }*/ } - @Transaction fun finishTemporary(repository: com.looker.droidify.entity.Repository, success: Boolean) { - if (success) { - productDao.deleteById(repository.id) - categoryDao.deleteById(repository.id) - productDao.insert(*(productTempDao.all)) - categoryDao.insert(*(categoryTempDao.all)) - repositoryDao.put(repository) + runInTransaction { + if (success) { + productDao.deleteById(repository.id) + categoryDao.deleteById(repository.id) + productDao.insert(*(productTempDao.all)) + categoryDao.insert(*(categoryTempDao.all)) + repositoryDao.put(repository) + } + productTempDao.emptyTable() + categoryTempDao.emptyTable() } - productTempDao.emptyTable() - categoryTempDao.emptyTable() } } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index 18f6e3bf..3b881cdc 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -16,7 +16,7 @@ class Repository { var id: Long = 0 var enabled = 0 - var deleted = 0 + var deleted = false @ColumnInfo(typeAffinity = ColumnInfo.BLOB) var data: Repository? = null @@ -25,7 +25,7 @@ class Repository { @ColumnInfo(name = "_id") var id = 0L - var deleted = 0 + var deleted = false } } @@ -64,12 +64,12 @@ open class Category { class CategoryTemp : Category() @Entity(tableName = "memory.installed") -class Installed { +class Installed(pName: String = "") { @PrimaryKey - var package_name = "" + var package_name = pName var version = "" - var version_code = 0 + var version_code = 0L var signature = "" } @@ -78,7 +78,7 @@ class Lock { @PrimaryKey var package_name = "" - var version_code = 0 + var version_code = 0L } object Converters { diff --git a/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt b/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt index 20e63893..1f0b9aee 100644 --- a/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt +++ b/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt @@ -4,6 +4,7 @@ import android.content.Context import android.net.Uri import com.looker.droidify.content.Cache import com.looker.droidify.database.Database +import com.looker.droidify.database.DatabaseX import com.looker.droidify.entity.Product import com.looker.droidify.entity.Release import com.looker.droidify.entity.Repository @@ -59,29 +60,28 @@ object RepositoryUpdater { private val updaterLock = Any() private val cleanupLock = Any() + lateinit var db: DatabaseX - fun init() { - + fun init(context: Context) { + db = DatabaseX.getInstance(context) var lastDisabled = setOf() Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) .flatMapSingle { RxUtils.querySingle { - Database.RepositoryAdapter.getAllDisabledDeleted( - it - ) + db.repositoryDao.allDisabledDeleted } } .forEach { it -> - val newDisabled = it.asSequence().filter { !it.second }.map { it.first }.toSet() + val newDisabled = it.asSequence().filter { !it.deleted }.map { it.id }.toSet() val disabled = newDisabled - lastDisabled lastDisabled = newDisabled - val deleted = it.asSequence().filter { it.second }.map { it.first }.toSet() + val deleted = it.asSequence().filter { it.deleted }.map { it.id }.toSet() if (disabled.isNotEmpty() || deleted.isNotEmpty()) { val pairs = (disabled.asSequence().map { Pair(it, false) } + deleted.asSequence().map { Pair(it, true) }).toSet() - synchronized(cleanupLock) { Database.RepositoryAdapter.cleanup(pairs) } + synchronized(cleanupLock) { db.cleanUp(pairs) } } } } @@ -193,12 +193,14 @@ object RepositoryUpdater { file: File, lastModified: String, entityTag: String, callback: (Stage, Long, Long?) -> Unit ): Boolean { var rollback = true + val db = DatabaseX.getInstance(context) return synchronized(updaterLock) { try { val jarFile = JarFile(file, true) val indexEntry = jarFile.getEntry(indexType.contentName) as JarEntry val total = indexEntry.size - Database.UpdaterAdapter.createTemporaryTable() + db.productTempDao.emptyTable() + db.categoryTempDao.emptyTable() val features = context.packageManager.systemAvailableFeatures .asSequence().map { it.name }.toSet() + setOf("android.hardware.touchscreen") @@ -231,7 +233,7 @@ object RepositoryUpdater { } products += transformProduct(product, features, unstable) if (products.size >= 50) { - Database.UpdaterAdapter.putTemporary(products) + db.productTempDao.putTemporary(products) products.clear() } } @@ -249,7 +251,7 @@ object RepositoryUpdater { throw InterruptedException() } if (products.isNotEmpty()) { - Database.UpdaterAdapter.putTemporary(products) + db.productTempDao.putTemporary(products) products.clear() } Pair(changedRepository, certificateFromIndex) @@ -334,7 +336,7 @@ object RepositoryUpdater { progress.toLong(), totalCount.toLong() ) - Database.UpdaterAdapter.putTemporary(products + db.productTempDao.putTemporary(products .map { transformProduct(it, features, unstable) }) } } @@ -407,7 +409,7 @@ object RepositoryUpdater { } callback(Stage.COMMIT, 0, null) synchronized(cleanupLock) { - Database.UpdaterAdapter.finishTemporary( + db.finishTemporary( commitRepository, true ) @@ -423,7 +425,7 @@ object RepositoryUpdater { } finally { file.delete() if (rollback) { - Database.UpdaterAdapter.finishTemporary(repository, false) + db.finishTemporary(repository, false) } } } diff --git a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt index 99bec4f3..ae5376f4 100644 --- a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt @@ -161,7 +161,7 @@ class EditRepositoryFragment() : ScreenFragment() { }) if (savedInstanceState == null) { - val repository = repositoryId?.let(Database.RepositoryAdapter::get) + val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.data } if (repository == null) { val clipboardManager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -240,9 +240,9 @@ class EditRepositoryFragment() : ScreenFragment() { } repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } .observeOn(AndroidSchedulers.mainThread()) .subscribe { it -> takenAddresses = it.asSequence().filter { it.id != repositoryId } @@ -462,10 +462,10 @@ class EditRepositoryFragment() : ScreenFragment() { MessageDialog(MessageDialog.Message.CantEditSyncing).show(childFragmentManager) invalidateState() } else { - val repository = repositoryId?.let(Database.RepositoryAdapter::get) + val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.data } ?.edit(address, fingerprint, authentication) ?: Repository.newRepository(address, fingerprint, authentication) - val changedRepository = Database.RepositoryAdapter.put(repository) + val changedRepository = screenActivity.db.repositoryDao.put(repository) if (repositoryId == null && changedRepository.enabled) { binder.sync(changedRepository) } diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt index 97cadc8e..40dcec0e 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt @@ -28,6 +28,7 @@ import com.looker.droidify.utility.Utils import com.looker.droidify.utility.Utils.startUpdate import com.looker.droidify.utility.extension.android.* import com.looker.droidify.utility.extension.text.trimAfter +import com.looker.droidify.utility.getInstalledItem import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable @@ -132,12 +133,16 @@ class ProductFragment() : ScreenFragment(), ProductAdapter.Callbacks { var first = true productDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Products)) + .concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } } + .flatMapSingle { + RxUtils.querySingle { + screenActivity.db.productDao.get(packageName).mapNotNull { it?.data } + } + } .flatMapSingle { products -> RxUtils - .querySingle { Database.RepositoryAdapter.getAll(it) } + .querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } .map { it -> it.asSequence().map { Pair(it.id, it) }.toMap() .let { @@ -154,7 +159,7 @@ class ProductFragment() : ScreenFragment(), ProductAdapter.Callbacks { } .flatMapSingle { products -> RxUtils - .querySingle { Nullable(Database.InstalledAdapter.get(packageName, it)) } + .querySingle { Nullable(screenActivity.db.installedDao.get(packageName).getInstalledItem()) } .map { Pair(products, it) } } .observeOn(AndroidSchedulers.mainThread()) diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductsAdapter.kt b/src/main/kotlin/com/looker/droidify/screen/ProductsAdapter.kt index 84fccbbd..8041f2b8 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductsAdapter.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductsAdapter.kt @@ -16,13 +16,13 @@ import com.google.android.material.progressindicator.CircularProgressIndicator import com.google.android.material.textview.MaterialTextView import com.looker.droidify.R import com.looker.droidify.content.Preferences -import com.looker.droidify.database.Database import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository import com.looker.droidify.network.CoilDownloader import com.looker.droidify.utility.Utils import com.looker.droidify.utility.extension.resources.* import com.looker.droidify.utility.extension.text.nullIfEmpty +import com.looker.droidify.utility.getProductItem import com.looker.droidify.widget.CursorRecyclerAdapter class ProductsAdapter(private val onClick: (ProductItem) -> Unit) : @@ -113,7 +113,7 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit) : } private fun getProductItem(position: Int): ProductItem { - return Database.ProductAdapter.transformItem(moveTo(position)) + return moveTo(position).getProductItem() } override fun onCreateViewHolder( diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt index 70a5dc63..9d5ca363 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt @@ -108,9 +108,9 @@ class ProductsFragment() : BaseFragment(), CursorOwner.Callback { screenActivity.cursorOwner.attach(this, request) repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } .map { it.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (recyclerView?.adapter as? ProductsAdapter)?.repositories = it } diff --git a/src/main/kotlin/com/looker/droidify/screen/RepositoriesAdapter.kt b/src/main/kotlin/com/looker/droidify/screen/RepositoriesAdapter.kt index 245b6f58..aad14873 100644 --- a/src/main/kotlin/com/looker/droidify/screen/RepositoriesAdapter.kt +++ b/src/main/kotlin/com/looker/droidify/screen/RepositoriesAdapter.kt @@ -6,9 +6,9 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.textview.MaterialTextView import com.looker.droidify.R -import com.looker.droidify.database.Database import com.looker.droidify.entity.Repository import com.looker.droidify.utility.extension.resources.inflate +import com.looker.droidify.utility.getRepository import com.looker.droidify.widget.CursorRecyclerAdapter class RepositoriesAdapter( @@ -33,7 +33,7 @@ class RepositoriesAdapter( } private fun getRepository(position: Int): Repository { - return Database.RepositoryAdapter.transform(moveTo(position)) + return moveTo(position).getRepository() } override fun onCreateViewHolder( diff --git a/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt b/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt index 3238025e..c038a0d8 100644 --- a/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt @@ -13,7 +13,6 @@ import androidx.core.widget.NestedScrollView import androidx.lifecycle.lifecycleScope import com.google.android.material.textview.MaterialTextView import com.looker.droidify.R -import com.looker.droidify.database.Database import com.looker.droidify.databinding.TitleTextItemBinding import com.looker.droidify.service.Connection import com.looker.droidify.service.SyncService @@ -104,7 +103,7 @@ class RepositoryFragment() : ScreenFragment() { } private fun updateRepositoryView() { - val repository = Database.RepositoryAdapter.get(repositoryId) + val repository = screenActivity.db.repositoryDao.get(repositoryId)?.data val layout = layout!! layout.removeAllViews() if (repository == null) { @@ -130,7 +129,7 @@ class RepositoryFragment() : ScreenFragment() { if (repository.enabled && (repository.lastModified.isNotEmpty() || repository.entityTag.isNotEmpty())) { layout.addTitleText( R.string.number_of_applications, - Database.ProductAdapter.getCount(repository.id).toString() + screenActivity.db.productDao.countForRepository(repository.id).toString() ) } } else { diff --git a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt index b3d31eab..e157e55c 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt @@ -20,6 +20,7 @@ import coil.load import com.google.android.material.imageview.ShapeableImageView import com.looker.droidify.R import com.looker.droidify.database.Database +import com.looker.droidify.database.DatabaseX import com.looker.droidify.entity.Product import com.looker.droidify.entity.Repository import com.looker.droidify.graphics.PaddingDrawable @@ -68,6 +69,7 @@ class ScreenshotsFragment() : DialogFragment() { val window = dialog.window val decorView = window?.decorView + val db = DatabaseX.getInstance(requireContext()) if (window != null) { WindowCompat.setDecorFitsSystemWindows(window, false) @@ -132,13 +134,17 @@ class ScreenshotsFragment() : DialogFragment() { var restored = false productDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Products)) + .concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } } + .flatMapSingle { + RxUtils.querySingle { + db.productDao.get(packageName).mapNotNull { it?.data } + } + } .map { it -> Pair( it.find { it.repositoryId == repositoryId }, - Database.RepositoryAdapter.get(repositoryId) + db.repositoryDao.get(repositoryId)?.data ) } .observeOn(AndroidSchedulers.mainThread()) diff --git a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt index 2612ce84..101abdc7 100644 --- a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt @@ -227,9 +227,9 @@ class TabsFragment : ScreenFragment() { } categoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Products)) + .concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.CategoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.categoryDao.allNames } } .observeOn(AndroidSchedulers.mainThread()) .subscribe { setSectionsAndUpdate( @@ -238,9 +238,9 @@ class TabsFragment : ScreenFragment() { ) } repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } .observeOn(AndroidSchedulers.mainThread()) .subscribe { it -> setSectionsAndUpdate(null, it.asSequence().filter { it.enabled } diff --git a/src/main/kotlin/com/looker/droidify/service/SyncService.kt b/src/main/kotlin/com/looker/droidify/service/SyncService.kt index 184ea04b..366f2d60 100644 --- a/src/main/kotlin/com/looker/droidify/service/SyncService.kt +++ b/src/main/kotlin/com/looker/droidify/service/SyncService.kt @@ -15,7 +15,7 @@ import androidx.core.app.NotificationCompat import androidx.fragment.app.Fragment import com.looker.droidify.* import com.looker.droidify.content.Preferences -import com.looker.droidify.database.Database +import com.looker.droidify.database.DatabaseX import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository import com.looker.droidify.index.RepositoryUpdater @@ -25,6 +25,7 @@ import com.looker.droidify.utility.extension.android.asSequence import com.looker.droidify.utility.extension.android.notificationManager import com.looker.droidify.utility.extension.resources.getColorFromAttr import com.looker.droidify.utility.extension.text.formatSize +import com.looker.droidify.utility.getProductItem import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable @@ -92,7 +93,7 @@ class SyncService : ConnectionService() { } fun sync(request: SyncRequest) { - val ids = Database.RepositoryAdapter.getAll(null) + val ids = db.repositoryDao.all.mapNotNull { it.data } .asSequence().filter { it.enabled }.map { it.id }.toList() sync(ids, request) } @@ -118,7 +119,7 @@ class SyncService : ConnectionService() { } fun setEnabled(repository: Repository, enabled: Boolean): Boolean { - Database.RepositoryAdapter.put(repository.enable(enabled)) + db.repositoryDao.put(repository.enable(enabled)) if (enabled) { if (repository.id != currentTask?.task?.repositoryId && !tasks.any { it.repositoryId == repository.id }) { tasks += Task(repository.id, true) @@ -137,10 +138,10 @@ class SyncService : ConnectionService() { } fun deleteRepository(repositoryId: Long): Boolean { - val repository = Database.RepositoryAdapter.get(repositoryId) + val repository = db.repositoryDao.get(repositoryId)?.data return repository != null && run { setEnabled(repository, false) - Database.RepositoryAdapter.markAsDeleted(repository.id) + db.repositoryDao.markAsDeleted(repository.id) true } } @@ -148,12 +149,14 @@ class SyncService : ConnectionService() { private val binder = Binder() override fun onBind(intent: Intent): Binder = binder + lateinit var db: DatabaseX private var stateDisposable: Disposable? = null override fun onCreate() { super.onCreate() + db = DatabaseX.getInstance(applicationContext) if (Android.sdk(26)) { NotificationChannel( NOTIFICATION_CHANNEL_SYNCING, @@ -330,7 +333,7 @@ class SyncService : ConnectionService() { if (currentTask == null) { if (tasks.isNotEmpty()) { val task = tasks.removeAt(0) - val repository = Database.RepositoryAdapter.get(task.repositoryId) + val repository = db.repositoryDao.get(task.repositoryId)?.data if (repository != null && repository.enabled) { val lastStarted = started val newStarted = @@ -374,7 +377,7 @@ class SyncService : ConnectionService() { if (hasUpdates && Preferences[Preferences.Key.UpdateNotify]) { val disposable = RxUtils .querySingle { it -> - Database.ProductAdapter + db.productDao .query( installed = true, updates = true, @@ -384,8 +387,7 @@ class SyncService : ConnectionService() { signal = it ) .use { - it.asSequence().map(Database.ProductAdapter::transformItem) - .toList() + it.asSequence().map { it.getProductItem() }.toList() } } .subscribeOn(Schedulers.io()) From cc8e7b6ea3be1707f42440084abcc8bea97b55e0 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:07:21 +0100 Subject: [PATCH 15/67] Clean up (Database.kt) --- .../com/looker/droidify/database/Database.kt | 535 +----------------- 1 file changed, 1 insertion(+), 534 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index 271181db..631bf2a4 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -1,24 +1,14 @@ package com.looker.droidify.database -import android.content.ContentValues import android.content.Context import android.database.Cursor import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import android.os.CancellationSignal -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.looker.droidify.entity.InstalledItem -import com.looker.droidify.entity.Product -import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository import com.looker.droidify.utility.extension.android.asSequence import com.looker.droidify.utility.extension.android.firstOrNull -import com.looker.droidify.utility.extension.json.Json -import com.looker.droidify.utility.extension.json.parseDictionary -import com.looker.droidify.utility.extension.json.writeDictionary import io.reactivex.rxjava3.core.Observable -import java.io.ByteArrayOutputStream object Database { fun init(context: Context): Boolean { @@ -26,7 +16,7 @@ object Database { db = helper.writableDatabase if (helper.created) { for (repository in Repository.defaultRepositories) { - RepositoryAdapter.put(repository) + //RepositoryAdapter.put(repository) } } return helper.created || helper.updated @@ -160,12 +150,6 @@ object Database { $ROW_VERSION_CODE INTEGER NOT NULL """ } - - // TODO find a class to include them as constants - object Synthetic { - const val ROW_CAN_UPDATE = "can_update" - const val ROW_MATCH_RANK = "match_rank" - } } // not needed remove after migration @@ -313,26 +297,6 @@ object Database { } } - // TODO not needed remove after migration (replaced by LiveData) - private fun notifyChanged(vararg subjects: Subject) { - synchronized(observers) { - subjects.asSequence().mapNotNull { observers[it] }.flatten().forEach { it() } - } - } - - // TODO Done through inserts/replace of DAOs, only temporary still not finished - private fun SQLiteDatabase.insertOrReplace( - replace: Boolean, - table: String, - contentValues: ContentValues - ): Long { - return if (replace) replace(table, null, contentValues) else insert( - table, - null, - contentValues - ) - } - private fun SQLiteDatabase.query( table: String, columns: Array? = null, selection: Pair>? = null, orderBy: String? = null, @@ -351,501 +315,4 @@ object Database { signal ) } - - private fun Cursor.observable(subject: Subject): ObservableCursor { - return ObservableCursor(this, dataObservable(subject)) - } - - fun ByteArray.jsonParse(callback: (JsonParser) -> T): T { - return Json.factory.createParser(this).use { it.parseDictionary(callback) } - } - - fun jsonGenerate(callback: (JsonGenerator) -> Unit): ByteArray { - val outputStream = ByteArrayOutputStream() - Json.factory.createGenerator(outputStream).use { it.writeDictionary(callback) } - return outputStream.toByteArray() - } - - // Partially done, only - object RepositoryAdapter { - // Done in put - internal fun putWithoutNotification(repository: Repository, shouldReplace: Boolean): Long { - return db.insertOrReplace(shouldReplace, Schema.Repository.name, ContentValues().apply { - if (shouldReplace) { - put(Schema.Repository.ROW_ID, repository.id) - } - put(Schema.Repository.ROW_ENABLED, if (repository.enabled) 1 else 0) - put(Schema.Repository.ROW_DELETED, 0) - put(Schema.Repository.ROW_DATA, jsonGenerate(repository::serialize)) - }) - } - - // Done - fun put(repository: Repository): Repository { - val shouldReplace = repository.id >= 0L - val newId = putWithoutNotification(repository, shouldReplace) - val id = if (shouldReplace) repository.id else newId - notifyChanged(Subject.Repositories, Subject.Repository(id), Subject.Products) - return if (newId != repository.id) repository.copy(id = newId) else repository - } - - // Done - fun get(id: Long): Repository? { - return db.query( - Schema.Repository.name, - selection = Pair( - "${Schema.Repository.ROW_ID} = ? AND ${Schema.Repository.ROW_DELETED} == 0", - arrayOf(id.toString()) - ) - ) - .use { it.firstOrNull()?.let(::transform) } - } - - // Done - // MAYBE signal has to be considered - fun getAll(signal: CancellationSignal?): List { - return db.query( - Schema.Repository.name, - selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), - signal = signal - ).use { it.asSequence().map(::transform).toList() } - } - - // Done Pair instead - // MAYBE signal has to be considered - fun getAllDisabledDeleted(signal: CancellationSignal?): Set> { - return db.query( - Schema.Repository.name, - columns = arrayOf(Schema.Repository.ROW_ID, Schema.Repository.ROW_DELETED), - selection = Pair( - "${Schema.Repository.ROW_ENABLED} == 0 OR ${Schema.Repository.ROW_DELETED} != 0", - emptyArray() - ), - signal = signal - ).use { - it.asSequence().map { - Pair( - it.getLong(it.getColumnIndex(Schema.Repository.ROW_ID)), - it.getInt(it.getColumnIndex(Schema.Repository.ROW_DELETED)) != 0 - ) - }.toSet() - } - } - - // Done - fun markAsDeleted(id: Long) { - db.update(Schema.Repository.name, ContentValues().apply { - put(Schema.Repository.ROW_DELETED, 1) - }, "${Schema.Repository.ROW_ID} = ?", arrayOf(id.toString())) - notifyChanged(Subject.Repositories, Subject.Repository(id), Subject.Products) - } - - // Done - fun cleanup(pairs: Set>) { - val result = pairs.windowed(10, 10, true).map { - val idsString = it.joinToString(separator = ", ") { it.first.toString() } - val productsCount = db.delete( - Schema.Product.name, - "${Schema.Product.ROW_REPOSITORY_ID} IN ($idsString)", null - ) - val categoriesCount = db.delete( - Schema.Category.name, - "${Schema.Category.ROW_REPOSITORY_ID} IN ($idsString)", null - ) - val deleteIdsString = it.asSequence().filter { it.second } - .joinToString(separator = ", ") { it.first.toString() } - if (deleteIdsString.isNotEmpty()) { - db.delete( - Schema.Repository.name, - "${Schema.Repository.ROW_ID} IN ($deleteIdsString)", - null - ) - } - productsCount != 0 || categoriesCount != 0 - } - if (result.any { it }) { - notifyChanged(Subject.Products) - } - } - - // get the cursor in the specific table. Unnecessary with Room - fun query(signal: CancellationSignal?): Cursor { - return db.query( - Schema.Repository.name, - selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), - signal = signal - ).observable(Subject.Repositories) - } - - // Done - fun transform(cursor: Cursor): Repository { - return cursor.getBlob(cursor.getColumnIndex(Schema.Repository.ROW_DATA)) - .jsonParse { - Repository.deserialize(it).apply { - this.id = cursor.getLong(cursor.getColumnIndex(Schema.Repository.ROW_ID)) - } - } - } - } - - object ProductAdapter { - // Done - fun get(packageName: String, signal: CancellationSignal?): List { - return db.query( - Schema.Product.name, - columns = arrayOf( - Schema.Product.ROW_REPOSITORY_ID, - Schema.Product.ROW_DESCRIPTION, - Schema.Product.ROW_DATA - ), - selection = Pair("${Schema.Product.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), - signal = signal - ).use { it.asSequence().map(::transform).toList() } - } - - // Done - fun getCount(repositoryId: Long): Int { - return db.query( - Schema.Product.name, columns = arrayOf("COUNT (*)"), - selection = Pair( - "${Schema.Product.ROW_REPOSITORY_ID} = ?", - arrayOf(repositoryId.toString()) - ) - ) - .use { it.firstOrNull()?.getInt(0) ?: 0 } - } - - // Done - fun query( - installed: Boolean, updates: Boolean, searchQuery: String, - section: ProductItem.Section, order: ProductItem.Order, signal: CancellationSignal? - ): Cursor { - val builder = QueryBuilder() - - val signatureMatches = """installed.${Schema.Installed.ROW_SIGNATURE} IS NOT NULL AND - product.${Schema.Product.ROW_SIGNATURES} LIKE ('%.' || installed.${Schema.Installed.ROW_SIGNATURE} || '.%') AND - product.${Schema.Product.ROW_SIGNATURES} != ''""" - - builder += """SELECT product.rowid AS _id, product.${Schema.Product.ROW_REPOSITORY_ID}, - product.${Schema.Product.ROW_PACKAGE_NAME}, product.${Schema.Product.ROW_NAME}, - product.${Schema.Product.ROW_SUMMARY}, installed.${Schema.Installed.ROW_VERSION}, - (COALESCE(lock.${Schema.Lock.ROW_VERSION_CODE}, -1) NOT IN (0, product.${Schema.Product.ROW_VERSION_CODE}) AND - product.${Schema.Product.ROW_COMPATIBLE} != 0 AND product.${Schema.Product.ROW_VERSION_CODE} > - COALESCE(installed.${Schema.Installed.ROW_VERSION_CODE}, 0xffffffff) AND $signatureMatches) - AS ${Schema.Synthetic.ROW_CAN_UPDATE}, product.${Schema.Product.ROW_COMPATIBLE}, - product.${Schema.Product.ROW_DATA_ITEM},""" - - if (searchQuery.isNotEmpty()) { - builder += """(((product.${Schema.Product.ROW_NAME} LIKE ? OR - product.${Schema.Product.ROW_SUMMARY} LIKE ?) * 7) | - ((product.${Schema.Product.ROW_PACKAGE_NAME} LIKE ?) * 3) | - (product.${Schema.Product.ROW_DESCRIPTION} LIKE ?)) AS ${Schema.Synthetic.ROW_MATCH_RANK},""" - builder %= List(4) { "%$searchQuery%" } - } else { - builder += "0 AS ${Schema.Synthetic.ROW_MATCH_RANK}," - } - - builder += """MAX((product.${Schema.Product.ROW_COMPATIBLE} AND - (installed.${Schema.Installed.ROW_SIGNATURE} IS NULL OR $signatureMatches)) || - PRINTF('%016X', product.${Schema.Product.ROW_VERSION_CODE})) FROM ${Schema.Product.name} AS product""" - builder += """JOIN ${Schema.Repository.name} AS repository - ON product.${Schema.Product.ROW_REPOSITORY_ID} = repository.${Schema.Repository.ROW_ID}""" - builder += """LEFT JOIN ${Schema.Lock.name} AS lock - ON product.${Schema.Product.ROW_PACKAGE_NAME} = lock.${Schema.Lock.ROW_PACKAGE_NAME}""" - - if (!installed && !updates) { - builder += "LEFT" - } - builder += """JOIN ${Schema.Installed.name} AS installed - ON product.${Schema.Product.ROW_PACKAGE_NAME} = installed.${Schema.Installed.ROW_PACKAGE_NAME}""" - - if (section is ProductItem.Section.Category) { - builder += """JOIN ${Schema.Category.name} AS category - ON product.${Schema.Product.ROW_PACKAGE_NAME} = category.${Schema.Product.ROW_PACKAGE_NAME}""" - } - - builder += """WHERE repository.${Schema.Repository.ROW_ENABLED} != 0 AND - repository.${Schema.Repository.ROW_DELETED} == 0""" - - if (section is ProductItem.Section.Category) { - builder += "AND category.${Schema.Category.ROW_NAME} = ?" - builder %= section.name - } else if (section is ProductItem.Section.Repository) { - builder += "AND product.${Schema.Product.ROW_REPOSITORY_ID} = ?" - builder %= section.id.toString() - } - - if (searchQuery.isNotEmpty()) { - builder += """AND ${Schema.Synthetic.ROW_MATCH_RANK} > 0""" - } - - builder += "GROUP BY product.${Schema.Product.ROW_PACKAGE_NAME} HAVING 1" - - if (updates) { - builder += "AND ${Schema.Synthetic.ROW_CAN_UPDATE}" - } - builder += "ORDER BY" - - if (searchQuery.isNotEmpty()) { - builder += """${Schema.Synthetic.ROW_MATCH_RANK} DESC,""" - } - - when (order) { - ProductItem.Order.NAME -> Unit - ProductItem.Order.DATE_ADDED -> builder += "product.${Schema.Product.ROW_ADDED} DESC," - ProductItem.Order.LAST_UPDATE -> builder += "product.${Schema.Product.ROW_UPDATED} DESC," - }::class - builder += "product.${Schema.Product.ROW_NAME} COLLATE LOCALIZED ASC" - - return builder.query(db, signal).observable(Subject.Products) - } - - // Done - private fun transform(cursor: Cursor): Product { - return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA)) - .jsonParse { - Product.deserialize(it).apply { - this.repositoryId = cursor - .getLong(cursor.getColumnIndex(Schema.Product.ROW_REPOSITORY_ID)) - this.description = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_DESCRIPTION)) - } - } - } - - // Done - fun transformItem(cursor: Cursor): ProductItem { - return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA_ITEM)) - .jsonParse { - ProductItem.deserialize(it).apply { - this.repositoryId = cursor - .getLong(cursor.getColumnIndex(Schema.Product.ROW_REPOSITORY_ID)) - this.packageName = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_PACKAGE_NAME)) - this.name = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_NAME)) - this.summary = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_SUMMARY)) - this.installedVersion = cursor - .getString(cursor.getColumnIndex(Schema.Installed.ROW_VERSION)) - .orEmpty() - this.compatible = cursor - .getInt(cursor.getColumnIndex(Schema.Product.ROW_COMPATIBLE)) != 0 - this.canUpdate = cursor - .getInt(cursor.getColumnIndex(Schema.Synthetic.ROW_CAN_UPDATE)) != 0 - this.matchRank = cursor - .getInt(cursor.getColumnIndex(Schema.Synthetic.ROW_MATCH_RANK)) - } - } - } - } - - // Done - object CategoryAdapter { - // Done - fun getAll(signal: CancellationSignal?): Set { - val builder = QueryBuilder() - - builder += """SELECT DISTINCT category.${Schema.Category.ROW_NAME} - FROM ${Schema.Category.name} AS category - JOIN ${Schema.Repository.name} AS repository - ON category.${Schema.Category.ROW_REPOSITORY_ID} = repository.${Schema.Repository.ROW_ID} - WHERE repository.${Schema.Repository.ROW_ENABLED} != 0 AND - repository.${Schema.Repository.ROW_DELETED} == 0""" - - return builder.query(db, signal).use { - it.asSequence() - .map { it.getString(it.getColumnIndex(Schema.Category.ROW_NAME)) }.toSet() - } - } - } - - // Done - object InstalledAdapter { - // Done - fun get(packageName: String, signal: CancellationSignal?): InstalledItem? { - return db.query( - Schema.Installed.name, - columns = arrayOf( - Schema.Installed.ROW_PACKAGE_NAME, Schema.Installed.ROW_VERSION, - Schema.Installed.ROW_VERSION_CODE, Schema.Installed.ROW_SIGNATURE - ), - selection = Pair("${Schema.Installed.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), - signal = signal - ).use { it.firstOrNull()?.let(::transform) } - } - - // Done in insert - private fun put(installedItem: InstalledItem, notify: Boolean) { - db.insertOrReplace(true, Schema.Installed.name, ContentValues().apply { - put(Schema.Installed.ROW_PACKAGE_NAME, installedItem.packageName) - put(Schema.Installed.ROW_VERSION, installedItem.version) - put(Schema.Installed.ROW_VERSION_CODE, installedItem.versionCode) - put(Schema.Installed.ROW_SIGNATURE, installedItem.signature) - }) - if (notify) { - notifyChanged(Subject.Products) - } - } - - // Done in insert - fun put(installedItem: InstalledItem) = put(installedItem, true) - - // Done in insert - fun putAll(installedItems: List) { - db.beginTransaction() - try { - db.delete(Schema.Installed.name, null, null) - installedItems.forEach { put(it, false) } - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - // Done - fun delete(packageName: String) { - val count = db.delete( - Schema.Installed.name, - "${Schema.Installed.ROW_PACKAGE_NAME} = ?", - arrayOf(packageName) - ) - if (count > 0) { - notifyChanged(Subject.Products) - } - } - - // Unnecessary with Room - private fun transform(cursor: Cursor): InstalledItem { - return InstalledItem( - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_PACKAGE_NAME)), - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_VERSION)), - cursor.getLong(cursor.getColumnIndex(Schema.Installed.ROW_VERSION_CODE)), - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_SIGNATURE)) - ) - } - } - - // Done with some changes in DAOs - object LockAdapter { - // Done in insert (Lock object instead of pair) - private fun put(lock: Pair, notify: Boolean) { - db.insertOrReplace(true, Schema.Lock.name, ContentValues().apply { - put(Schema.Lock.ROW_PACKAGE_NAME, lock.first) - put(Schema.Lock.ROW_VERSION_CODE, lock.second) - }) - if (notify) { - notifyChanged(Subject.Products) - } - } - - // Done in insert (Lock object instead of pair) - fun put(lock: Pair) = put(lock, true) - - // Done in insert (Lock object instead of pair) - fun putAll(locks: List>) { - db.beginTransaction() - try { - db.delete(Schema.Lock.name, null, null) - locks.forEach { put(it, false) } - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - // Done - fun delete(packageName: String) { - db.delete(Schema.Lock.name, "${Schema.Lock.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)) - notifyChanged(Subject.Products) - } - } - - // Done - object UpdaterAdapter { - private val Table.temporaryName: String - get() = "${name}_temporary" - - // Done - fun createTemporaryTable() { - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - db.execSQL(Schema.Product.formatCreateTable(Schema.Product.temporaryName)) - db.execSQL(Schema.Category.formatCreateTable(Schema.Category.temporaryName)) - } - - // Done - fun putTemporary(products: List) { - db.beginTransaction() - try { - for (product in products) { - // Format signatures like ".signature1.signature2." for easier select - val signatures = product.signatures.joinToString { ".$it" } - .let { if (it.isNotEmpty()) "$it." else "" } - db.insertOrReplace(true, Schema.Product.temporaryName, ContentValues().apply { - put(Schema.Product.ROW_REPOSITORY_ID, product.repositoryId) - put(Schema.Product.ROW_PACKAGE_NAME, product.packageName) - put(Schema.Product.ROW_NAME, product.name) - put(Schema.Product.ROW_SUMMARY, product.summary) - put(Schema.Product.ROW_DESCRIPTION, product.description) - put(Schema.Product.ROW_ADDED, product.added) - put(Schema.Product.ROW_UPDATED, product.updated) - put(Schema.Product.ROW_VERSION_CODE, product.versionCode) - put(Schema.Product.ROW_SIGNATURES, signatures) - put(Schema.Product.ROW_COMPATIBLE, if (product.compatible) 1 else 0) - put(Schema.Product.ROW_DATA, jsonGenerate(product::serialize)) - put(Schema.Product.ROW_DATA_ITEM, jsonGenerate(product.item()::serialize)) - }) - for (category in product.categories) { - db.insertOrReplace( - true, - Schema.Category.temporaryName, - ContentValues().apply { - put(Schema.Category.ROW_REPOSITORY_ID, product.repositoryId) - put(Schema.Category.ROW_PACKAGE_NAME, product.packageName) - put(Schema.Category.ROW_NAME, category) - }) - } - } - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - // Done - fun finishTemporary(repository: Repository, success: Boolean) { - if (success) { - db.beginTransaction() - try { - db.delete( - Schema.Product.name, "${Schema.Product.ROW_REPOSITORY_ID} = ?", - arrayOf(repository.id.toString()) - ) - db.delete( - Schema.Category.name, "${Schema.Category.ROW_REPOSITORY_ID} = ?", - arrayOf(repository.id.toString()) - ) - db.execSQL("INSERT INTO ${Schema.Product.name} SELECT * FROM ${Schema.Product.temporaryName}") - db.execSQL("INSERT INTO ${Schema.Category.name} SELECT * FROM ${Schema.Category.temporaryName}") - RepositoryAdapter.putWithoutNotification(repository, true) - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - if (success) { - notifyChanged( - Subject.Repositories, - Subject.Repository(repository.id), - Subject.Products - ) - } - } else { - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - } - } - } } From f966630e8a7c68966aa7962bc42349cab4fd97aa Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Tue, 9 Nov 2021 23:36:50 +0100 Subject: [PATCH 16/67] Fix: Crashes caused by tables' names --- src/main/kotlin/com/looker/droidify/Common.kt | 4 ++-- .../kotlin/com/looker/droidify/database/DAOs.kt | 16 ++++++++-------- .../com/looker/droidify/database/Tables.kt | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/Common.kt b/src/main/kotlin/com/looker/droidify/Common.kt index 6dca68af..a1471b0d 100644 --- a/src/main/kotlin/com/looker/droidify/Common.kt +++ b/src/main/kotlin/com/looker/droidify/Common.kt @@ -30,8 +30,8 @@ const val ROW_MATCH_RANK = "match_rank" const val ROW_REPOSITORY_NAME = "repository" const val ROW_PRODUCT_NAME = "product" const val ROW_CATEGORY_NAME = "category" -const val ROW_INSTALLED_NAME = "memory.installed" -const val ROW_LOCK_NAME = "memory.lock" +const val ROW_INSTALLED_NAME = "memory_installed" +const val ROW_LOCK_NAME = "memory_lock" const val JOB_ID_SYNC = 1 diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 91c05098..774b410d 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -10,7 +10,7 @@ import com.looker.droidify.entity.ProductItem interface BaseDao { - @Insert + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg product: T) @Update(onConflict = OnConflictStrategy.REPLACE) @@ -191,25 +191,25 @@ interface InstalledDao : BaseDao { } } - @Query("SELECT * FROM `memory.installed` WHERE package_name = :packageName") + @Query("SELECT * FROM memory_installed WHERE package_name = :packageName") fun get(packageName: String): Cursor - @Query("DELETE FROM 'memory.installed' WHERE package_name = :packageName") + @Query("DELETE FROM memory_installed WHERE package_name = :packageName") fun delete(packageName: String) } @Dao interface LockDao : BaseDao { - @Query("DELETE FROM 'memory.lock' WHERE package_name = :packageName") + @Query("DELETE FROM memory_lock WHERE package_name = :packageName") fun delete(packageName: String) } @Dao interface ProductTempDao : BaseDao { - @get:Query("SELECT * FROM `product.temporary`") + @get:Query("SELECT * FROM temporary_product") val all: Array - @Query("DELETE FROM `product.temporary`") + @Query("DELETE FROM temporary_product") fun emptyTable() @Insert @@ -249,9 +249,9 @@ interface ProductTempDao : BaseDao { @Dao interface CategoryTempDao : BaseDao { - @get:Query("SELECT * FROM `category.temporary`") + @get:Query("SELECT * FROM temporary_category") val all: Array - @Query("DELETE FROM `category.temporary`") + @Query("DELETE FROM temporary_category") fun emptyTable() } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index 3b881cdc..cda3236e 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -50,7 +50,7 @@ open class Product { var data_item: ProductItem? = null } -@Entity(tableName = "product.temporary") +@Entity(tableName = "temporary_product") class ProductTemp : Product() @Entity(tableName = "category", primaryKeys = ["repository_id", "package_name", "name"]) @@ -60,10 +60,10 @@ open class Category { var name = "" } -@Entity(tableName = "category.temporary") +@Entity(tableName = "temporary_category") class CategoryTemp : Category() -@Entity(tableName = "memory.installed") +@Entity(tableName = "memory_installed") class Installed(pName: String = "") { @PrimaryKey var package_name = pName @@ -73,7 +73,7 @@ class Installed(pName: String = "") { var signature = "" } -@Entity(tableName = "memory.lock") +@Entity(tableName = "memory_lock") class Lock { @PrimaryKey var package_name = "" From 501e46d65733ab0c88637b3aadd4bec8e98b5b17 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Wed, 10 Nov 2021 00:57:24 +0100 Subject: [PATCH 17/67] Update: Remove Database class (4.8/5 in replacing SQLite with Room) --- .../com/looker/droidify/database/Database.kt | 318 ------------------ .../droidify/index/RepositoryUpdater.kt | 3 +- .../droidify/screen/EditRepositoryFragment.kt | 3 +- .../looker/droidify/screen/ProductFragment.kt | 3 +- .../droidify/screen/ProductsFragment.kt | 3 +- .../droidify/screen/ScreenshotsFragment.kt | 3 +- .../looker/droidify/screen/TabsFragment.kt | 5 +- 7 files changed, 7 insertions(+), 331 deletions(-) delete mode 100644 src/main/kotlin/com/looker/droidify/database/Database.kt diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt deleted file mode 100644 index 631bf2a4..00000000 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ /dev/null @@ -1,318 +0,0 @@ -package com.looker.droidify.database - -import android.content.Context -import android.database.Cursor -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper -import android.os.CancellationSignal -import com.looker.droidify.entity.Repository -import com.looker.droidify.utility.extension.android.asSequence -import com.looker.droidify.utility.extension.android.firstOrNull -import io.reactivex.rxjava3.core.Observable - -object Database { - fun init(context: Context): Boolean { - val helper = Helper(context) - db = helper.writableDatabase - if (helper.created) { - for (repository in Repository.defaultRepositories) { - //RepositoryAdapter.put(repository) - } - } - return helper.created || helper.updated - } - - private lateinit var db: SQLiteDatabase - - // not needed with Room - private interface Table { - val memory: Boolean - val innerName: String - val createTable: String - val createIndex: String? - get() = null - - val databasePrefix: String - get() = if (memory) "memory." else "" - - val name: String - get() = "$databasePrefix$innerName" - - fun formatCreateTable(name: String): String { - return "CREATE TABLE $name (${QueryBuilder.trimQuery(createTable)})" - } - - val createIndexPairFormatted: Pair? - get() = createIndex?.let { - Pair( - "CREATE INDEX ${innerName}_index ON $innerName ($it)", - "CREATE INDEX ${name}_index ON $innerName ($it)" - ) - } - } - - private object Schema { - // implemented - object Repository : Table { - const val ROW_ID = "_id" - const val ROW_ENABLED = "enabled" - const val ROW_DELETED = "deleted" - const val ROW_DATA = "data" - - override val memory = false - override val innerName = "repository" - override val createTable = """ - $ROW_ID INTEGER PRIMARY KEY AUTOINCREMENT, - $ROW_ENABLED INTEGER NOT NULL, - $ROW_DELETED INTEGER NOT NULL, - $ROW_DATA BLOB NOT NULL - """ - } - - // implemented - object Product : Table { - const val ROW_REPOSITORY_ID = "repository_id" - const val ROW_PACKAGE_NAME = "package_name" - const val ROW_NAME = "name" - const val ROW_SUMMARY = "summary" - const val ROW_DESCRIPTION = "description" - const val ROW_ADDED = "added" - const val ROW_UPDATED = "updated" - const val ROW_VERSION_CODE = "version_code" - const val ROW_SIGNATURES = "signatures" - const val ROW_COMPATIBLE = "compatible" - const val ROW_DATA = "data" - const val ROW_DATA_ITEM = "data_item" - - override val memory = false - override val innerName = "product" - override val createTable = """ - $ROW_REPOSITORY_ID INTEGER NOT NULL, - $ROW_PACKAGE_NAME TEXT NOT NULL, - $ROW_NAME TEXT NOT NULL, - $ROW_SUMMARY TEXT NOT NULL, - $ROW_DESCRIPTION TEXT NOT NULL, - $ROW_ADDED INTEGER NOT NULL, - $ROW_UPDATED INTEGER NOT NULL, - $ROW_VERSION_CODE INTEGER NOT NULL, - $ROW_SIGNATURES TEXT NOT NULL, - $ROW_COMPATIBLE INTEGER NOT NULL, - $ROW_DATA BLOB NOT NULL, - $ROW_DATA_ITEM BLOB NOT NULL, - PRIMARY KEY ($ROW_REPOSITORY_ID, $ROW_PACKAGE_NAME) - """ - override val createIndex = ROW_PACKAGE_NAME - } - - // implemented - object Category : Table { - const val ROW_REPOSITORY_ID = "repository_id" - const val ROW_PACKAGE_NAME = "package_name" - const val ROW_NAME = "name" - - override val memory = false - override val innerName = "category" - override val createTable = """ - $ROW_REPOSITORY_ID INTEGER NOT NULL, - $ROW_PACKAGE_NAME TEXT NOT NULL, - $ROW_NAME TEXT NOT NULL, - PRIMARY KEY ($ROW_REPOSITORY_ID, $ROW_PACKAGE_NAME, $ROW_NAME) - """ - override val createIndex = "$ROW_PACKAGE_NAME, $ROW_NAME" - } - - // implemented - object Installed : Table { - const val ROW_PACKAGE_NAME = "package_name" - const val ROW_VERSION = "version" - const val ROW_VERSION_CODE = "version_code" - const val ROW_SIGNATURE = "signature" - - override val memory = true - override val innerName = "installed" - override val createTable = """ - $ROW_PACKAGE_NAME TEXT PRIMARY KEY, - $ROW_VERSION TEXT NOT NULL, - $ROW_VERSION_CODE INTEGER NOT NULL, - $ROW_SIGNATURE TEXT NOT NULL - """ - } - - // implemented - object Lock : Table { - const val ROW_PACKAGE_NAME = "package_name" - const val ROW_VERSION_CODE = "version_code" - - override val memory = true - override val innerName = "lock" - override val createTable = """ - $ROW_PACKAGE_NAME TEXT PRIMARY KEY, - $ROW_VERSION_CODE INTEGER NOT NULL - """ - } - } - - // not needed remove after migration - private class Helper(context: Context) : SQLiteOpenHelper(context, "droidify", null, 1) { - var created = false - private set - var updated = false - private set - - override fun onCreate(db: SQLiteDatabase) = Unit - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = - onVersionChange(db) - - override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = - onVersionChange(db) - - private fun onVersionChange(db: SQLiteDatabase) { - handleTables(db, true, Schema.Product, Schema.Category) - this.updated = true - } - - override fun onOpen(db: SQLiteDatabase) { - val create = handleTables(db, false, Schema.Repository) - val updated = handleTables(db, create, Schema.Product, Schema.Category) - db.execSQL("ATTACH DATABASE ':memory:' AS memory") - handleTables(db, false, Schema.Installed, Schema.Lock) - handleIndexes( - db, - Schema.Repository, - Schema.Product, - Schema.Category, - Schema.Installed, - Schema.Lock - ) - dropOldTables(db, Schema.Repository, Schema.Product, Schema.Category) - this.created = this.created || create - this.updated = this.updated || create || updated - } - } - - // not needed remove after migration - private fun handleTables(db: SQLiteDatabase, recreate: Boolean, vararg tables: Table): Boolean { - val shouldRecreate = recreate || tables.any { - val sql = db.query( - "${it.databasePrefix}sqlite_master", columns = arrayOf("sql"), - selection = Pair("type = ? AND name = ?", arrayOf("table", it.innerName)) - ) - .use { it.firstOrNull()?.getString(0) }.orEmpty() - it.formatCreateTable(it.innerName) != sql - } - return shouldRecreate && run { - val shouldVacuum = tables.map { - db.execSQL("DROP TABLE IF EXISTS ${it.name}") - db.execSQL(it.formatCreateTable(it.name)) - !it.memory - } - if (shouldVacuum.any { it } && !db.inTransaction()) { - db.execSQL("VACUUM") - } - true - } - } - - // not needed remove after migration - private fun handleIndexes(db: SQLiteDatabase, vararg tables: Table) { - val shouldVacuum = tables.map { - val sqls = db.query( - "${it.databasePrefix}sqlite_master", columns = arrayOf("name", "sql"), - selection = Pair("type = ? AND tbl_name = ?", arrayOf("index", it.innerName)) - ) - .use { - it.asSequence() - .mapNotNull { it.getString(1)?.let { sql -> Pair(it.getString(0), sql) } } - .toList() - } - .filter { !it.first.startsWith("sqlite_") } - val createIndexes = it.createIndexPairFormatted?.let { listOf(it) }.orEmpty() - createIndexes.map { it.first } != sqls.map { it.second } && run { - for (name in sqls.map { it.first }) { - db.execSQL("DROP INDEX IF EXISTS $name") - } - for (createIndexPair in createIndexes) { - db.execSQL(createIndexPair.second) - } - !it.memory - } - } - if (shouldVacuum.any { it } && !db.inTransaction()) { - db.execSQL("VACUUM") - } - } - - // TODO not needed, remove after migration - private fun dropOldTables(db: SQLiteDatabase, vararg neededTables: Table) { - val tables = db.query( - "sqlite_master", columns = arrayOf("name"), - selection = Pair("type = ?", arrayOf("table")) - ) - .use { it.asSequence().mapNotNull { it.getString(0) }.toList() } - .filter { !it.startsWith("sqlite_") && !it.startsWith("android_") } - .toSet() - neededTables.mapNotNull { if (it.memory) null else it.name } - if (tables.isNotEmpty()) { - for (table in tables) { - db.execSQL("DROP TABLE IF EXISTS $table") - } - if (!db.inTransaction()) { - db.execSQL("VACUUM") - } - } - } - - sealed class Subject { - object Repositories : Subject() - data class Repository(val id: Long) : Subject() - object Products : Subject() - } - - private val observers = mutableMapOf Unit>>() - - - // TODO not needed remove after migration (replaced by LiveData) - private fun dataObservable(subject: Subject): (Boolean, () -> Unit) -> Unit = - { register, observer -> - synchronized(observers) { - val set = observers[subject] ?: run { - val set = mutableSetOf<() -> Unit>() - observers[subject] = set - set - } - if (register) { - set += observer - } else { - set -= observer - } - } - } - - // TODO not needed remove after migration (replaced by LiveData) - fun observable(subject: Subject): Observable { - return Observable.create { - val callback: () -> Unit = { it.onNext(Unit) } - val dataObservable = dataObservable(subject) - dataObservable(true, callback) - it.setCancellable { dataObservable(false, callback) } - } - } - - private fun SQLiteDatabase.query( - table: String, columns: Array? = null, - selection: Pair>? = null, orderBy: String? = null, - signal: CancellationSignal? = null - ): Cursor { - return query( - false, - table, - columns, - selection?.first, - selection?.second, - null, - null, - orderBy, - null, - signal - ) - } -} diff --git a/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt b/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt index 1f0b9aee..d7d1b831 100644 --- a/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt +++ b/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt @@ -3,7 +3,6 @@ package com.looker.droidify.index import android.content.Context import android.net.Uri import com.looker.droidify.content.Cache -import com.looker.droidify.database.Database import com.looker.droidify.database.DatabaseX import com.looker.droidify.entity.Product import com.looker.droidify.entity.Release @@ -66,7 +65,7 @@ object RepositoryUpdater { db = DatabaseX.getInstance(context) var lastDisabled = setOf() Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava + //.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) .flatMapSingle { RxUtils.querySingle { diff --git a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt index ae5376f4..e3cfa1aa 100644 --- a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt @@ -17,7 +17,6 @@ import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.looker.droidify.R -import com.looker.droidify.database.Database import com.looker.droidify.databinding.EditRepositoryBinding import com.looker.droidify.entity.Repository import com.looker.droidify.network.Downloader @@ -240,7 +239,7 @@ class EditRepositoryFragment() : ScreenFragment() { } repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava + //.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } .observeOn(AndroidSchedulers.mainThread()) diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt index 40dcec0e..8ef47e46 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt @@ -18,7 +18,6 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.looker.droidify.R import com.looker.droidify.content.ProductPreferences -import com.looker.droidify.database.Database import com.looker.droidify.entity.* import com.looker.droidify.installer.AppInstaller import com.looker.droidify.service.Connection @@ -133,7 +132,7 @@ class ProductFragment() : ScreenFragment(), ProductAdapter.Callbacks { var first = true productDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava + //.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) .flatMapSingle { RxUtils.querySingle { diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt index 9d5ca363..f6c87243 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt @@ -10,7 +10,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.looker.droidify.R import com.looker.droidify.database.CursorOwner -import com.looker.droidify.database.Database import com.looker.droidify.entity.ProductItem import com.looker.droidify.utility.RxUtils import com.looker.droidify.widget.RecyclerFastScroller @@ -108,7 +107,7 @@ class ProductsFragment() : BaseFragment(), CursorOwner.Callback { screenActivity.cursorOwner.attach(this, request) repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava + //.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } .map { it.asSequence().map { Pair(it.id, it) }.toMap() } diff --git a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt index e157e55c..38978791 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt @@ -19,7 +19,6 @@ import androidx.viewpager2.widget.ViewPager2 import coil.load import com.google.android.material.imageview.ShapeableImageView import com.looker.droidify.R -import com.looker.droidify.database.Database import com.looker.droidify.database.DatabaseX import com.looker.droidify.entity.Product import com.looker.droidify.entity.Repository @@ -134,7 +133,7 @@ class ScreenshotsFragment() : DialogFragment() { var restored = false productDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava + //.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) .flatMapSingle { RxUtils.querySingle { diff --git a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt index 101abdc7..43ea3567 100644 --- a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt @@ -16,7 +16,6 @@ import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.textview.MaterialTextView import com.looker.droidify.R import com.looker.droidify.content.Preferences -import com.looker.droidify.database.Database import com.looker.droidify.databinding.TabsToolbarBinding import com.looker.droidify.entity.ProductItem import com.looker.droidify.service.Connection @@ -227,7 +226,7 @@ class TabsFragment : ScreenFragment() { } categoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava + //.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) .flatMapSingle { RxUtils.querySingle { screenActivity.db.categoryDao.allNames } } .observeOn(AndroidSchedulers.mainThread()) @@ -238,7 +237,7 @@ class TabsFragment : ScreenFragment() { ) } repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava + //.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } .observeOn(AndroidSchedulers.mainThread()) From c6e6a9b0886ae0dddcaf2360b95fbf5e841463ae Mon Sep 17 00:00:00 2001 From: LooKeR Date: Thu, 23 Dec 2021 22:02:53 +0530 Subject: [PATCH 18/67] Upgrade Coroutine Library 1.5.2 -> 1.6.0 Upgrade Gradle 7.3.2 -> 7.3.3 Enable configuration caching --- build.gradle | 4 ++-- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index ec5f9aab..a4c3f477 100644 --- a/build.gradle +++ b/build.gradle @@ -148,8 +148,8 @@ dependencies { // Coroutines / Lifecycle implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' // Room implementation 'androidx.room:room-runtime:2.4.0' diff --git a/gradle.properties b/gradle.properties index 00b46820..b85d5790 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +org.gradle.unsafe.configuration-cache=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cb2384a6..69f2ab82 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Dec 16 21:31:46 IST 2021 +#Thu Dec 23 21:52:45 IST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From 4778f128f27a638763251817b24f2e200eafabb8 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 24 Dec 2021 15:07:45 +0100 Subject: [PATCH 19/67] Fix: Common content calls --- src/main/kotlin/com/looker/droidify/Common.kt | 7 ++----- .../com/looker/droidify/content/Preferences.kt | 4 ++-- .../looker/droidify/installer/InstallerService.kt | 13 +++++++------ 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/Common.kt b/src/main/kotlin/com/looker/droidify/Common.kt index 5f948509..a0dc8668 100644 --- a/src/main/kotlin/com/looker/droidify/Common.kt +++ b/src/main/kotlin/com/looker/droidify/Common.kt @@ -35,8 +35,5 @@ const val ROW_LOCK_NAME = "memory_lock" const val JOB_ID_SYNC = 1 - const val PREFS_LANGUAGE = "languages" - const val PREFS_LANGUAGE_DEFAULT = "system" - - const val JOB_ID_SYNC = 1 -} +const val PREFS_LANGUAGE = "languages" +const val PREFS_LANGUAGE_DEFAULT = "system" diff --git a/src/main/kotlin/com/looker/droidify/content/Preferences.kt b/src/main/kotlin/com/looker/droidify/content/Preferences.kt index 664635bf..3ae8dfa9 100644 --- a/src/main/kotlin/com/looker/droidify/content/Preferences.kt +++ b/src/main/kotlin/com/looker/droidify/content/Preferences.kt @@ -3,8 +3,8 @@ package com.looker.droidify.content import android.content.Context import android.content.SharedPreferences import android.content.res.Configuration -import com.looker.droidify.Common.PREFS_LANGUAGE -import com.looker.droidify.Common.PREFS_LANGUAGE_DEFAULT +import com.looker.droidify.PREFS_LANGUAGE +import com.looker.droidify.PREFS_LANGUAGE_DEFAULT import com.looker.droidify.R import com.looker.droidify.entity.ProductItem import com.looker.droidify.utility.extension.android.Android diff --git a/src/main/kotlin/com/looker/droidify/installer/InstallerService.kt b/src/main/kotlin/com/looker/droidify/installer/InstallerService.kt index 0a6e49ee..fec9dde0 100644 --- a/src/main/kotlin/com/looker/droidify/installer/InstallerService.kt +++ b/src/main/kotlin/com/looker/droidify/installer/InstallerService.kt @@ -7,7 +7,8 @@ import android.content.pm.PackageManager import android.os.IBinder import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat -import com.looker.droidify.Common +import com.looker.droidify.NOTIFICATION_CHANNEL_DOWNLOADING +import com.looker.droidify.NOTIFICATION_ID_DOWNLOADING import com.looker.droidify.R import com.looker.droidify.utility.extension.android.notificationManager import com.looker.droidify.utility.extension.resources.getColorFromAttr @@ -70,7 +71,7 @@ class InstallerService : Service() { // start building val builder = NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING) + .Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING) .setAutoCancel(true) .setColor( ContextThemeWrapper(this, R.style.Theme_Main_Light) @@ -81,7 +82,7 @@ class InstallerService : Service() { PackageInstaller.STATUS_SUCCESS -> { if (installerAction == ACTION_UNINSTALL) // remove any notification for this app - notificationManager.cancel(notificationTag, Common.NOTIFICATION_ID_DOWNLOADING) + notificationManager.cancel(notificationTag, NOTIFICATION_ID_DOWNLOADING) else { val notification = builder .setSmallIcon(android.R.drawable.stat_sys_download_done) @@ -90,11 +91,11 @@ class InstallerService : Service() { .build() notificationManager.notify( notificationTag, - Common.NOTIFICATION_ID_DOWNLOADING, + NOTIFICATION_ID_DOWNLOADING, notification ) Thread.sleep(5000) - notificationManager.cancel(notificationTag, Common.NOTIFICATION_ID_DOWNLOADING) + notificationManager.cancel(notificationTag, NOTIFICATION_ID_DOWNLOADING) } } PackageInstaller.STATUS_FAILURE_ABORTED -> { @@ -109,7 +110,7 @@ class InstallerService : Service() { .build() notificationManager.notify( notificationTag, - Common.NOTIFICATION_ID_DOWNLOADING, + NOTIFICATION_ID_DOWNLOADING, notification ) } From b359475db70bdbb49129a432f893c0815d20bd27 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 24 Dec 2021 15:08:25 +0100 Subject: [PATCH 20/67] Fix: Post merge errors/conflicts --- .../kotlin/com/looker/droidify/content/ProductPreferences.kt | 4 ++-- src/main/kotlin/com/looker/droidify/utility/Utils.kt | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt b/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt index 96c3ee5d..c5f493e4 100644 --- a/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt +++ b/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt @@ -41,11 +41,11 @@ object ProductPreferences { CoroutineScope(Dispatchers.Default).launch { subject.collect { (packageName, versionCode) -> if (versionCode != null) db.lockDao.insert(Lock().apply { - package_name = pName + package_name = packageName version_code = versionCode } ) - else db.lockDao.delete(pName) + else db.lockDao.delete(packageName) } } } diff --git a/src/main/kotlin/com/looker/droidify/utility/Utils.kt b/src/main/kotlin/com/looker/droidify/utility/Utils.kt index 517503d0..e1180598 100644 --- a/src/main/kotlin/com/looker/droidify/utility/Utils.kt +++ b/src/main/kotlin/com/looker/droidify/utility/Utils.kt @@ -4,8 +4,11 @@ import android.content.Context import android.content.pm.PackageInfo import android.content.pm.Signature import android.content.res.Configuration +import android.database.Cursor import android.graphics.drawable.Drawable import android.os.Build +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser import com.looker.droidify.* import com.looker.droidify.content.Preferences import com.looker.droidify.entity.InstalledItem @@ -26,6 +29,7 @@ import com.looker.droidify.utility.extension.text.hex import com.topjohnwu.superuser.Shell import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect +import java.io.ByteArrayOutputStream import java.security.MessageDigest import java.security.cert.Certificate import java.security.cert.CertificateEncodingException From 5db9aa259184389ce3f68bd4950d9273fdbc22f8 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 24 Dec 2021 15:09:15 +0100 Subject: [PATCH 21/67] Remove: Old database class --- .../com/looker/droidify/database/Database.kt | 828 ------------------ .../droidify/ui/activities/MainActivityX.kt | 13 +- .../droidify/ui/fragments/ExploreFragment.kt | 5 +- .../ui/fragments/InstalledFragment.kt | 5 +- .../droidify/ui/fragments/LatestFragment.kt | 5 +- 5 files changed, 14 insertions(+), 842 deletions(-) delete mode 100644 src/main/kotlin/com/looker/droidify/database/Database.kt diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt deleted file mode 100644 index 68f0f965..00000000 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ /dev/null @@ -1,828 +0,0 @@ -package com.looker.droidify.database - -import android.content.ContentValues -import android.content.Context -import android.database.Cursor -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper -import android.os.CancellationSignal -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.looker.droidify.entity.InstalledItem -import com.looker.droidify.entity.Product -import com.looker.droidify.entity.ProductItem -import com.looker.droidify.entity.Repository -import com.looker.droidify.utility.extension.android.asSequence -import com.looker.droidify.utility.extension.android.firstOrNull -import com.looker.droidify.utility.extension.json.Json -import com.looker.droidify.utility.extension.json.parseDictionary -import com.looker.droidify.utility.extension.json.writeDictionary -import io.reactivex.rxjava3.core.Observable -import java.io.ByteArrayOutputStream - -object Database { - fun init(context: Context): Boolean { - val helper = Helper(context) - db = helper.writableDatabase - if (helper.created) { - for (repository in Repository.defaultRepositories) { - RepositoryAdapter.put(repository) - } - } - return helper.created || helper.updated - } - - private lateinit var db: SQLiteDatabase - - private interface Table { - val memory: Boolean - val innerName: String - val createTable: String - val createIndex: String? - get() = null - - val databasePrefix: String - get() = if (memory) "memory." else "" - - val name: String - get() = "$databasePrefix$innerName" - - fun formatCreateTable(name: String): String { - return "CREATE TABLE $name (${QueryBuilder.trimQuery(createTable)})" - } - - val createIndexPairFormatted: Pair? - get() = createIndex?.let { - Pair( - "CREATE INDEX ${innerName}_index ON $innerName ($it)", - "CREATE INDEX ${name}_index ON $innerName ($it)" - ) - } - } - - private object Schema { - object Repository : Table { - const val ROW_ID = "_id" - const val ROW_ENABLED = "enabled" - const val ROW_DELETED = "deleted" - const val ROW_DATA = "data" - - override val memory = false - override val innerName = "repository" - override val createTable = """ - $ROW_ID INTEGER PRIMARY KEY AUTOINCREMENT, - $ROW_ENABLED INTEGER NOT NULL, - $ROW_DELETED INTEGER NOT NULL, - $ROW_DATA BLOB NOT NULL - """ - } - - object Product : Table { - const val ROW_REPOSITORY_ID = "repository_id" - const val ROW_PACKAGE_NAME = "package_name" - const val ROW_NAME = "name" - const val ROW_SUMMARY = "summary" - const val ROW_DESCRIPTION = "description" - const val ROW_ADDED = "added" - const val ROW_UPDATED = "updated" - const val ROW_VERSION_CODE = "version_code" - const val ROW_SIGNATURES = "signatures" - const val ROW_COMPATIBLE = "compatible" - const val ROW_DATA = "data" - const val ROW_DATA_ITEM = "data_item" - - override val memory = false - override val innerName = "product" - override val createTable = """ - $ROW_REPOSITORY_ID INTEGER NOT NULL, - $ROW_PACKAGE_NAME TEXT NOT NULL, - $ROW_NAME TEXT NOT NULL, - $ROW_SUMMARY TEXT NOT NULL, - $ROW_DESCRIPTION TEXT NOT NULL, - $ROW_ADDED INTEGER NOT NULL, - $ROW_UPDATED INTEGER NOT NULL, - $ROW_VERSION_CODE INTEGER NOT NULL, - $ROW_SIGNATURES TEXT NOT NULL, - $ROW_COMPATIBLE INTEGER NOT NULL, - $ROW_DATA BLOB NOT NULL, - $ROW_DATA_ITEM BLOB NOT NULL, - PRIMARY KEY ($ROW_REPOSITORY_ID, $ROW_PACKAGE_NAME) - """ - override val createIndex = ROW_PACKAGE_NAME - } - - object Category : Table { - const val ROW_REPOSITORY_ID = "repository_id" - const val ROW_PACKAGE_NAME = "package_name" - const val ROW_NAME = "name" - - override val memory = false - override val innerName = "category" - override val createTable = """ - $ROW_REPOSITORY_ID INTEGER NOT NULL, - $ROW_PACKAGE_NAME TEXT NOT NULL, - $ROW_NAME TEXT NOT NULL, - PRIMARY KEY ($ROW_REPOSITORY_ID, $ROW_PACKAGE_NAME, $ROW_NAME) - """ - override val createIndex = "$ROW_PACKAGE_NAME, $ROW_NAME" - } - - object Installed : Table { - const val ROW_PACKAGE_NAME = "package_name" - const val ROW_VERSION = "version" - const val ROW_VERSION_CODE = "version_code" - const val ROW_SIGNATURE = "signature" - - override val memory = true - override val innerName = "installed" - override val createTable = """ - $ROW_PACKAGE_NAME TEXT PRIMARY KEY, - $ROW_VERSION TEXT NOT NULL, - $ROW_VERSION_CODE INTEGER NOT NULL, - $ROW_SIGNATURE TEXT NOT NULL - """ - } - - object Lock : Table { - const val ROW_PACKAGE_NAME = "package_name" - const val ROW_VERSION_CODE = "version_code" - - override val memory = true - override val innerName = "lock" - override val createTable = """ - $ROW_PACKAGE_NAME TEXT PRIMARY KEY, - $ROW_VERSION_CODE INTEGER NOT NULL - """ - } - - object Synthetic { - const val ROW_CAN_UPDATE = "can_update" - const val ROW_MATCH_RANK = "match_rank" - } - } - - private class Helper(context: Context) : SQLiteOpenHelper(context, "droidify", null, 1) { - var created = false - private set - var updated = false - private set - - override fun onCreate(db: SQLiteDatabase) = Unit - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = - onVersionChange(db) - - override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = - onVersionChange(db) - - private fun onVersionChange(db: SQLiteDatabase) { - handleTables(db, true, Schema.Product, Schema.Category) - this.updated = true - } - - override fun onOpen(db: SQLiteDatabase) { - val create = handleTables(db, false, Schema.Repository) - val updated = handleTables(db, create, Schema.Product, Schema.Category) - db.execSQL("ATTACH DATABASE ':memory:' AS memory") - handleTables(db, false, Schema.Installed, Schema.Lock) - handleIndexes( - db, - Schema.Repository, - Schema.Product, - Schema.Category, - Schema.Installed, - Schema.Lock - ) - dropOldTables(db, Schema.Repository, Schema.Product, Schema.Category) - this.created = this.created || create - this.updated = this.updated || create || updated - } - } - - private fun handleTables(db: SQLiteDatabase, recreate: Boolean, vararg tables: Table): Boolean { - val shouldRecreate = recreate || tables.any { - val sql = db.query( - "${it.databasePrefix}sqlite_master", columns = arrayOf("sql"), - selection = Pair("type = ? AND name = ?", arrayOf("table", it.innerName)) - ) - .use { it.firstOrNull()?.getString(0) }.orEmpty() - it.formatCreateTable(it.innerName) != sql - } - return shouldRecreate && run { - val shouldVacuum = tables.map { - db.execSQL("DROP TABLE IF EXISTS ${it.name}") - db.execSQL(it.formatCreateTable(it.name)) - !it.memory - } - if (shouldVacuum.any { it } && !db.inTransaction()) { - db.execSQL("VACUUM") - } - true - } - } - - private fun handleIndexes(db: SQLiteDatabase, vararg tables: Table) { - val shouldVacuum = tables.map { - val sqls = db.query( - "${it.databasePrefix}sqlite_master", columns = arrayOf("name", "sql"), - selection = Pair("type = ? AND tbl_name = ?", arrayOf("index", it.innerName)) - ) - .use { - it.asSequence() - .mapNotNull { it.getString(1)?.let { sql -> Pair(it.getString(0), sql) } } - .toList() - } - .filter { !it.first.startsWith("sqlite_") } - val createIndexes = it.createIndexPairFormatted?.let { listOf(it) }.orEmpty() - createIndexes.map { it.first } != sqls.map { it.second } && run { - for (name in sqls.map { it.first }) { - db.execSQL("DROP INDEX IF EXISTS $name") - } - for (createIndexPair in createIndexes) { - db.execSQL(createIndexPair.second) - } - !it.memory - } - } - if (shouldVacuum.any { it } && !db.inTransaction()) { - db.execSQL("VACUUM") - } - } - - private fun dropOldTables(db: SQLiteDatabase, vararg neededTables: Table) { - val tables = db.query( - "sqlite_master", columns = arrayOf("name"), - selection = Pair("type = ?", arrayOf("table")) - ) - .use { it.asSequence().mapNotNull { it.getString(0) }.toList() } - .filter { !it.startsWith("sqlite_") && !it.startsWith("android_") } - .toSet() - neededTables.mapNotNull { if (it.memory) null else it.name } - if (tables.isNotEmpty()) { - for (table in tables) { - db.execSQL("DROP TABLE IF EXISTS $table") - } - if (!db.inTransaction()) { - db.execSQL("VACUUM") - } - } - } - - sealed class Subject { - object Repositories : Subject() - data class Repository(val id: Long) : Subject() - object Products : Subject() - } - - private val observers = mutableMapOf Unit>>() - - private fun dataObservable(subject: Subject): (Boolean, () -> Unit) -> Unit = - { register, observer -> - synchronized(observers) { - val set = observers[subject] ?: run { - val set = mutableSetOf<() -> Unit>() - observers[subject] = set - set - } - if (register) { - set += observer - } else { - set -= observer - } - } - } - - fun observable(subject: Subject): Observable { - return Observable.create { - val callback: () -> Unit = { it.onNext(Unit) } - val dataObservable = dataObservable(subject) - dataObservable(true, callback) - it.setCancellable { dataObservable(false, callback) } - } - } - - private fun notifyChanged(vararg subjects: Subject) { - synchronized(observers) { - subjects.asSequence().mapNotNull { observers[it] }.flatten().forEach { it() } - } - } - - private fun SQLiteDatabase.insertOrReplace( - replace: Boolean, - table: String, - contentValues: ContentValues, - ): Long { - return if (replace) replace(table, null, contentValues) else insert( - table, - null, - contentValues - ) - } - - private fun SQLiteDatabase.query( - table: String, columns: Array? = null, - selection: Pair>? = null, orderBy: String? = null, - signal: CancellationSignal? = null, - ): Cursor { - return query( - false, - table, - columns, - selection?.first, - selection?.second, - null, - null, - orderBy, - null, - signal - ) - } - - private fun Cursor.observable(subject: Subject): ObservableCursor { - return ObservableCursor(this, dataObservable(subject)) - } - - fun ByteArray.jsonParse(callback: (JsonParser) -> T): T { - return Json.factory.createParser(this).use { it.parseDictionary(callback) } - } - - fun jsonGenerate(callback: (JsonGenerator) -> Unit): ByteArray { - val outputStream = ByteArrayOutputStream() - Json.factory.createGenerator(outputStream).use { it.writeDictionary(callback) } - return outputStream.toByteArray() - } - - object RepositoryAdapter { - // Done in put - internal fun putWithoutNotification(repository: Repository, shouldReplace: Boolean): Long { - return db.insertOrReplace(shouldReplace, Schema.Repository.name, ContentValues().apply { - if (shouldReplace) { - put(Schema.Repository.ROW_ID, repository.id) - } - put(Schema.Repository.ROW_ENABLED, if (repository.enabled) 1 else 0) - put(Schema.Repository.ROW_DELETED, 0) - put(Schema.Repository.ROW_DATA, jsonGenerate(repository::serialize)) - }) - } - - // Done - fun put(repository: Repository): Repository { - val shouldReplace = repository.id >= 0L - val newId = putWithoutNotification(repository, shouldReplace) - val id = if (shouldReplace) repository.id else newId - notifyChanged(Subject.Repositories, Subject.Repository(id), Subject.Products) - return if (newId != repository.id) repository.copy(id = newId) else repository - } - - // Done - fun get(id: Long): Repository? { - return db.query( - Schema.Repository.name, - selection = Pair( - "${Schema.Repository.ROW_ID} = ? AND ${Schema.Repository.ROW_DELETED} == 0", - arrayOf(id.toString()) - ) - ) - .use { it.firstOrNull()?.let(::transform) } - } - - // Done - // MAYBE signal has to be considered - fun getAll(signal: CancellationSignal?): List { - return db.query( - Schema.Repository.name, - selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), - signal = signal - ).use { it.asSequence().map(::transform).toList() } - } - - // Done Pair instead - // MAYBE signal has to be considered - fun getAllDisabledDeleted(signal: CancellationSignal?): Set> { - return db.query( - Schema.Repository.name, - columns = arrayOf(Schema.Repository.ROW_ID, Schema.Repository.ROW_DELETED), - selection = Pair( - "${Schema.Repository.ROW_ENABLED} == 0 OR ${Schema.Repository.ROW_DELETED} != 0", - emptyArray() - ), - signal = signal - ).use { - it.asSequence().map { - Pair( - it.getLong(it.getColumnIndex(Schema.Repository.ROW_ID)), - it.getInt(it.getColumnIndex(Schema.Repository.ROW_DELETED)) != 0 - ) - }.toSet() - } - } - - // Done - fun markAsDeleted(id: Long) { - db.update(Schema.Repository.name, ContentValues().apply { - put(Schema.Repository.ROW_DELETED, 1) - }, "${Schema.Repository.ROW_ID} = ?", arrayOf(id.toString())) - notifyChanged(Subject.Repositories, Subject.Repository(id), Subject.Products) - } - - // Done - fun cleanup(pairs: Set>) { - val result = pairs.windowed(10, 10, true).map { - val idsString = it.joinToString(separator = ", ") { it.first.toString() } - val productsCount = db.delete( - Schema.Product.name, - "${Schema.Product.ROW_REPOSITORY_ID} IN ($idsString)", null - ) - val categoriesCount = db.delete( - Schema.Category.name, - "${Schema.Category.ROW_REPOSITORY_ID} IN ($idsString)", null - ) - val deleteIdsString = it.asSequence().filter { it.second } - .joinToString(separator = ", ") { it.first.toString() } - if (deleteIdsString.isNotEmpty()) { - db.delete( - Schema.Repository.name, - "${Schema.Repository.ROW_ID} IN ($deleteIdsString)", - null - ) - } - productsCount != 0 || categoriesCount != 0 - } - if (result.any { it }) { - notifyChanged(Subject.Products) - } - } - - // get the cursor in the specific table. Unnecessary with Room - fun query(signal: CancellationSignal?): Cursor { - return db.query( - Schema.Repository.name, - selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), - signal = signal - ).observable(Subject.Repositories) - } - - // Unnecessary with Room - fun transform(cursor: Cursor): Repository { - return cursor.getBlob(cursor.getColumnIndex(Schema.Repository.ROW_DATA)) - .jsonParse { - Repository.deserialize(it).apply { - this.id = cursor.getLong(cursor.getColumnIndex(Schema.Repository.ROW_ID)) - } - } - } - } - - object ProductAdapter { - // Done - fun get(packageName: String, signal: CancellationSignal?): List { - return db.query( - Schema.Product.name, - columns = arrayOf( - Schema.Product.ROW_REPOSITORY_ID, - Schema.Product.ROW_DESCRIPTION, - Schema.Product.ROW_DATA - ), - selection = Pair("${Schema.Product.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), - signal = signal - ).use { it.asSequence().map(::transform).toList() } - } - - // Done - fun getCount(repositoryId: Long): Int { - return db.query( - Schema.Product.name, columns = arrayOf("COUNT (*)"), - selection = Pair( - "${Schema.Product.ROW_REPOSITORY_ID} = ?", - arrayOf(repositoryId.toString()) - ) - ) - .use { it.firstOrNull()?.getInt(0) ?: 0 } - } - - // Complex left to wiring phase - fun query( - installed: Boolean, updates: Boolean, searchQuery: String, - section: ProductItem.Section, order: ProductItem.Order, signal: CancellationSignal?, - ): Cursor { - val builder = QueryBuilder() - - val signatureMatches = """installed.${Schema.Installed.ROW_SIGNATURE} IS NOT NULL AND - product.${Schema.Product.ROW_SIGNATURES} LIKE ('%.' || installed.${Schema.Installed.ROW_SIGNATURE} || '.%') AND - product.${Schema.Product.ROW_SIGNATURES} != ''""" - - builder += """SELECT product.rowid AS _id, product.${Schema.Product.ROW_REPOSITORY_ID}, - product.${Schema.Product.ROW_PACKAGE_NAME}, product.${Schema.Product.ROW_NAME}, - product.${Schema.Product.ROW_SUMMARY}, installed.${Schema.Installed.ROW_VERSION}, - (COALESCE(lock.${Schema.Lock.ROW_VERSION_CODE}, -1) NOT IN (0, product.${Schema.Product.ROW_VERSION_CODE}) AND - product.${Schema.Product.ROW_COMPATIBLE} != 0 AND product.${Schema.Product.ROW_VERSION_CODE} > - COALESCE(installed.${Schema.Installed.ROW_VERSION_CODE}, 0xffffffff) AND $signatureMatches) - AS ${Schema.Synthetic.ROW_CAN_UPDATE}, product.${Schema.Product.ROW_COMPATIBLE}, - product.${Schema.Product.ROW_DATA_ITEM},""" - - if (searchQuery.isNotEmpty()) { - builder += """(((product.${Schema.Product.ROW_NAME} LIKE ? OR - product.${Schema.Product.ROW_SUMMARY} LIKE ?) * 7) | - ((product.${Schema.Product.ROW_PACKAGE_NAME} LIKE ?) * 3) | - (product.${Schema.Product.ROW_DESCRIPTION} LIKE ?)) AS ${Schema.Synthetic.ROW_MATCH_RANK},""" - builder %= List(4) { "%$searchQuery%" } - } else { - builder += "0 AS ${Schema.Synthetic.ROW_MATCH_RANK}," - } - - builder += """MAX((product.${Schema.Product.ROW_COMPATIBLE} AND - (installed.${Schema.Installed.ROW_SIGNATURE} IS NULL OR $signatureMatches)) || - PRINTF('%016X', product.${Schema.Product.ROW_VERSION_CODE})) FROM ${Schema.Product.name} AS product""" - builder += """JOIN ${Schema.Repository.name} AS repository - ON product.${Schema.Product.ROW_REPOSITORY_ID} = repository.${Schema.Repository.ROW_ID}""" - builder += """LEFT JOIN ${Schema.Lock.name} AS lock - ON product.${Schema.Product.ROW_PACKAGE_NAME} = lock.${Schema.Lock.ROW_PACKAGE_NAME}""" - - if (!installed && !updates) { - builder += "LEFT" - } - builder += """JOIN ${Schema.Installed.name} AS installed - ON product.${Schema.Product.ROW_PACKAGE_NAME} = installed.${Schema.Installed.ROW_PACKAGE_NAME}""" - - if (section is ProductItem.Section.Category) { - builder += """JOIN ${Schema.Category.name} AS category - ON product.${Schema.Product.ROW_PACKAGE_NAME} = category.${Schema.Product.ROW_PACKAGE_NAME}""" - } - - builder += """WHERE repository.${Schema.Repository.ROW_ENABLED} != 0 AND - repository.${Schema.Repository.ROW_DELETED} == 0""" - - if (section is ProductItem.Section.Category) { - builder += "AND category.${Schema.Category.ROW_NAME} = ?" - builder %= section.name - } else if (section is ProductItem.Section.Repository) { - builder += "AND product.${Schema.Product.ROW_REPOSITORY_ID} = ?" - builder %= section.id.toString() - } - - if (searchQuery.isNotEmpty()) { - builder += """AND ${Schema.Synthetic.ROW_MATCH_RANK} > 0""" - } - - builder += "GROUP BY product.${Schema.Product.ROW_PACKAGE_NAME} HAVING 1" - - if (updates) { - builder += "AND ${Schema.Synthetic.ROW_CAN_UPDATE}" - } - builder += "ORDER BY" - - if (searchQuery.isNotEmpty()) { - builder += """${Schema.Synthetic.ROW_MATCH_RANK} DESC,""" - } - - when (order) { - ProductItem.Order.NAME -> Unit - ProductItem.Order.DATE_ADDED -> builder += "product.${Schema.Product.ROW_ADDED} DESC," - ProductItem.Order.LAST_UPDATE -> builder += "product.${Schema.Product.ROW_UPDATED} DESC," - }::class - builder += "product.${Schema.Product.ROW_NAME} COLLATE LOCALIZED ASC" - - return builder.query(db, signal).observable(Subject.Products) - } - - // Unnecessary with Room - private fun transform(cursor: Cursor): Product { - return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA)) - .jsonParse { - Product.deserialize(it).apply { - this.repositoryId = cursor - .getLong(cursor.getColumnIndex(Schema.Product.ROW_REPOSITORY_ID)) - this.description = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_DESCRIPTION)) - } - } - } - - // Unnecessary with Room - fun transformItem(cursor: Cursor): ProductItem { - return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA_ITEM)) - .jsonParse { - ProductItem.deserialize(it).apply { - this.repositoryId = cursor - .getLong(cursor.getColumnIndex(Schema.Product.ROW_REPOSITORY_ID)) - this.packageName = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_PACKAGE_NAME)) - this.name = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_NAME)) - this.summary = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_SUMMARY)) - this.installedVersion = cursor - .getString(cursor.getColumnIndex(Schema.Installed.ROW_VERSION)) - .orEmpty() - this.compatible = cursor - .getInt(cursor.getColumnIndex(Schema.Product.ROW_COMPATIBLE)) != 0 - this.canUpdate = cursor - .getInt(cursor.getColumnIndex(Schema.Synthetic.ROW_CAN_UPDATE)) != 0 - this.matchRank = cursor - .getInt(cursor.getColumnIndex(Schema.Synthetic.ROW_MATCH_RANK)) - } - } - } - } - - object CategoryAdapter { - // Done - fun getAll(signal: CancellationSignal?): Set { - val builder = QueryBuilder() - - builder += """SELECT DISTINCT category.${Schema.Category.ROW_NAME} - FROM ${Schema.Category.name} AS category - JOIN ${Schema.Repository.name} AS repository - ON category.${Schema.Category.ROW_REPOSITORY_ID} = repository.${Schema.Repository.ROW_ID} - WHERE repository.${Schema.Repository.ROW_ENABLED} != 0 AND - repository.${Schema.Repository.ROW_DELETED} == 0""" - - return builder.query(db, signal).use { - it.asSequence() - .map { it.getString(it.getColumnIndex(Schema.Category.ROW_NAME)) }.toSet() - } - } - } - - object InstalledAdapter { - // Done - fun get(packageName: String, signal: CancellationSignal?): InstalledItem? { - return db.query( - Schema.Installed.name, - columns = arrayOf( - Schema.Installed.ROW_PACKAGE_NAME, Schema.Installed.ROW_VERSION, - Schema.Installed.ROW_VERSION_CODE, Schema.Installed.ROW_SIGNATURE - ), - selection = Pair("${Schema.Installed.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), - signal = signal - ).use { it.firstOrNull()?.let(::transform) } - } - - // Done in insert - private fun put(installedItem: InstalledItem, notify: Boolean) { - db.insertOrReplace(true, Schema.Installed.name, ContentValues().apply { - put(Schema.Installed.ROW_PACKAGE_NAME, installedItem.packageName) - put(Schema.Installed.ROW_VERSION, installedItem.version) - put(Schema.Installed.ROW_VERSION_CODE, installedItem.versionCode) - put(Schema.Installed.ROW_SIGNATURE, installedItem.signature) - }) - if (notify) { - notifyChanged(Subject.Products) - } - } - - // Done in insert - fun put(installedItem: InstalledItem) = put(installedItem, true) - - // Done in insert - fun putAll(installedItems: List) { - db.beginTransaction() - try { - db.delete(Schema.Installed.name, null, null) - installedItems.forEach { put(it, false) } - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - // Done - fun delete(packageName: String) { - val count = db.delete( - Schema.Installed.name, - "${Schema.Installed.ROW_PACKAGE_NAME} = ?", - arrayOf(packageName) - ) - if (count > 0) { - notifyChanged(Subject.Products) - } - } - - // Unnecessary with Room - private fun transform(cursor: Cursor): InstalledItem { - return InstalledItem( - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_PACKAGE_NAME)), - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_VERSION)), - cursor.getLong(cursor.getColumnIndex(Schema.Installed.ROW_VERSION_CODE)), - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_SIGNATURE)) - ) - } - } - - object LockAdapter { - // Done in insert (Lock object instead of pair) - private fun put(lock: Pair, notify: Boolean) { - db.insertOrReplace(true, Schema.Lock.name, ContentValues().apply { - put(Schema.Lock.ROW_PACKAGE_NAME, lock.first) - put(Schema.Lock.ROW_VERSION_CODE, lock.second) - }) - if (notify) { - notifyChanged(Subject.Products) - } - } - - // Done in insert (Lock object instead of pair) - fun put(lock: Pair) = put(lock, true) - - // Done in insert (Lock object instead of pair) - fun putAll(locks: List>) { - db.beginTransaction() - try { - db.delete(Schema.Lock.name, null, null) - locks.forEach { put(it, false) } - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - // Done - fun delete(packageName: String) { - db.delete(Schema.Lock.name, "${Schema.Lock.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)) - notifyChanged(Subject.Products) - } - } - - // TODO add temporary tables - object UpdaterAdapter { - private val Table.temporaryName: String - get() = "${name}_temporary" - - fun createTemporaryTable() { - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - db.execSQL(Schema.Product.formatCreateTable(Schema.Product.temporaryName)) - db.execSQL(Schema.Category.formatCreateTable(Schema.Category.temporaryName)) - } - - fun putTemporary(products: List) { - db.beginTransaction() - try { - for (product in products) { - // Format signatures like ".signature1.signature2." for easier select - val signatures = product.signatures.joinToString { ".$it" } - .let { if (it.isNotEmpty()) "$it." else "" } - db.insertOrReplace(true, Schema.Product.temporaryName, ContentValues().apply { - put(Schema.Product.ROW_REPOSITORY_ID, product.repositoryId) - put(Schema.Product.ROW_PACKAGE_NAME, product.packageName) - put(Schema.Product.ROW_NAME, product.name) - put(Schema.Product.ROW_SUMMARY, product.summary) - put(Schema.Product.ROW_DESCRIPTION, product.description) - put(Schema.Product.ROW_ADDED, product.added) - put(Schema.Product.ROW_UPDATED, product.updated) - put(Schema.Product.ROW_VERSION_CODE, product.versionCode) - put(Schema.Product.ROW_SIGNATURES, signatures) - put(Schema.Product.ROW_COMPATIBLE, if (product.compatible) 1 else 0) - put(Schema.Product.ROW_DATA, jsonGenerate(product::serialize)) - put(Schema.Product.ROW_DATA_ITEM, jsonGenerate(product.item()::serialize)) - }) - for (category in product.categories) { - db.insertOrReplace( - true, - Schema.Category.temporaryName, - ContentValues().apply { - put(Schema.Category.ROW_REPOSITORY_ID, product.repositoryId) - put(Schema.Category.ROW_PACKAGE_NAME, product.packageName) - put(Schema.Category.ROW_NAME, category) - }) - } - } - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - fun finishTemporary(repository: Repository, success: Boolean) { - if (success) { - db.beginTransaction() - try { - db.delete( - Schema.Product.name, "${Schema.Product.ROW_REPOSITORY_ID} = ?", - arrayOf(repository.id.toString()) - ) - db.delete( - Schema.Category.name, "${Schema.Category.ROW_REPOSITORY_ID} = ?", - arrayOf(repository.id.toString()) - ) - db.execSQL("INSERT INTO ${Schema.Product.name} SELECT * FROM ${Schema.Product.temporaryName}") - db.execSQL("INSERT INTO ${Schema.Category.name} SELECT * FROM ${Schema.Category.temporaryName}") - RepositoryAdapter.putWithoutNotification(repository, true) - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - if (success) { - notifyChanged( - Subject.Repositories, - Subject.Repository(repository.id), - Subject.Products - ) - } - } else { - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - } - } - } -} diff --git a/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt b/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt index 66c4f6dc..4cd78275 100644 --- a/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt @@ -20,10 +20,10 @@ import androidx.navigation.ui.setupWithNavController import com.google.android.material.appbar.MaterialToolbar import com.looker.droidify.BuildConfig import com.looker.droidify.ContextWrapperX +import com.looker.droidify.MainApplication import com.looker.droidify.R import com.looker.droidify.content.Preferences import com.looker.droidify.database.CursorOwner -import com.looker.droidify.database.Database import com.looker.droidify.database.QueryLoader import com.looker.droidify.databinding.ActivityMainXBinding import com.looker.droidify.installer.AppInstaller @@ -67,6 +67,9 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks } }) + val db + get() = (application as MainApplication).db + lateinit var cursorOwner: CursorOwner private set @@ -264,7 +267,7 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks val request = viewModel.activeRequests[id]!!.request return QueryLoader(this) { when (request) { - is CursorOwner.Request.ProductsAvailable -> Database.ProductAdapter + is CursorOwner.Request.ProductsAvailable -> db.productDao .query( installed = false, updates = false, @@ -273,7 +276,7 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks order = request.order, signal = it ) - is CursorOwner.Request.ProductsInstalled -> Database.ProductAdapter + is CursorOwner.Request.ProductsInstalled -> db.productDao .query( installed = true, updates = false, @@ -282,7 +285,7 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks order = request.order, signal = it ) - is CursorOwner.Request.ProductsUpdates -> Database.ProductAdapter + is CursorOwner.Request.ProductsUpdates -> db.productDao .query( installed = true, updates = true, @@ -291,7 +294,7 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks order = request.order, signal = it ) - is CursorOwner.Request.Repositories -> Database.RepositoryAdapter.query(it) + is CursorOwner.Request.Repositories -> db.repositoryDao.allCursor } } } diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt index 892186fa..f6624136 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt @@ -12,7 +12,6 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.looker.droidify.R import com.looker.droidify.database.CursorOwner -import com.looker.droidify.database.Database import com.looker.droidify.databinding.FragmentExploreXBinding import com.looker.droidify.ui.adapters.AppListAdapter import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX @@ -60,9 +59,9 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback { mainActivityX.attachCursorOwner(this, viewModel.request(source)) repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + //.concatWith(Database.observable(Database.Subject.Repositories)) TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.data } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it } diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt index ea427cc6..4c9aa95f 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt @@ -12,7 +12,6 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.looker.droidify.R import com.looker.droidify.database.CursorOwner -import com.looker.droidify.database.Database import com.looker.droidify.databinding.FragmentInstalledXBinding import com.looker.droidify.ui.adapters.AppListAdapter import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX @@ -58,9 +57,9 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { mainActivityX.attachCursorOwner(this, viewModel.request(source)) repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + //.concatWith(Database.observable(Database.Subject.Repositories)) TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.data } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it } diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt index 778eb7c6..414d6a98 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt @@ -12,7 +12,6 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.looker.droidify.R import com.looker.droidify.database.CursorOwner -import com.looker.droidify.database.Database import com.looker.droidify.databinding.FragmentLatestXBinding import com.looker.droidify.ui.adapters.AppListAdapter import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX @@ -59,9 +58,9 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { mainActivityX.attachCursorOwner(this, viewModel.request(source)) repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + //.concatWith(Database.observable(Database.Subject.Repositories)) TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.data } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it } From 3e4aa20c3db8ebc3121991f8d1b9798f4ce44db0 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 24 Dec 2021 15:10:31 +0100 Subject: [PATCH 22/67] Fix: UNIQUE id failure for Repository --- src/main/kotlin/com/looker/droidify/database/DAOs.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 774b410d..2cff0b6d 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -10,7 +10,7 @@ import com.looker.droidify.entity.ProductItem interface BaseDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) + @Insert fun insert(vararg product: T) @Update(onConflict = OnConflictStrategy.REPLACE) @@ -22,20 +22,23 @@ interface BaseDao { @Dao interface RepositoryDao : BaseDao { + @get:Query("SELECT COUNT(_id) FROM repository") + val count: Int + fun put(repository: com.looker.droidify.entity.Repository): com.looker.droidify.entity.Repository { repository.let { val dbRepo = Repository().apply { - id = it.id + if (it.id >= 0L) id = it.id enabled = if (it.enabled) 1 else 0 deleted = false data = it } - val newId = if (repository.id >= 0L) update(dbRepo).toLong() else returnInsert(dbRepo) + val newId = if (it.id > 0L) update(dbRepo).toLong() else returnInsert(dbRepo) return if (newId != repository.id) repository.copy(id = newId) else repository } } - @Insert(onConflict = OnConflictStrategy.REPLACE) + @Insert fun returnInsert(product: Repository): Long @Query("SELECT * FROM repository WHERE _id = :id and deleted == 0") From 066369e769c589e4f21dd88c6c6e458bb88fc503 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 24 Dec 2021 15:11:44 +0100 Subject: [PATCH 23/67] Add: Initiate repositories in new DB on first start --- src/main/kotlin/com/looker/droidify/database/DatabaseX.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt index 72a10b27..c4cd5baf 100644 --- a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt +++ b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt @@ -5,6 +5,7 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters +import com.looker.droidify.entity.Repository.Companion.defaultRepositories @Database( entities = [ @@ -43,6 +44,11 @@ abstract class DatabaseX : RoomDatabase() { .fallbackToDestructiveMigration() .allowMainThreadQueries() .build() + INSTANCE?.let { instance -> + if (instance.repositoryDao.count == 0) defaultRepositories.forEach { + instance.repositoryDao.put(it) + } + } } return INSTANCE!! } From 88776cbe04a637969675d9e319e247d9969cdb96 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 24 Dec 2021 15:13:07 +0100 Subject: [PATCH 24/67] Clean up --- src/main/kotlin/com/looker/droidify/database/DatabaseX.kt | 2 +- src/main/kotlin/com/looker/droidify/service/SyncService.kt | 1 + .../looker/droidify/ui/viewmodels/MainActivityViewModelX.kt | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt index c4cd5baf..24b1c18d 100644 --- a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt +++ b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt @@ -66,7 +66,7 @@ abstract class DatabaseX : RoomDatabase() { productsCount != 0 || categoriesCount != 0 } } - // Use live objects and observers instead + // TODO Use live objects and observers instead /*if (result.any { it }) { com.looker.droidify.database.Database.notifyChanged(com.looker.droidify.database.Database.Subject.Products) }*/ diff --git a/src/main/kotlin/com/looker/droidify/service/SyncService.kt b/src/main/kotlin/com/looker/droidify/service/SyncService.kt index 2ff49219..4bd9e38d 100644 --- a/src/main/kotlin/com/looker/droidify/service/SyncService.kt +++ b/src/main/kotlin/com/looker/droidify/service/SyncService.kt @@ -96,6 +96,7 @@ class SyncService : ConnectionService() { } } + // TODO fix sync (better management of repositories' ids fun sync(request: SyncRequest) { val ids = db.repositoryDao.all.mapNotNull { it.data } .asSequence().filter { it.enabled }.map { it.id }.toList() diff --git a/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainActivityViewModelX.kt b/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainActivityViewModelX.kt index bc3fb11c..687b4cc8 100644 --- a/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainActivityViewModelX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainActivityViewModelX.kt @@ -2,9 +2,8 @@ package com.looker.droidify.ui.viewmodels import androidx.lifecycle.ViewModel import com.looker.droidify.database.CursorOwner -import com.looker.droidify.ui.activities.MainActivityX -class MainActivityViewModelX() : ViewModel() { +class MainActivityViewModelX : ViewModel() { val activeRequests = mutableMapOf() } \ No newline at end of file From 814052d821902dc5e7e8c95384d689a07a806ee5 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Sun, 26 Dec 2021 00:04:52 +0100 Subject: [PATCH 25/67] Fix: Crash on trying to readd/update data in installed table --- src/main/kotlin/com/looker/droidify/database/DAOs.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 2cff0b6d..623d2cd0 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -13,6 +13,9 @@ interface BaseDao { @Insert fun insert(vararg product: T) + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertReplace(vararg product: T) + @Update(onConflict = OnConflictStrategy.REPLACE) fun update(vararg obj: T): Int @@ -186,7 +189,7 @@ interface CategoryDao : BaseDao { interface InstalledDao : BaseDao { fun put(vararg isntalled: com.looker.droidify.entity.InstalledItem) { isntalled.forEach { - insert(Installed(it.packageName).apply { + insertReplace(Installed(it.packageName).apply { version = it.version version_code = it.versionCode signature = it.signature From 9e022d477d8e373d1da5266a8181f223fc0fe6bf Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Sun, 26 Dec 2021 00:44:15 +0100 Subject: [PATCH 26/67] Fix: Calling the data repository with the real id --- src/main/kotlin/com/looker/droidify/MainApplication.kt | 2 +- src/main/kotlin/com/looker/droidify/database/Tables.kt | 3 +++ .../com/looker/droidify/screen/EditRepositoryFragment.kt | 6 +++--- .../com/looker/droidify/screen/RepositoryFragment.kt | 2 +- .../com/looker/droidify/screen/ScreenshotsFragment.kt | 2 +- src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt | 2 +- src/main/kotlin/com/looker/droidify/service/SyncService.kt | 7 +++---- .../com/looker/droidify/ui/fragments/AppDetailFragment.kt | 2 +- .../com/looker/droidify/ui/fragments/AppListFragment.kt | 2 +- .../com/looker/droidify/ui/fragments/ExploreFragment.kt | 2 +- .../com/looker/droidify/ui/fragments/InstalledFragment.kt | 2 +- .../com/looker/droidify/ui/fragments/LatestFragment.kt | 2 +- 12 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/MainApplication.kt b/src/main/kotlin/com/looker/droidify/MainApplication.kt index 838a3188..62ca454f 100644 --- a/src/main/kotlin/com/looker/droidify/MainApplication.kt +++ b/src/main/kotlin/com/looker/droidify/MainApplication.kt @@ -183,7 +183,7 @@ class MainApplication : Application(), ImageLoaderFactory { } private fun forceSyncAll() { - db.repositoryDao.all.mapNotNull { it.data }.forEach { + db.repositoryDao.all.mapNotNull { it.trueData }.forEach { if (it.lastModified.isNotEmpty() || it.entityTag.isNotEmpty()) { db.repositoryDao.put(it.copy(lastModified = "", entityTag = "")) } diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index cda3236e..5ee964ef 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -21,6 +21,9 @@ class Repository { @ColumnInfo(typeAffinity = ColumnInfo.BLOB) var data: Repository? = null + val trueData: Repository? + get() = data?.copy(id = id) + class IdAndDeleted { @ColumnInfo(name = "_id") var id = 0L diff --git a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt index 3adca521..4286bfc6 100644 --- a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt @@ -153,7 +153,7 @@ class EditRepositoryFragment() : ScreenFragment() { } if (savedInstanceState == null) { - val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.data } + val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.trueData } if (repository == null) { val clipboardManager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -232,7 +232,7 @@ class EditRepositoryFragment() : ScreenFragment() { } lifecycleScope.launch { - val list = screenActivity.db.repositoryDao.all.mapNotNull { it.data } + val list = screenActivity.db.repositoryDao.all.mapNotNull { it.trueData } takenAddresses = list.asSequence().filter { it.id != repositoryId } .flatMap { (it.mirrors + it.address).asSequence() } .map { it.withoutKnownPath }.toSet() @@ -448,7 +448,7 @@ class EditRepositoryFragment() : ScreenFragment() { MessageDialog(MessageDialog.Message.CantEditSyncing).show(childFragmentManager) invalidateState() } else { - val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.data } + val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.trueData } ?.edit(address, fingerprint, authentication) ?: Repository.newRepository(address, fingerprint, authentication) val changedRepository = screenActivity.db.repositoryDao.put(repository) diff --git a/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt b/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt index 82beff60..472e1691 100644 --- a/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt @@ -98,7 +98,7 @@ class RepositoryFragment() : ScreenFragment() { } private fun updateRepositoryView() { - val repository = screenActivity.db.repositoryDao.get(repositoryId)?.data + val repository = screenActivity.db.repositoryDao.get(repositoryId)?.trueData val layout = layout!! layout.removeAllViews() if (repository == null) { diff --git a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt index 93320579..7520df2e 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt @@ -143,7 +143,7 @@ class ScreenshotsFragment() : DialogFragment() { .map { it -> Pair( it.find { it.repositoryId == repositoryId }, - db.repositoryDao.get(repositoryId)?.data + db.repositoryDao.get(repositoryId)?.trueData ) } .observeOn(AndroidSchedulers.mainThread()) diff --git a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt index 86853e10..404395fe 100644 --- a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt @@ -247,7 +247,7 @@ class TabsFragment : ScreenFragment() { repositoriesDisposable = Observable.just(Unit) //.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.trueData } } } .observeOn(AndroidSchedulers.mainThread()) .subscribe { it -> setSectionsAndUpdate(null, it.asSequence().filter { it.enabled } diff --git a/src/main/kotlin/com/looker/droidify/service/SyncService.kt b/src/main/kotlin/com/looker/droidify/service/SyncService.kt index 4bd9e38d..d8e657f1 100644 --- a/src/main/kotlin/com/looker/droidify/service/SyncService.kt +++ b/src/main/kotlin/com/looker/droidify/service/SyncService.kt @@ -96,9 +96,8 @@ class SyncService : ConnectionService() { } } - // TODO fix sync (better management of repositories' ids fun sync(request: SyncRequest) { - val ids = db.repositoryDao.all.mapNotNull { it.data } + val ids = db.repositoryDao.all.mapNotNull { it.trueData } .asSequence().filter { it.enabled }.map { it.id }.toList() sync(ids, request) } @@ -143,7 +142,7 @@ class SyncService : ConnectionService() { } fun deleteRepository(repositoryId: Long): Boolean { - val repository = db.repositoryDao.get(repositoryId)?.data + val repository = db.repositoryDao.get(repositoryId)?.trueData return repository != null && run { setEnabled(repository, false) db.repositoryDao.markAsDeleted(repository.id) @@ -331,7 +330,7 @@ class SyncService : ConnectionService() { if (currentTask == null) { if (tasks.isNotEmpty()) { val task = tasks.removeAt(0) - val repository = db.repositoryDao.get(task.repositoryId)?.data + val repository = db.repositoryDao.get(task.repositoryId)?.trueData if (repository != null && repository.enabled) { val lastStarted = started val newStarted = diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt index 08c3c5ec..8d89612c 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt @@ -138,7 +138,7 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { } .flatMapSingle { products -> RxUtils - .querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } + .querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.trueData } } .map { it -> it.asSequence().map { Pair(it.id, it) }.toMap() .let { diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/AppListFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/AppListFragment.kt index 33735a0c..759e8a26 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/AppListFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/AppListFragment.kt @@ -79,7 +79,7 @@ class AppListFragment() : BaseFragment(), CursorOwner.Callback { repositoriesDisposable = Observable.just(Unit) //.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.trueData } } } .map { it.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (recyclerView?.adapter as? AppListAdapter)?.repositories = it } diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt index f6624136..9a03b185 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt @@ -61,7 +61,7 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback { repositoriesDisposable = Observable.just(Unit) //.concatWith(Database.observable(Database.Subject.Repositories)) TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.data } } } + .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.trueData } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it } diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt index 4c9aa95f..acc819b9 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt @@ -59,7 +59,7 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { repositoriesDisposable = Observable.just(Unit) //.concatWith(Database.observable(Database.Subject.Repositories)) TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.data } } } + .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.trueData } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it } diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt index 414d6a98..0894d728 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt @@ -60,7 +60,7 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { repositoriesDisposable = Observable.just(Unit) //.concatWith(Database.observable(Database.Subject.Repositories)) TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.data } } } + .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.trueData } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it } From f35ef9f265283da0caa82e9c4eed9ec81c55c81b Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Sun, 26 Dec 2021 01:39:19 +0100 Subject: [PATCH 27/67] Add: FastAdapter as dependency --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index b3eebd41..c5549ee9 100644 --- a/build.gradle +++ b/build.gradle @@ -137,6 +137,12 @@ dependencies { // Material3 implementation 'com.google.android.material:material:1.6.0-alpha01' + + // FastAdapter + implementation("com.mikepenz:fastadapter:5.6.0") + implementation("com.mikepenz:fastadapter-extensions-diff:5.6.0") + implementation("com.mikepenz:fastadapter-extensions-binding:5.6.0") + // Coil implementation 'io.coil-kt:coil:1.4.0' From 3f440dff2a358623d3616ec8b0a3a7afb155c9f8 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Sun, 26 Dec 2021 01:39:26 +0100 Subject: [PATCH 28/67] Add: Horizontal and vertical apps' items' classes --- .../com/looker/droidify/ui/items/HAppItem.kt | 40 ++++++++++ .../com/looker/droidify/ui/items/VAppItem.kt | 76 +++++++++++++++++++ src/main/res/layout/item_app_horiz_x.xml | 23 +++++- 3 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/com/looker/droidify/ui/items/HAppItem.kt create mode 100644 src/main/kotlin/com/looker/droidify/ui/items/VAppItem.kt diff --git a/src/main/kotlin/com/looker/droidify/ui/items/HAppItem.kt b/src/main/kotlin/com/looker/droidify/ui/items/HAppItem.kt new file mode 100644 index 00000000..9e804367 --- /dev/null +++ b/src/main/kotlin/com/looker/droidify/ui/items/HAppItem.kt @@ -0,0 +1,40 @@ +package com.looker.droidify.ui.items + +import android.view.LayoutInflater +import android.view.ViewGroup +import coil.load +import coil.transform.RoundedCornersTransformation +import com.looker.droidify.R +import com.looker.droidify.databinding.ItemAppHorizXBinding +import com.looker.droidify.entity.ProductItem +import com.looker.droidify.entity.Repository +import com.looker.droidify.network.CoilDownloader +import com.looker.droidify.utility.Utils +import com.looker.droidify.utility.extension.resources.toPx +import com.mikepenz.fastadapter.binding.AbstractBindingItem + +class HAppItem(val item: ProductItem, val repository: Repository) : + AbstractBindingItem() { + override val type: Int + get() = R.id.fastadapter_item + + override fun createBinding(inflater: LayoutInflater, parent: ViewGroup?) + : ItemAppHorizXBinding = ItemAppHorizXBinding.inflate(inflater, parent, false) + + override fun bindView(binding: ItemAppHorizXBinding, payloads: List) { + val (progressIcon, defaultIcon) = Utils.getDefaultApplicationIcons(binding.icon.context) + + binding.name.text = item.name + binding.icon.load( + CoilDownloader.createIconUri( + binding.icon, item.packageName, + item.icon, item.metadataIcon, repository + ) + ) { + transformations(RoundedCornersTransformation(4.toPx)) + placeholder(progressIcon) + error(defaultIcon) + } + binding.version.text = if (item.canUpdate) item.version else item.installedVersion + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/ui/items/VAppItem.kt b/src/main/kotlin/com/looker/droidify/ui/items/VAppItem.kt new file mode 100644 index 00000000..ea810272 --- /dev/null +++ b/src/main/kotlin/com/looker/droidify/ui/items/VAppItem.kt @@ -0,0 +1,76 @@ +package com.looker.droidify.ui.items + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.res.ResourcesCompat +import coil.load +import coil.transform.RoundedCornersTransformation +import com.looker.droidify.R +import com.looker.droidify.databinding.ItemAppVerticalXBinding +import com.looker.droidify.entity.ProductItem +import com.looker.droidify.entity.Repository +import com.looker.droidify.network.CoilDownloader +import com.looker.droidify.utility.Utils +import com.looker.droidify.utility.extension.resources.getColorFromAttr +import com.looker.droidify.utility.extension.resources.sizeScaled +import com.looker.droidify.utility.extension.resources.toPx +import com.looker.droidify.utility.extension.text.nullIfEmpty +import com.mikepenz.fastadapter.binding.AbstractBindingItem + +class VAppItem(val item: ProductItem, val repository: Repository) : + AbstractBindingItem() { + override val type: Int + get() = R.id.fastadapter_item + + override fun createBinding(inflater: LayoutInflater, parent: ViewGroup?) + : ItemAppVerticalXBinding = ItemAppVerticalXBinding.inflate(inflater, parent, false) + + override fun bindView(binding: ItemAppVerticalXBinding, payloads: List) { + val (progressIcon, defaultIcon) = Utils.getDefaultApplicationIcons(binding.icon.context) + + binding.name.text = item.name + binding.summary.text = + if (item.name == item.summary) "" else item.summary + binding.summary.visibility = + if (binding.summary.text.isNotEmpty()) View.VISIBLE else View.GONE + binding.icon.load( + CoilDownloader.createIconUri( + binding.icon, item.packageName, + item.icon, item.metadataIcon, repository + ) + ) { + transformations(RoundedCornersTransformation(4.toPx)) + placeholder(progressIcon) + error(defaultIcon) + } + binding.status.apply { + if (item.canUpdate) { + text = item.version + if (background == null) { + background = + ResourcesCompat.getDrawable( + binding.root.resources, + R.drawable.background_border, + context.theme + ) + resources.sizeScaled(6).let { setPadding(it, it, it, it) } + backgroundTintList = + context.getColorFromAttr(R.attr.colorSecondaryContainer) + setTextColor(context.getColorFromAttr(R.attr.colorSecondary)) + } + } else { + text = item.installedVersion.nullIfEmpty() ?: item.version + if (background != null) { + setPadding(0, 0, 0, 0) + setTextColor(binding.status.context.getColorFromAttr(android.R.attr.colorControlNormal)) + background = null + } + } + } + val enabled = item.compatible || item.installedVersion.isNotEmpty() + sequenceOf(binding.name, binding.status, binding.summary).forEach { + it.isEnabled = enabled + } + } +} \ No newline at end of file diff --git a/src/main/res/layout/item_app_horiz_x.xml b/src/main/res/layout/item_app_horiz_x.xml index 038d4d4a..a42b2e3b 100644 --- a/src/main/res/layout/item_app_horiz_x.xml +++ b/src/main/res/layout/item_app_horiz_x.xml @@ -23,23 +23,38 @@ android:elevation="8dp" android:src="@drawable/ic_application_default" android:tint="?colorPrimary" - app:layout_constraintBottom_toTopOf="@id/label" + app:layout_constraintBottom_toTopOf="@id/name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintTop_toBottomOf="@id/name" /> \ No newline at end of file From 6b49062093505fa37e9f8d3c265b8e26f96ad9ae Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Sun, 26 Dec 2021 02:40:37 +0100 Subject: [PATCH 29/67] Update: Allow creating V/HAppItems without repo (no icon downloaded) --- .../com/looker/droidify/ui/items/HAppItem.kt | 22 ++++++++++-------- .../com/looker/droidify/ui/items/VAppItem.kt | 23 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/ui/items/HAppItem.kt b/src/main/kotlin/com/looker/droidify/ui/items/HAppItem.kt index 9e804367..5d12bb58 100644 --- a/src/main/kotlin/com/looker/droidify/ui/items/HAppItem.kt +++ b/src/main/kotlin/com/looker/droidify/ui/items/HAppItem.kt @@ -13,7 +13,7 @@ import com.looker.droidify.utility.Utils import com.looker.droidify.utility.extension.resources.toPx import com.mikepenz.fastadapter.binding.AbstractBindingItem -class HAppItem(val item: ProductItem, val repository: Repository) : +class HAppItem(val item: ProductItem, val repository: Repository?) : AbstractBindingItem() { override val type: Int get() = R.id.fastadapter_item @@ -25,15 +25,17 @@ class HAppItem(val item: ProductItem, val repository: Repository) : val (progressIcon, defaultIcon) = Utils.getDefaultApplicationIcons(binding.icon.context) binding.name.text = item.name - binding.icon.load( - CoilDownloader.createIconUri( - binding.icon, item.packageName, - item.icon, item.metadataIcon, repository - ) - ) { - transformations(RoundedCornersTransformation(4.toPx)) - placeholder(progressIcon) - error(defaultIcon) + repository?.let { + binding.icon.load( + CoilDownloader.createIconUri( + binding.icon, item.packageName, + item.icon, item.metadataIcon, repository + ) + ) { + transformations(RoundedCornersTransformation(4.toPx)) + placeholder(progressIcon) + error(defaultIcon) + } } binding.version.text = if (item.canUpdate) item.version else item.installedVersion } diff --git a/src/main/kotlin/com/looker/droidify/ui/items/VAppItem.kt b/src/main/kotlin/com/looker/droidify/ui/items/VAppItem.kt index ea810272..2a9a63b9 100644 --- a/src/main/kotlin/com/looker/droidify/ui/items/VAppItem.kt +++ b/src/main/kotlin/com/looker/droidify/ui/items/VAppItem.kt @@ -18,7 +18,7 @@ import com.looker.droidify.utility.extension.resources.toPx import com.looker.droidify.utility.extension.text.nullIfEmpty import com.mikepenz.fastadapter.binding.AbstractBindingItem -class VAppItem(val item: ProductItem, val repository: Repository) : +class VAppItem(val item: ProductItem, val repository: Repository?) : AbstractBindingItem() { override val type: Int get() = R.id.fastadapter_item @@ -34,15 +34,18 @@ class VAppItem(val item: ProductItem, val repository: Repository) : if (item.name == item.summary) "" else item.summary binding.summary.visibility = if (binding.summary.text.isNotEmpty()) View.VISIBLE else View.GONE - binding.icon.load( - CoilDownloader.createIconUri( - binding.icon, item.packageName, - item.icon, item.metadataIcon, repository - ) - ) { - transformations(RoundedCornersTransformation(4.toPx)) - placeholder(progressIcon) - error(defaultIcon) + + repository?.let { + binding.icon.load( + CoilDownloader.createIconUri( + binding.icon, item.packageName, + item.icon, item.metadataIcon, it + ) + ) { + transformations(RoundedCornersTransformation(4.toPx)) + placeholder(progressIcon) + error(defaultIcon) + } } binding.status.apply { if (item.canUpdate) { From 113cb9864d139a212afd85264d94a3bcc66057fb Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Sun, 26 Dec 2021 02:41:18 +0100 Subject: [PATCH 30/67] Add: New adapters to InstalledFragment --- .../ui/fragments/InstalledFragment.kt | 54 +++++++++++++------ src/main/res/layout/fragment_installed_x.xml | 2 +- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt index acc819b9..c2df2c3b 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt @@ -6,31 +6,37 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager -import com.looker.droidify.R +import androidx.recyclerview.widget.RecyclerView import com.looker.droidify.database.CursorOwner import com.looker.droidify.databinding.FragmentInstalledXBinding -import com.looker.droidify.ui.adapters.AppListAdapter +import com.looker.droidify.entity.ProductItem +import com.looker.droidify.entity.Repository +import com.looker.droidify.ui.items.HAppItem +import com.looker.droidify.ui.items.VAppItem import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX import com.looker.droidify.utility.RxUtils import com.looker.droidify.widget.RecyclerFastScroller +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.adapters.ItemAdapter import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { override val viewModel: MainNavFragmentViewModelX by viewModels() private lateinit var binding: FragmentInstalledXBinding + private val installedItemAdapter = ItemAdapter() + private var installedFastAdapter: FastAdapter? = null + private val updatedItemAdapter = ItemAdapter() + private var updatedFastAdapter: FastAdapter? = null + override val source = Source.INSTALLED + private var repositories: Map = mapOf() private var repositoriesDisposable: Disposable? = null override fun onCreateView( @@ -42,11 +48,22 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { binding = FragmentInstalledXBinding.inflate(inflater, container, false) binding.lifecycleOwner = this - binding.recyclerView.apply { - layoutManager = LinearLayoutManager(context) + installedFastAdapter = FastAdapter.with(installedItemAdapter) + installedFastAdapter?.setHasStableIds(true) + binding.installedRecycler.apply { + layoutManager = LinearLayoutManager(requireContext()) isMotionEventSplittingEnabled = false isVerticalScrollBarEnabled = false - adapter = AppListAdapter { mainActivityX.navigateProduct(it.packageName) } + adapter = installedFastAdapter + RecyclerFastScroller(this) + } + updatedFastAdapter = FastAdapter.with(updatedItemAdapter) + updatedFastAdapter?.setHasStableIds(true) + binding.updatedRecycler.apply { + layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) + isMotionEventSplittingEnabled = false + isVerticalScrollBarEnabled = false + adapter = updatedFastAdapter RecyclerFastScroller(this) } return binding.root @@ -62,7 +79,7 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.trueData } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) - .subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it } + .subscribe { repositories = it } } override fun onDestroyView() { @@ -74,9 +91,16 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { } override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) { - // TODO create app list out of cursor and use those on the different RecycleViews - (binding.recyclerView.adapter as? AppListAdapter)?.apply { - this.cursor = cursor + // TODO get a list instead of the cursor + // TODO use LiveData and observers instead of listeners + val appItemList: List = listOf() + installedItemAdapter.set(appItemList + .map { VAppItem(it, repositories[it.repositoryId]) } + ) + updatedItemAdapter.set(appItemList.filter { it.canUpdate } + .map { HAppItem(it, repositories[it.repositoryId]) } + ) + /* lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { emptyText = when { @@ -87,6 +111,6 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { } } } - } + */ } } diff --git a/src/main/res/layout/fragment_installed_x.xml b/src/main/res/layout/fragment_installed_x.xml index 92aedc5f..9bea4a68 100644 --- a/src/main/res/layout/fragment_installed_x.xml +++ b/src/main/res/layout/fragment_installed_x.xml @@ -130,7 +130,7 @@ app:layout_constraintTop_toBottomOf="@id/modeBar"> Date: Sun, 26 Dec 2021 02:41:34 +0100 Subject: [PATCH 31/67] Add: New adapters to LatestFragment --- .../droidify/ui/fragments/LatestFragment.kt | 57 +++++++++++++------ src/main/res/layout/fragment_latest_x.xml | 6 +- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt index 0894d728..3744bcc7 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt @@ -6,31 +6,37 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager -import com.looker.droidify.R +import androidx.recyclerview.widget.RecyclerView import com.looker.droidify.database.CursorOwner import com.looker.droidify.databinding.FragmentLatestXBinding -import com.looker.droidify.ui.adapters.AppListAdapter +import com.looker.droidify.entity.ProductItem +import com.looker.droidify.entity.Repository +import com.looker.droidify.ui.items.HAppItem +import com.looker.droidify.ui.items.VAppItem import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX import com.looker.droidify.utility.RxUtils import com.looker.droidify.widget.RecyclerFastScroller +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.adapters.ItemAdapter import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { override val viewModel: MainNavFragmentViewModelX by viewModels() private lateinit var binding: FragmentLatestXBinding - override val source = Source.UPDATES + private val updatedItemAdapter = ItemAdapter() + private var updatedFastAdapter: FastAdapter? = null + private val newItemAdapter = ItemAdapter() + private var newFastAdapter: FastAdapter? = null + override val source = Source.AVAILABLE + + private var repositories: Map = mapOf() private var repositoriesDisposable: Disposable? = null override fun onCreateView( @@ -42,12 +48,22 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { binding = FragmentLatestXBinding.inflate(inflater, container, false) binding.lifecycleOwner = this - binding.recyclerView.apply { - id = android.R.id.list - layoutManager = LinearLayoutManager(context) + updatedFastAdapter = FastAdapter.with(updatedItemAdapter) + updatedFastAdapter?.setHasStableIds(true) + binding.updatedRecycler.apply { + layoutManager = LinearLayoutManager(requireContext()) isMotionEventSplittingEnabled = false isVerticalScrollBarEnabled = false - adapter = AppListAdapter { mainActivityX.navigateProduct(it.packageName) } + adapter = updatedFastAdapter + RecyclerFastScroller(this) + } + newFastAdapter = FastAdapter.with(newItemAdapter) + newFastAdapter?.setHasStableIds(true) + binding.newRecycler.apply { + layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) + isMotionEventSplittingEnabled = false + isVerticalScrollBarEnabled = false + adapter = newFastAdapter RecyclerFastScroller(this) } return binding.root @@ -63,7 +79,7 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.trueData } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) - .subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it } + .subscribe { repositories = it } } override fun onDestroyView() { @@ -75,9 +91,16 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { } override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) { - // TODO create app list out of cursor and use those on the different RecycleViews - (binding.recyclerView.adapter as? AppListAdapter)?.apply { - this.cursor = cursor + // TODO get a list instead of the cursor + // TODO use LiveData and observers instead of listeners + val appItemList: List = listOf() + updatedItemAdapter.set(appItemList // .filter { !it.hasOneRelease } + .map { VAppItem(it, repositories[it.repositoryId]) } + ) + newItemAdapter.set(appItemList // .filter { it.hasOneRelease } + .map { HAppItem(it, repositories[it.repositoryId]) } + ) + /* lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { emptyText = when { @@ -88,6 +111,6 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { } } } - } + */ } } diff --git a/src/main/res/layout/fragment_latest_x.xml b/src/main/res/layout/fragment_latest_x.xml index f9b12e7d..271861b7 100644 --- a/src/main/res/layout/fragment_latest_x.xml +++ b/src/main/res/layout/fragment_latest_x.xml @@ -44,12 +44,12 @@ android:layout_marginHorizontal="12dp" android:layout_marginVertical="14dp" android:text="@string/new_applications" - app:layout_constraintBottom_toTopOf="@id/updatedRecycler" + app:layout_constraintBottom_toTopOf="@id/newRecycler" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> Date: Sun, 26 Dec 2021 17:10:20 +0100 Subject: [PATCH 32/67] Added translation using Weblate (Japanese) --- src/main/res/values-ja/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/res/values-ja/strings.xml diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/src/main/res/values-ja/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From dbd43b1a2b458b96e33cf89e15460100d95cdd79 Mon Sep 17 00:00:00 2001 From: LooKeR Date: Fri, 24 Dec 2021 12:36:41 +0530 Subject: [PATCH 33/67] Improve: Better Share menu --- .../ui/fragments/AppDetailFragment.kt | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt index 8d89612c..681c50b8 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt @@ -37,10 +37,11 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.util.* +import kotlin.collections.ArrayList class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { companion object { @@ -456,15 +457,7 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { } else Unit } AppDetailAdapter.Action.SHARE -> { - val sendIntent: Intent = Intent().apply { - this.action = Intent.ACTION_SEND - putExtra( - Intent.EXTRA_TEXT, - "https://www.f-droid.org/packages/${products[0].first.packageName}/" - ) - type = "text/plain" - } - startActivity(Intent.createChooser(sendIntent, null)) + shareIntent(packageName, products[0].first.name) } }::class } @@ -482,6 +475,21 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { } } + private fun shareIntent(packageName: String, appName: String) { + val shareIntent = Intent(Intent.ACTION_SEND) + val extraText = if (Android.sdk(24)) { + "https://www.f-droid.org/${resources.configuration.locales[0].language}/packages/${packageName}/" + } else "https://www.f-droid.org/${resources.configuration.locale.language}/packages/${packageName}/" + + + shareIntent.type = "text/plain" + shareIntent.putExtra(Intent.EXTRA_TITLE, appName) + shareIntent.putExtra(Intent.EXTRA_SUBJECT, appName) + shareIntent.putExtra(Intent.EXTRA_TEXT, extraText) + + startActivity(Intent.createChooser(shareIntent, "Where to Send?")) + } + override fun onPreferenceChanged(preference: ProductPreference) { lifecycleScope.launch { updateButtons(preference) } } From 66075c9e64476db22887f6e817864827a2471f4e Mon Sep 17 00:00:00 2001 From: LooKeR Date: Fri, 24 Dec 2021 12:37:21 +0530 Subject: [PATCH 34/67] Fix: Micro-crash on download cancel Improve: Use provided function --- .../droidify/service/DownloadService.kt | 24 +++++++++++-------- .../looker/droidify/service/SyncService.kt | 5 ++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/service/DownloadService.kt b/src/main/kotlin/com/looker/droidify/service/DownloadService.kt index 327139d5..256a9e9c 100644 --- a/src/main/kotlin/com/looker/droidify/service/DownloadService.kt +++ b/src/main/kotlin/com/looker/droidify/service/DownloadService.kt @@ -23,9 +23,7 @@ import com.looker.droidify.utility.extension.text.* import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.Disposable import kotlinx.coroutines.* -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.* import java.io.File import java.security.MessageDigest import kotlin.math.* @@ -42,7 +40,8 @@ class DownloadService : ConnectionService() { private val downloadState = mutableDownloadState.asSharedFlow() } - val scope = CoroutineScope(Dispatchers.Default) + private val scope = CoroutineScope(Dispatchers.Default) + private val mainDispatcher = Dispatchers.Main class Receiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -150,9 +149,7 @@ class DownloadService : ConnectionService() { .let(notificationManager::createNotificationChannel) } - scope.launch { - downloadState.collect { publishForegroundState(false, it) } - } + downloadState.onEach { publishForegroundState(false, it) }.launchIn(scope) } override fun onDestroy() { @@ -173,7 +170,14 @@ class DownloadService : ConnectionService() { private fun cancelTasks(packageName: String?) { tasks.removeAll { (packageName == null || it.packageName == packageName) && run { - scope.launch { mutableStateSubject.emit(State.Cancel(it.packageName, it.name)) } + scope.launch(mainDispatcher) { + mutableStateSubject.emit( + State.Cancel( + it.packageName, + it.name + ) + ) + } true } } @@ -183,7 +187,7 @@ class DownloadService : ConnectionService() { currentTask?.let { if (packageName == null || it.task.packageName == packageName) { currentTask = null - scope.launch { + scope.launch(mainDispatcher) { mutableStateSubject.emit( State.Cancel( it.task.packageName, @@ -300,7 +304,7 @@ class DownloadService : ConnectionService() { private fun publishSuccess(task: Task) { var consumed = false - scope.launch { + scope.launch(mainDispatcher) { mutableStateSubject.emit(State.Success(task.packageName, task.name, task.release)) consumed = true } diff --git a/src/main/kotlin/com/looker/droidify/service/SyncService.kt b/src/main/kotlin/com/looker/droidify/service/SyncService.kt index d8e657f1..227b76b4 100644 --- a/src/main/kotlin/com/looker/droidify/service/SyncService.kt +++ b/src/main/kotlin/com/looker/droidify/service/SyncService.kt @@ -7,7 +7,6 @@ import android.app.job.JobParameters import android.app.job.JobService import android.content.Intent import android.graphics.Color -import android.os.Build import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan import android.view.ContextThemeWrapper @@ -248,7 +247,7 @@ class SyncService : ConnectionService() { this, 0, Intent(this, this::class.java).setAction(ACTION_CANCEL), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + if (Android.sdk(23)) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_UPDATE_CURRENT @@ -438,7 +437,7 @@ class SyncService : ConnectionService() { 0, Intent(this, MainActivity::class.java) .setAction(MainActivity.ACTION_UPDATES), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + if (Android.sdk(23)) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_UPDATE_CURRENT From fe67eb48de0a548fe03bd3e53207234cb071ee04 Mon Sep 17 00:00:00 2001 From: MrWooltrest Date: Sun, 26 Dec 2021 18:39:48 +0000 Subject: [PATCH 35/67] Translated using Weblate (Russian) Currently translated at 100.0% (174 of 174 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/ru/ --- src/main/res/values-ru/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 3dedb7be..f0a41571 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -178,4 +178,6 @@ Исследуйте Обновить все Установленные приложения + Последние + Сортировать и фильтровать \ No newline at end of file From 298dbb1e7fc5b4f947317273445c73aca2cdaa56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 25 Dec 2021 20:23:28 +0000 Subject: [PATCH 36/67] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (174 of 174 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/nb_NO/ --- src/main/res/values-nb-rNO/strings.xml | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml index 9217da0c..7065118a 100644 --- a/src/main/res/values-nb-rNO/strings.xml +++ b/src/main/res/values-nb-rNO/strings.xml @@ -2,18 +2,18 @@ Finnes allerede Alltid - Svart - Forfatter e-post + Amoled + Utviklerens e-postadresse Utviklerens nettside Endringslogg Kan ikke redigere pakkebrønnen siden den synkroniseres akkurat nå. - Kontrollerer depotet… + Sjekker pakkebrønn Kompilert for avlusing Bekreftelse Kunne ikke laste ned %s - Kobler til… + Kobler til Inneholder ufri media - Slett depotet\? + Vil du slette pakkebrønnen\? Lastet ned %s Laster ned Fingeravtrykk @@ -36,13 +36,13 @@ %s-lisens Lenke kopiert til utklippstavle Listeanimasjoner - Vis liste animasjon på hovedsiden + Skru på listeanimasjoner på hovedsiden Nye versjoner av programmer tilgjengelig - Ingen programmer tilgjengelig + Ingen programmer tilgjengelige Ingen programmer installert Ingen beskrivelse tilgjengelig. Åpne %s\? - Fant ingen slike programmer + Fant ingen samsvarende programmer Gi merknad om nye versjoner av programmer Antall programmer Kun kompatibelt med %s @@ -51,7 +51,7 @@ Tilganger +%d til Innstillinger - Behandler %1$s… + Behandler %1$s Promoterer ufrie nettverkstjenester Tilbudt av %s Mellomtjenervert @@ -94,12 +94,12 @@ Versjon %s Venter på å laste ned… Handlingen mislyktes - Alle programmene dine er av nyeste dato + Alt er av nyeste dato Kjør Vis en merknad når nye versjoner er tilgjengelig Legg til pakkebrønn Bidragsytere - Nedlasting %s… + Laster ned %s Nettverksfeil. Ingen mellomtjener Kun på Wi-Fi @@ -108,14 +108,14 @@ Versjoner Detaljer Manglende funksjoner. - Finner ikke det programmet + Fant ikke programmet Endringer Kunne ikke synkronisere %s Mørk Beskrivelse Doner Rediger pakkebrønn - Vis programversjoner som ikke er kompatible med enheten + Vis programversjoner som er ukompatible med enheten Installert Ugyldig metadata. @@ -166,9 +166,9 @@ Kildekoden er ikke lenger tilgjengelig Oppgraderinger Seneste - Denne pakkebrønnen har ikke blitt brukt enda. Skru den på for å vise programmene i den. - Oppgradering - Språk + Denne pakkebrønnen har ikke blitt brukt enda. Du må skru den på for å vise programmene den tilbyr. + Oppgrader + Språ Personalisering Vis mindre Siste From c9a920705c4e50246166b6e552e04c18a79e331b Mon Sep 17 00:00:00 2001 From: teemue Date: Sun, 26 Dec 2021 18:09:32 +0000 Subject: [PATCH 37/67] Translated using Weblate (Finnish) Currently translated at 97.1% (169 of 174 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/fi/ --- src/main/res/values-fi/strings.xml | 110 ++++++++++++++--------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml index 6d88cd3a..d764bea3 100644 --- a/src/main/res/values-fi/strings.xml +++ b/src/main/res/values-fi/strings.xml @@ -8,56 +8,56 @@ Musta Anti-ominaisuudet Sovellus - Toiminta epäonnistui - Lisää arkisto + Toiminto epäonnistui + Lisää tietovarasto Tätä sovellusta ei löytynyt - Tekijän sähköposti - Kirjoittajan verkkosivusto + Kehittäjän sähköposti + Kehittäjän verkkosivusto Saatavilla Vikojen jäljitin Poista - Pimeä - Ei voitu validoida %s + Tumma + Ei voitu validoida kohdetta %s Käännetty virheenkorjausta varten Vahvistus Yhdistetään… Sisältää ei-vapaata mediaa - Ei voitu ladata %s - Ei voitu synkronoida %s - Opintopisteet + Ei voitu ladata kohdetta %s + Ei voitu synkronoida kohdetta %s + Tekijät Peruuta - Arkistoa ei voi muokata, koska se synkronoidaan juuri nyt. + Tietovarastoa ei voida muokata, koska sen synkronointi on käynnissä. Muutosloki Muutokset Tarkistetaan tietovarastoa… Sormenjälki Väärä tiedostomuoto. - Muokkaa arkistoa + Muokkaa tietovarastoa Ladataan %s… - Lataaminen - Ladattu %s + Ladataan + Kohde %s ladattu Lahjoita Tiedot - Description - Poista arkisto\? - On mainontaa + Kuvaus + Poistetaanko tietovarasto\? + Sisältää mainontaa Ei-vapaita riippuvuuksia - On tietoturva-aukkoja - Virheellinen palvelinvastaus. + Tietoturvassa on haavoittuvuuksia + Virheellinen palvelimen vastaus. HTTP-välityspalvelin - Jätä kaikki uudet versiot huomiotta + Älä huomioi uusia versioita Jätä tämä versio huomiotta Sinun %1$s (API-versio %2$d) ei ole tuettu. %3$s Suurin API-versio on %d. - Vähimmäis API-versio on %d. + Varhaisin API-versio on %d. Puuttuvat ominaisuudet. Tämä versio on vanhempi kuin laitteeseesi asennettu versio. Poista se ensin. Yhteensopimaton versio Yhteensopimattomat versiot Näytä sovellusversiot, jotka eivät ole yhteensopivia laitteen kanssa - Yhteensopimaton %s + Yhteensopimaton kohteen %s kanssa Asenna - Asennuksen tyypit + Asennustyypit Asennettu Ei voitu tarkistaa eheyttä. Virheellinen osoite @@ -66,74 +66,74 @@ Virheelliset käyttöoikeudet. Virheellinen allekirjoitus. Virheellinen käyttäjänimen muoto - Laukaisu + Avaa Lisenssi %s -lisenssi - Valo + Vaalea Linkki kopioitu leikepöydälle Linkit Lista-animaatiot Näytä listan animaatio pääsivulla Yhdistäminen %s Nimi - Verkko virhe. - Koskaan - Sovellusten uudet versiot saatavilla + Verkkovirhe. + Ei koskaan + Uusia sovellusten versioita saatavilla - %d -sovelluksella on uusi versio. + %d -sovelluksesta on uusi versio. %d -sovelluksia, joilla on uusia versioita. - Ei käytettävissä olevia sovelluksia + Ei sovelluksia saatavilla Ei asennettuja sovelluksia Kuvausta ei ole saatavilla. - Ei löytynyt tällaisia sovelluksia + Haulla ei löytynyt sovelluksia Toimittanut %s - Proxy-isäntä - Proxy portti - Proxy tyyppi + Välityspalvelimen osoite + Välityspalvelimen portti + Välityspalvelimen tyyppi Äskettäin päivitetty Tietovarastot - Varasto + Tietovarasto Vaatii %s Hiljainen asennus Salli pääkäyttäjän oikeudet hiljaisiin asennuksiin Tallenna Tallennetaan tietoja… Kuvakaappaukset - Vain Wi-Fi - Avaa %s\? + Vain Wi-Fi-yhteydellä + Avataanko %s\? Muut Indeksitiedostoa ei voitu analysoida. Salasana Salasana puuttuu - Luvat + Käyttöoikeudet +%d lisää Asetukset - Käsitellään %1$s… - Hankkeen verkkosivusto - Edistää muita kuin ilmaisia verkkopalveluja + Käsitellään kohdetta %1$s… + Verkkosivusto + Edistää ei-vapaita verkkopalveluja Haku - Valitse peili - Ei proxy + Valitse peilipalvelin + Ei välityspalvelinta Ilmoita sovellusten uusista versioista Näytä ilmoitus, kun uusia versioita on saatavilla - Yhteensopiva vain %s - Osake + Yhteensopiva vain arkkitehtuurilla %s + Jaa Näytä lisää Näytä vanhemmat versiot - OKEI - Seuraa toimintaasi tai raportoi siitä + OK + Seuraa tai raportoi toimintaasi Poista asennus Tuntematon Tuntematon virhe. Tuntematon: %s - Merkitsemätön + Allekirjoittamaton Epävakaat päivitykset Vahvistamaton Alkuperäinen lähdekoodi ei ole vapaa Verkkosivut Mitä uutta - Odotan latauksen aloittamista… + Odotetaan latauksen aloittamista… Käyttäjätunnus puuttuu Käyttäjänimi Allekirjoitus %s @@ -141,12 +141,12 @@ Koko Ohita SOCKS-välityspalvelin - Lajittelu järjestys + Lajittelujärjestys Lähdekoodi Lähdekoodi ei enää saatavilla Suositeltu - Synkronoi arkistot - Synkronoi arkistot automaattisesti + Synkronoi tietovarastot + Synkronoi tietovarastot automaattisesti Synkronointi Synkronoidaan %s… Teemat @@ -157,13 +157,13 @@ Päivitys Päivitykset Edistää ei-vapaita ohjelmia - Tätä arkistoa ei ole vielä käytetty. Ota se käyttöön nähdäksesi siinä olevat sovellukset. - Proxy - Allekirjoittamaton. Sovellusluetteloa ei voitu tarkistaa. Ole varovainen ladatessasi sovelluksia allekirjoittamattomista arkistoista. + Tätä tietovarastoa ei ole vielä käytetty. Ota se käyttöön nähdäksesi siinä olevat sovellukset. + Välityspalvelin + Allekirjoittamaton. Sovellusluetteloa ei voitu varmistaa. Ole varovainen ladatessasi sovelluksia allekirjoittamattomista tietovarastoista. Versio %s Indeksiä ei voitu validoida. Ehdota epävakaiden versioiden asentamista - Tämä versio on allekirjoitettu eri varmenteella kuin laitteeseesi asennettu varmenne. Poista se ensin. + Tämä versio on allekirjoitettu eri varmenteella kuin laitteellesi asennettu versio. Poista se ensin. Alustasi %1$s ei ole tuettu. Tuetut alustat: %2$s. Versiot Versio From ef2268f811d32e6d0b5149b317bc87102bccc323 Mon Sep 17 00:00:00 2001 From: MrWooltrest Date: Sun, 26 Dec 2021 18:42:05 +0000 Subject: [PATCH 38/67] Translated using Weblate (Ukrainian) Currently translated at 100.0% (174 of 174 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/uk/ --- src/main/res/values-uk/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 6ddeacc2..ed11c5da 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -177,4 +177,6 @@ Оновити все Встановлені додатки Нові додатки + Останні + Сортувати та фільтрувати \ No newline at end of file From 80c50af6c91e67d45be26600503fc24b4e5f59fa Mon Sep 17 00:00:00 2001 From: Name Protected Date: Sun, 26 Dec 2021 11:28:34 +0000 Subject: [PATCH 39/67] Translated using Weblate (Czech) Currently translated at 47.1% (82 of 174 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/cs/ --- src/main/res/values-cs/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 37092627..cf2a3d15 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -81,4 +81,9 @@ Odkaz zkopírován do schránky Typy instalací Odkazy + + %d aplikace má novou verzi. + + + \ No newline at end of file From ad51a48d0299dfbc530db867df7f7a0b70e7d23e Mon Sep 17 00:00:00 2001 From: aorinngoDo Date: Sun, 26 Dec 2021 16:15:19 +0000 Subject: [PATCH 40/67] Translated using Weblate (Japanese) Currently translated at 64.9% (113 of 174 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/ja/ --- src/main/res/values-ja/strings.xml | 118 ++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index a6b3daec..b67ab627 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -1,2 +1,118 @@ - \ No newline at end of file + + 作者のウェブサイト + 操作に失敗しました + リポジトリ追加 + すべてのアプリが最新です + 既に存在しています + 常に + ブラック + 好ましくない可能性のある機能 + アプリ + 利用可能 + バグトラッカー + 現在同期中のため,リポジトリを編集できません。 + 変更内容 + 確認 + 接続中… + 非フリーのメディアを含みます + %s をダウンロードできませんでした + %s を同期できませんでした + %s を確認できませんでした + クレジット + ダーク + リポジトリを削除しますか\? + 説明 + 詳細 + %s をダウンロードしました + ダウンロード中 + %s をダウンロード中… + 無効なファイル形式です。 + 電子指紋 + 非フリーな依存関係を含みます + セキュリティ上の危険性を含みます + 無効なサーバー応答です。 + HTTPプロキシ + 不足している機能。 + 最大のAPIバージョンは %d です。 + 最小のAPIバージョンは %d です。 + 互換性のないバージョン + 互換性のないバージョン + インストール + 無効なアドレス + 無効な電子指紋形式 + 無効なパーミッションです。 + 無効なメタデータです。 + 無効な署名です。 + 無効なユーザー名形式 + ライセンス + ライト + リンク + リストアニメーション + メインページにリストアニメーションを表示 + %s をマージ + 名前 + ネットワーク エラー。 + なし + アプリの新バージョンが利用可能 + 利用可能なアプリはありません + インストール済みのアプリはありません + 説明がありません。 + プロキシなし + アプリの新バージョンを通知 + アプリの数 + OK + Wi-Fiのみ + %s を開きますか\? + その他 + インデックスファイルを解析できませんでした。 + パスワード + パスワードがありません + + %d 詳細 + %1$s を処理中… + 非フリーなネットワークサービスを推奨 + 非フリーなソフトウェアを推奨 + %s 提供 + プロキシホスト + プロキシポート + プロキシタイプ + 新規更新 + リポジトリ + アドレス + すべてのアプリ + アプリを発見できませんでした + キャンセル + リポジトリを編集 + 作者のメールアドレス + 更新履歴 + リポジトリのチェック中です… + 削除 + 寄付 + デバッグ用にコンパイル済み + 広告を含みます + このバージョンを無視 + すべての新バージョンを無視 + これはあなたのデバイスにインストールされているバージョンより古いバージョンです。先にアンインストールしてください。 + お使いの %1$s (APIバージョン %2$d) はサポートされていません。%3$s + インストール済み + 整合性を確認できませんでした。 + あなたの %1$s プラットフォームはサポート外です。サポートされているプラットフォーム: %2$s 。 + このバージョンは,デバイスにインストールされているアプリの証明書とは異なる証明書で署名されています。先にアンインストールしてください。 + デバイスと互換性のないアプリケーションのバージョンを表示 + %s とは互換性がありません + リンクがクリップボードにコピーされました + + %d 個のアプリで新バージョンが利用可能です。 + + 新しいバージョンが利用可能なときに通知を表示します + 権限 + インストールの種類 + %s ライセンス + 起動 + %s とのみ互換性があります + そのようなアプリは見つかりませんでした + 設定 + プロジェクトのウェブサイト + プロキシ + リポジトリ + \ No newline at end of file From b67a5fdc9fb7b78d66a2ab8c4e87f8db1167e646 Mon Sep 17 00:00:00 2001 From: LooKeR Date: Tue, 28 Dec 2021 13:22:08 +0530 Subject: [PATCH 41/67] Add: Standard Lib for Kotlin --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index c5549ee9..30f4ec23 100644 --- a/build.gradle +++ b/build.gradle @@ -125,6 +125,7 @@ repositories { dependencies { // Core + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10' implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'androidx.appcompat:appcompat-resources:1.4.0' From 25fac9e77337c349a0581aec701d87a8214776ab Mon Sep 17 00:00:00 2001 From: LooKeR Date: Tue, 28 Dec 2021 13:24:04 +0530 Subject: [PATCH 42/67] Improve: Added Sync automatically only when on wifi and plugged in --- .../com/looker/droidify/MainApplication.kt | 75 +++++++++++++------ .../looker/droidify/content/Preferences.kt | 9 ++- .../droidify/screen/SettingsFragment.kt | 2 +- src/main/res/values/strings.xml | 1 + 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/MainApplication.kt b/src/main/kotlin/com/looker/droidify/MainApplication.kt index 62ca454f..37ba2ba6 100644 --- a/src/main/kotlin/com/looker/droidify/MainApplication.kt +++ b/src/main/kotlin/com/looker/droidify/MainApplication.kt @@ -5,6 +5,7 @@ import android.app.Application import android.app.job.JobInfo import android.app.job.JobScheduler import android.content.* +import android.os.BatteryManager import coil.ImageLoader import coil.ImageLoaderFactory import com.looker.droidify.content.Cache @@ -21,10 +22,11 @@ import com.looker.droidify.utility.Utils.toInstalledItem import com.looker.droidify.utility.extension.android.Android import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import java.net.InetSocketAddress import java.net.Proxy +import kotlin.time.Duration.Companion.hours + @Suppress("unused") class MainApplication : Application(), ImageLoaderFactory { @@ -131,36 +133,63 @@ class MainApplication : Application(), ImageLoaderFactory { if (reschedule) { val autoSync = Preferences[Preferences.Key.AutoSync] when (autoSync) { - Preferences.AutoSync.Never -> { + is Preferences.AutoSync.Never -> { jobScheduler.cancel(JOB_ID_SYNC) } - Preferences.AutoSync.Wifi, Preferences.AutoSync.Always -> { - val period = 12 * 60 * 60 * 1000L // 12 hours - val wifiOnly = autoSync == Preferences.AutoSync.Wifi - jobScheduler.schedule(JobInfo - .Builder( - JOB_ID_SYNC, - ComponentName(this, SyncService.Job::class.java) + is Preferences.AutoSync.Wifi -> { + autoSync( + jobScheduler = jobScheduler, + connectionType = JobInfo.NETWORK_TYPE_UNMETERED + ) + } + is Preferences.AutoSync.WifiBattery -> { + if (isCharging(this)) { + autoSync( + jobScheduler = jobScheduler, + connectionType = JobInfo.NETWORK_TYPE_UNMETERED ) - .setRequiredNetworkType(if (wifiOnly) JobInfo.NETWORK_TYPE_UNMETERED else JobInfo.NETWORK_TYPE_ANY) - .apply { - if (Android.sdk(26)) { - setRequiresBatteryNotLow(true) - setRequiresStorageNotLow(true) - } - if (Android.sdk(24)) { - setPeriodic(period, JobInfo.getMinFlexMillis()) - } else { - setPeriodic(period) - } - } - .build()) + } Unit } + is Preferences.AutoSync.Always -> { + autoSync( + jobScheduler = jobScheduler, + connectionType = JobInfo.NETWORK_TYPE_ANY + ) + } }::class.java } } + private fun autoSync(jobScheduler: JobScheduler, connectionType: Int) { + val period = 12.hours.inWholeMilliseconds + jobScheduler.schedule( + JobInfo + .Builder( + JOB_ID_SYNC, + ComponentName(this, SyncService.Job::class.java) + ) + .setRequiredNetworkType(connectionType) + .apply { + if (Android.sdk(26)) { + setRequiresBatteryNotLow(true) + setRequiresStorageNotLow(true) + } + if (Android.sdk(24)) setPeriodic(period, JobInfo.getMinFlexMillis()) + else setPeriodic(period) + } + .build() + ) + } + + private fun isCharging(context: Context): Boolean { + val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + val plugged = intent!!.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB + || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS + } + private fun updateProxy() { val type = Preferences[Preferences.Key.ProxyType].proxyType val host = Preferences[Preferences.Key.ProxyHost] @@ -178,7 +207,7 @@ class MainApplication : Application(), ImageLoaderFactory { } } } - val proxy = socketAddress?.let { Proxy(type, socketAddress) } + val proxy = socketAddress?.let { Proxy(type, it) } Downloader.proxy = proxy } diff --git a/src/main/kotlin/com/looker/droidify/content/Preferences.kt b/src/main/kotlin/com/looker/droidify/content/Preferences.kt index 3ae8dfa9..fd377550 100644 --- a/src/main/kotlin/com/looker/droidify/content/Preferences.kt +++ b/src/main/kotlin/com/looker/droidify/content/Preferences.kt @@ -38,8 +38,10 @@ object Preferences { fun init(context: Context) { preferences = - context.getSharedPreferences("${context.packageName}_preferences", - Context.MODE_PRIVATE) + context.getSharedPreferences( + "${context.packageName}_preferences", + Context.MODE_PRIVATE + ) preferences.registerOnSharedPreferenceChangeListener { _, keyString -> CoroutineScope(Dispatchers.Default).launch { keys[keyString]?.let { @@ -164,10 +166,11 @@ object Preferences { sealed class AutoSync(override val valueString: String) : Enumeration { override val values: List - get() = listOf(Never, Wifi, Always) + get() = listOf(Never, Wifi, WifiBattery, Always) object Never : AutoSync("never") object Wifi : AutoSync("wifi") + object WifiBattery : AutoSync("wifi-battery") object Always : AutoSync("always") } diff --git a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt index d6fa1e5a..32efdc09 100644 --- a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt @@ -33,7 +33,6 @@ import com.looker.droidify.utility.Utils.languagesList import com.looker.droidify.utility.Utils.translateLocale import com.looker.droidify.utility.extension.resources.* import com.topjohnwu.superuser.Shell -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch class SettingsFragment : ScreenFragment() { @@ -100,6 +99,7 @@ class SettingsFragment : ScreenFragment() { when (it) { Preferences.AutoSync.Never -> getString(R.string.never) Preferences.AutoSync.Wifi -> getString(R.string.only_on_wifi) + Preferences.AutoSync.WifiBattery -> getString(R.string.only_on_wifi_and_battery) Preferences.AutoSync.Always -> getString(R.string.always) } } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 83f48423..12827b55 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -99,6 +99,7 @@ OK Only compatible with %s Only on Wi-Fi + Only on Wi-Fi and Plugged-In Open %s? Other Could not parse the index file. From fc9529a3898cc0fa88654fbdbd560afe446ef898 Mon Sep 17 00:00:00 2001 From: LooKeR Date: Tue, 28 Dec 2021 13:27:20 +0530 Subject: [PATCH 43/67] Fix: Error for "QUERY_ALL_PACKAGES" --- src/main/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 0df126de..dc2a4421 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -8,7 +8,8 @@ - + Date: Tue, 28 Dec 2021 13:37:48 +0530 Subject: [PATCH 44/67] Fix: Remove Configuration Caching to fix building & some renaming --- gradle.properties | 3 +-- src/main/kotlin/com/looker/droidify/content/Preferences.kt | 6 +++--- src/main/kotlin/com/looker/droidify/index/IndexV1Parser.kt | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/gradle.properties b/gradle.properties index b85d5790..8323ddb3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -android.useAndroidX=true -org.gradle.unsafe.configuration-cache=true \ No newline at end of file +android.useAndroidX=true \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/content/Preferences.kt b/src/main/kotlin/com/looker/droidify/content/Preferences.kt index fd377550..e8a25a28 100644 --- a/src/main/kotlin/com/looker/droidify/content/Preferences.kt +++ b/src/main/kotlin/com/looker/droidify/content/Preferences.kt @@ -18,8 +18,8 @@ import java.net.Proxy object Preferences { private lateinit var preferences: SharedPreferences - private val _subject = MutableSharedFlow>() - val subject = _subject.asSharedFlow() + private val mutableSubject = MutableSharedFlow>() + val subject = mutableSubject.asSharedFlow() private val keys = sequenceOf( Key.Language, @@ -45,7 +45,7 @@ object Preferences { preferences.registerOnSharedPreferenceChangeListener { _, keyString -> CoroutineScope(Dispatchers.Default).launch { keys[keyString]?.let { - _subject.emit(it) + mutableSubject.emit(it) } } } diff --git a/src/main/kotlin/com/looker/droidify/index/IndexV1Parser.kt b/src/main/kotlin/com/looker/droidify/index/IndexV1Parser.kt index 0d718a63..8c8d7954 100644 --- a/src/main/kotlin/com/looker/droidify/index/IndexV1Parser.kt +++ b/src/main/kotlin/com/looker/droidify/index/IndexV1Parser.kt @@ -153,9 +153,9 @@ object IndexV1Parser { it.string("openCollective") -> donates += Product.Donate.OpenCollective( valueAsString ) - it.dictionary("localized") -> forEachKey { it -> - if (it.token == JsonToken.START_OBJECT) { - val locale = it.key + it.dictionary("localized") -> forEachKey { keyToken -> + if (keyToken.token == JsonToken.START_OBJECT) { + val locale = keyToken.key var name = "" var summary = "" var description = "" From 0298875e14d6f1425321ecb194356bbe7d3a238a Mon Sep 17 00:00:00 2001 From: LooKeR Date: Tue, 28 Dec 2021 13:55:55 +0530 Subject: [PATCH 45/67] Fix: Settings switch changing on theme changes --- .../kotlin/com/looker/droidify/screen/SettingsFragment.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt index 32efdc09..57246064 100644 --- a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt @@ -40,6 +40,11 @@ class SettingsFragment : ScreenFragment() { private var preferenceBinding: PreferenceItemBinding? = null private val preferences = mutableMapOf, Preference<*>>() + override fun onResume() { + super.onResume() + preferences.forEach { (_, preference) -> preference.update() } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) preferenceBinding = PreferenceItemBinding.inflate(layoutInflater) From 51030cfe1a21a2d598a97a29f72b5d4fde983c81 Mon Sep 17 00:00:00 2001 From: LooKeR Date: Wed, 29 Dec 2021 12:24:44 +0530 Subject: [PATCH 46/67] Improve: Performance Improvement with inline function --- src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt index 57246064..7adb4cd5 100644 --- a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt @@ -217,7 +217,7 @@ class SettingsFragment : ScreenFragment() { } } - private fun LinearLayoutCompat.addCategory( + private inline fun LinearLayoutCompat.addCategory( title: String, callback: LinearLayoutCompat.() -> Unit, ) { From 0bda05445a51f1cec0e49303882ae77c9a68c057 Mon Sep 17 00:00:00 2001 From: Adi Nugroho Date: Thu, 30 Dec 2021 19:03:26 +0100 Subject: [PATCH 47/67] Added translation using Weblate (Indonesian) --- src/main/res/values-in/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/res/values-in/strings.xml diff --git a/src/main/res/values-in/strings.xml b/src/main/res/values-in/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/src/main/res/values-in/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 551cabfea945b0904745737efc6526bb7e541e77 Mon Sep 17 00:00:00 2001 From: badlop Date: Tue, 28 Dec 2021 10:14:52 +0000 Subject: [PATCH 48/67] Translated using Weblate (Spanish) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/es/ --- src/main/res/values-es/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 48eee4d4..3032c1ce 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -177,4 +177,5 @@ Actualizar todos Aplicaciones instaladas Aplicaciones nuevas + Solo en Wi-Fi y Conectado \ No newline at end of file From cfb586e45694d8e2546b1bea02cc69e56dccc9cb Mon Sep 17 00:00:00 2001 From: Paolo Campetto Date: Wed, 29 Dec 2021 15:48:42 +0000 Subject: [PATCH 49/67] Translated using Weblate (Italian) Currently translated at 99.4% (174 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/it/ --- src/main/res/values-it/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 12a1ec00..7974326f 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -45,7 +45,7 @@ Contiene vulnerabilità Risposta del server non valida. Proxy HTTP - Ignora tuttie le nuove versioni + Ignora tutte le nuove versioni Ignora questa versione Il tuo %1$s (API version %2$d) non è supportato. %3$s La versione massima API è %d. @@ -180,4 +180,5 @@ Più recenti Ordina e filtra Nuove applicazioni + Solo su Wi-Fi e Plugged-In \ No newline at end of file From f861da7c83dd4e823adfc9bfdc99da6475c3a9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Carvalho=20de=20Ara=C3=BAjo?= Date: Tue, 28 Dec 2021 16:09:36 +0000 Subject: [PATCH 50/67] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/pt_BR/ --- src/main/res/values-pt-rBR/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index d108a8aa..e4d5adfc 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -1,14 +1,14 @@ - A ação falhou + Ação falhou Adicionar repositório Endereço Todos os aplicativos - Todos os seus aplicativos estão atualizados + Todos os teus aplicativos estão atualizados Já existe Sempre - Escuro - Características indesejadas + Preto + Anti-funções Aplicativo Não foi possível encontrar esse aplicativo E-mail do autor From c1606532d2ef4e46a41627315c00b2137fb9298b Mon Sep 17 00:00:00 2001 From: Ricardo Date: Tue, 28 Dec 2021 15:04:51 +0000 Subject: [PATCH 51/67] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/pt_BR/ --- src/main/res/values-pt-rBR/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index e4d5adfc..08f7e7c4 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -180,4 +180,5 @@ Mais recente Explorar Aplicativos instalados + Apenas em Wi-Fi e Plugado \ No newline at end of file From 3fe23facfaa1d806d6f3623263c5f243547e2afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 28 Dec 2021 16:09:44 +0000 Subject: [PATCH 52/67] Translated using Weblate (Turkish) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/tr/ --- src/main/res/values-tr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-tr/strings.xml b/src/main/res/values-tr/strings.xml index 80ca5136..cf87ce33 100644 --- a/src/main/res/values-tr/strings.xml +++ b/src/main/res/values-tr/strings.xml @@ -178,4 +178,5 @@ En yeni Tümünü güncelle Keşfet + Yalnızca Wi-Fi\'de ve Prize Takılı \ No newline at end of file From 8e22af92d5b2acb58b23e56e62d264b3b9b8ab30 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 28 Dec 2021 08:01:40 +0000 Subject: [PATCH 53/67] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index dbbb45ab..f7b63136 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -179,4 +179,5 @@ 更新全部 新程序 已安装的程序 + 仅在 Wi-Fi 和充电情况下 \ No newline at end of file From f0e30b78d13480defa20bf6a6a43aa9b4b0e73b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 30 Dec 2021 14:22:54 +0000 Subject: [PATCH 54/67] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 99.4% (174 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/nb_NO/ --- src/main/res/values-nb-rNO/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml index 7065118a..f6f28540 100644 --- a/src/main/res/values-nb-rNO/strings.xml +++ b/src/main/res/values-nb-rNO/strings.xml @@ -177,4 +177,5 @@ Installerte programmer Sorter og filtrer Nye programmer + Kun på Wi-Fi tilkoblet lader \ No newline at end of file From 82c48b618336cd5aa47cb23227175f2d525bcaf9 Mon Sep 17 00:00:00 2001 From: Ebrahim Byagowi Date: Thu, 30 Dec 2021 00:34:46 +0000 Subject: [PATCH 55/67] Translated using Weblate (Persian) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/fa/ --- src/main/res/values-fa/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-fa/strings.xml b/src/main/res/values-fa/strings.xml index c85a846c..da54ba31 100644 --- a/src/main/res/values-fa/strings.xml +++ b/src/main/res/values-fa/strings.xml @@ -177,4 +177,5 @@ نادیده گرفتن تمام نسخه‌های جدید پاسخ نادرست سرور. ناسازگار با %s + فقط در وای‌فای و به برق وصل‌شده \ No newline at end of file From a197338b488c96b5e135b10f2674afff06d2ab85 Mon Sep 17 00:00:00 2001 From: teemue Date: Thu, 30 Dec 2021 15:37:22 +0000 Subject: [PATCH 56/67] Translated using Weblate (Finnish) Currently translated at 97.1% (170 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/fi/ --- src/main/res/values-fi/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml index d764bea3..fc5ed415 100644 --- a/src/main/res/values-fi/strings.xml +++ b/src/main/res/values-fi/strings.xml @@ -78,10 +78,10 @@ Nimi Verkkovirhe. Ei koskaan - Uusia sovellusten versioita saatavilla + Päivityksiä saatavilla - %d -sovelluksesta on uusi versio. - %d -sovelluksia, joilla on uusia versioita. + Uusi versio on saatavilla yhteen sovellukseen. + Uusi versio saatavilla %d sovellukseen. Ei sovelluksia saatavilla Ei asennettuja sovelluksia From 62ac644891ecf33543e1fb5b1637be465b090e24 Mon Sep 17 00:00:00 2001 From: Adi Nugroho Date: Thu, 30 Dec 2021 18:06:18 +0000 Subject: [PATCH 57/67] Translated using Weblate (Indonesian) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/id/ --- src/main/res/values-in/strings.xml | 180 ++++++++++++++++++++++++++++- 1 file changed, 179 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-in/strings.xml b/src/main/res/values-in/strings.xml index a6b3daec..ccc3d757 100644 --- a/src/main/res/values-in/strings.xml +++ b/src/main/res/values-in/strings.xml @@ -1,2 +1,180 @@ - \ No newline at end of file + + Pelacak bug + Platform %1$s Anda tidak didukung. Platform yang didukung: %2$s. + Semua aplikasi + Hapus + Anti-fitur + Situs pembuat + Tersedia + Batal + Tidak dapat mengedit repositori karena sedang mensinkronisasi. + Daftar perubahan + Perubahan + Memeriksa repositori… + Menghubungkan… + Mengandung media non-bebas + Tidak dapat mensinkronisasi %s + Tidak dapat memvalidasi %s + Kredit + Deskripsi + Detail + Donasi + %s telah diunduh + Mengunduh %s… + Ubah repositori + Format berkas tidak valid. + Sidik + Ada iklan + Ada celah keamanan + Respon server tidak valid. + Abaikan semua versi baru + Abaikan versi ini + %1$s Anda (versi API %2$d) tidak didukung. %3$s + Versi API maksimal %d. + Versi API minimal %d. + Fitur yang hilang. + Versi ini lebih tua dari yang ter-install di perangkat Anda. Uninstall terlebih dahulu. + Versi yang tidak cocok + Versi yang tidak cocok + Tipe Instalasi + Ter-install + Tidak dapat mengecek integritas. + Alamat tidak valid + Perlihatkan animasi list di halaman utama + Menggabungkan %s + Nama + Tidak pernah + Tanpa proxy + Beritahu tentang versi baru aplikasi + Hanya kompatibel dengan %s + Hanya di jaringan Wi-Fi + Sandi + Pengaturan + Situs projek + Disediakan oleh %s + Sinkronisasi repositori otomatis + Mensinkronisasi + Sistem + Tap untuk menginstall. + Target + Tema + Tema + Merekam atau melaporkan aktivitas Anda + Uninstall + Galat. + Sarankan menginstall versi tidak stabil + Pembaruan + Pembaruan + Menunggu mulai unduhan… + Jelajahi + Perbaharui semua + Urutkan & Saring + Aksi gagal + Tambah repositori + Semua aplikasi Anda terbaharukan + Selalu + E-mail pembuat + Dikompilasi untuk debugging + Alamat + Sudah ada + Hitam + Tidak dapat menemukan aplikasi itu + Aplikasi + Konfirmasi + Tidak dapat mengunduh %s + Hapus repositori\? + Ada dependensi non-bebas + Versi ini ditandatangani dengan sertifikat yang berbeda dari yang ter-install di perangkat Anda. Uninstall terlebih dahulu. + Install + Tidak ada aplikasi yang ter-install + Gelap + Mengunduh + HTTP proxy + Tidak cocok dengan %s + Perlihatkan versi aplikasi yang tidak cocok dengan perangkat + Format sidik tidak valid + Tidak dapat mengurai berkas indeks. + Metadata tidak valid. + Perizinan tidak valid. + Memproses %1$s… + Tandatangan tidak valid. + Lisensi %s + Animasi List + + %d aplikasi dengan versi baru. + + Format username tidak valid + Jalankan + Lisensi + Tautan disalin ke clipboard + Tautan + Terang + Galat jaringan. + Versi aplikasi baru tersedia + Tidak ada aplikasi yang tersedia + Perlihatkan notifikasi saat versi baru tersedia + Jumlah aplikasi + OK + Sandi tidak ditemukan + Perizinan + Proxy SOCKS + Tidak ada deskripsi. + Tidak dapat menemukan aplikasi tersebut + Hanya di jaringan Wi-Fi dan terhubung ke listrik + Buka %s\? + Lainnya + +%d lebih banyak + Port proxy + Repositori + Mempromosikan software non-bebas + Proxy + Mempromosikan situs non-bebas + Jenis proxy + Host proxy + Perlihatkan versi yang lebih tua + Diperbaharui akhir-akhir ini + Repositori + Perbolehkan perizinan root untuk instalasi senyap + Tidak ditandatangani. Tidak dapat memverifikasi daftar aplikasi. Hati-hati mengunduh aplikasi dari repositori yang tidak ditandatangani. + Menyimpan detail… + Pilih mirror + Pengurutan + Kode sumber + Username + Repositori ini belum dipakai. Aktifkan untuk melihat aplikasi di dalamnya. + Membutuhkan %s + Simpan + Cari + Instalasi Senyap + Tangkapan layar + Lewati + Kode sumber sudah tidak tersedia + Disarankan + Bagikan + Perlihatkan lebih banyak + Tandatangan %s + Ditandatangani dengan algoritma yang tidak aman + Ukuran + Versi + Sinkronisasi repositori + Mensinkronisasi %s… + Tidak ditandatangani + Pembaruan tidak stabil + Kode sumber tidak bebas + Tidak terverifikasi + Indeks tidak dapat divalidasi. + Tidak diketahui + Tidak diketahui: %s + Username tidak ada + Versi + Perlihatkan Lebih Sedikit + Terbaru + Versi %s + Situs + Bahasa + Personalisasi + Apa yang Baru + Aplikasi ter-install + Aplikasi baru + \ No newline at end of file From 6c21afb8a1fb7a02d2c423e1d44cd7681b0a61e2 Mon Sep 17 00:00:00 2001 From: "J. Lavoie" Date: Fri, 31 Dec 2021 13:36:32 +0000 Subject: [PATCH 58/67] Translated using Weblate (French) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/fr/ --- src/main/res/values-fr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 9613935e..b5e68f4c 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -177,4 +177,5 @@ Nouveautés Nouvelles applications Tout mettre à jour + Uniquement en Wi-Fi et branché \ No newline at end of file From 70eee260481e5ec6043cb7e3d174b0501051a76f Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 3 Jan 2022 00:29:16 +0100 Subject: [PATCH 59/67] Add: Database instance to Main fragments VM --- .../droidify/ui/fragments/ExploreFragment.kt | 7 +++++-- .../ui/fragments/InstalledFragment.kt | 7 +++++-- .../droidify/ui/fragments/LatestFragment.kt | 7 +++++-- .../droidify/ui/fragments/MainNavFragmentX.kt | 2 +- .../viewmodels/MainNavFragmentViewModelX.kt | 21 ++++++++++++++++--- 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt index 9a03b185..17e1bf26 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt @@ -5,8 +5,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager @@ -26,7 +26,7 @@ import kotlinx.coroutines.launch class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback { - override val viewModel: MainNavFragmentViewModelX by viewModels() + override lateinit var viewModel: MainNavFragmentViewModelX private lateinit var binding: FragmentExploreXBinding override val source = Source.AVAILABLE @@ -41,6 +41,9 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback { super.onCreate(savedInstanceState) binding = FragmentExploreXBinding.inflate(inflater, container, false) binding.lifecycleOwner = this + val viewModelFactory = MainNavFragmentViewModelX.Factory(mainActivityX.db) + viewModel = ViewModelProvider(this, viewModelFactory) + .get(MainNavFragmentViewModelX::class.java) binding.recyclerView.apply { layoutManager = LinearLayoutManager(context) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt index c2df2c3b..549566cb 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt @@ -5,7 +5,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.looker.droidify.database.CursorOwner @@ -26,7 +26,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { - override val viewModel: MainNavFragmentViewModelX by viewModels() + override lateinit var viewModel: MainNavFragmentViewModelX private lateinit var binding: FragmentInstalledXBinding private val installedItemAdapter = ItemAdapter() @@ -47,6 +47,9 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { super.onCreate(savedInstanceState) binding = FragmentInstalledXBinding.inflate(inflater, container, false) binding.lifecycleOwner = this + val viewModelFactory = MainNavFragmentViewModelX.Factory(mainActivityX.db) + viewModel = ViewModelProvider(this, viewModelFactory) + .get(MainNavFragmentViewModelX::class.java) installedFastAdapter = FastAdapter.with(installedItemAdapter) installedFastAdapter?.setHasStableIds(true) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt index 3744bcc7..203cb6c9 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt @@ -5,7 +5,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.looker.droidify.database.CursorOwner @@ -26,7 +26,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { - override val viewModel: MainNavFragmentViewModelX by viewModels() + override lateinit var viewModel: MainNavFragmentViewModelX private lateinit var binding: FragmentLatestXBinding private val updatedItemAdapter = ItemAdapter() @@ -47,6 +47,9 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { super.onCreate(savedInstanceState) binding = FragmentLatestXBinding.inflate(inflater, container, false) binding.lifecycleOwner = this + val viewModelFactory = MainNavFragmentViewModelX.Factory(mainActivityX.db) + viewModel = ViewModelProvider(this, viewModelFactory) + .get(MainNavFragmentViewModelX::class.java) updatedFastAdapter = FastAdapter.with(updatedItemAdapter) updatedFastAdapter?.setHasStableIds(true) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/MainNavFragmentX.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/MainNavFragmentX.kt index 3abcb73c..56686390 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/MainNavFragmentX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/MainNavFragmentX.kt @@ -10,7 +10,7 @@ import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX abstract class MainNavFragmentX : Fragment(), CursorOwner.Callback { val mainActivityX: MainActivityX get() = requireActivity() as MainActivityX - abstract val viewModel: MainNavFragmentViewModelX + abstract var viewModel: MainNavFragmentViewModelX abstract val source: Source open fun onBackPressed(): Boolean = false diff --git a/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt b/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt index 30832c56..ad6e8457 100644 --- a/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt @@ -1,15 +1,20 @@ package com.looker.droidify.ui.viewmodels import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.looker.droidify.database.CursorOwner +import com.looker.droidify.database.DatabaseX import com.looker.droidify.entity.ProductItem import com.looker.droidify.ui.fragments.Source import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -class MainNavFragmentViewModelX : ViewModel() { +class MainNavFragmentViewModelX(val db: DatabaseX) : ViewModel() { private val _order = MutableStateFlow(ProductItem.Order.LAST_UPDATE) private val _sections = MutableStateFlow(ProductItem.Section.All) @@ -86,4 +91,14 @@ class MainNavFragmentViewModelX : ViewModel() { } } } -} \ No newline at end of file + + class Factory(val db: DatabaseX) : ViewModelProvider.Factory { + @Suppress("unchecked_cast") + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(MainNavFragmentViewModelX::class.java)) { + return MainNavFragmentViewModelX(db) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } + } +} From 4547ef8a55c18a5f2f09edff4b98f08c27f219e7 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 3 Jan 2022 00:38:55 +0100 Subject: [PATCH 60/67] Update: Replace repositories observable disposable with Room-RxJava's Flowable --- .../kotlin/com/looker/droidify/database/DAOs.kt | 4 ++++ .../droidify/ui/fragments/ExploreFragment.kt | 14 +++++--------- .../droidify/ui/fragments/InstalledFragment.kt | 10 ++-------- .../looker/droidify/ui/fragments/LatestFragment.kt | 10 ++-------- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 623d2cd0..da2ab7b3 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -7,6 +7,7 @@ import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteQuery import com.looker.droidify.* import com.looker.droidify.entity.ProductItem +import io.reactivex.rxjava3.core.Flowable interface BaseDao { @@ -53,6 +54,9 @@ interface RepositoryDao : BaseDao { @get:Query("SELECT * FROM repository WHERE deleted == 0 ORDER BY _id ASC") val all: List + @get:Query("SELECT * FROM repository WHERE deleted == 0 ORDER BY _id ASC") + val allFlowable: Flowable> + @get:Query("SELECT _id, deleted FROM repository WHERE deleted != 0 and enabled == 0 ORDER BY _id ASC") val allDisabledDeleted: List diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt index 17e1bf26..215aea47 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt @@ -13,13 +13,12 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.looker.droidify.R import com.looker.droidify.database.CursorOwner import com.looker.droidify.databinding.FragmentExploreXBinding +import com.looker.droidify.entity.Repository import com.looker.droidify.ui.adapters.AppListAdapter import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX import com.looker.droidify.utility.RxUtils import com.looker.droidify.widget.RecyclerFastScroller import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -31,7 +30,7 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback { override val source = Source.AVAILABLE - private var repositoriesDisposable: Disposable? = null + private var repositories: Map = mapOf() override fun onCreateView( inflater: LayoutInflater, @@ -61,21 +60,18 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback { super.onViewCreated(view, savedInstanceState) mainActivityX.attachCursorOwner(this, viewModel.request(source)) - repositoriesDisposable = Observable.just(Unit) - //.concatWith(Database.observable(Database.Subject.Repositories)) TODO have to be replaced like whole rxJava + viewModel.db.repositoryDao.allFlowable .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.trueData } } } + .flatMapSingle { list -> RxUtils.querySingle { list.mapNotNull { it.trueData } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) - .subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it } + .subscribe { repositories = it } } override fun onDestroyView() { super.onDestroyView() mainActivityX.detachCursorOwner(this) - repositoriesDisposable?.dispose() - repositoriesDisposable = null } override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) { diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt index 549566cb..bb51af66 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt @@ -20,8 +20,6 @@ import com.looker.droidify.widget.RecyclerFastScroller import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.adapters.ItemAdapter import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { @@ -37,7 +35,6 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { override val source = Source.INSTALLED private var repositories: Map = mapOf() - private var repositoriesDisposable: Disposable? = null override fun onCreateView( inflater: LayoutInflater, @@ -76,10 +73,9 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { super.onViewCreated(view, savedInstanceState) mainActivityX.attachCursorOwner(this, viewModel.request(source)) - repositoriesDisposable = Observable.just(Unit) - //.concatWith(Database.observable(Database.Subject.Repositories)) TODO have to be replaced like whole rxJava + viewModel.db.repositoryDao.allFlowable .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.trueData } } } + .flatMapSingle { list -> RxUtils.querySingle { list.mapNotNull { it.trueData } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { repositories = it } @@ -89,8 +85,6 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { super.onDestroyView() mainActivityX.detachCursorOwner(this) - repositoriesDisposable?.dispose() - repositoriesDisposable = null } override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) { diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt index 203cb6c9..f0cd127b 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt @@ -20,8 +20,6 @@ import com.looker.droidify.widget.RecyclerFastScroller import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.adapters.ItemAdapter import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { @@ -37,7 +35,6 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { override val source = Source.AVAILABLE private var repositories: Map = mapOf() - private var repositoriesDisposable: Disposable? = null override fun onCreateView( inflater: LayoutInflater, @@ -76,10 +73,9 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { super.onViewCreated(view, savedInstanceState) mainActivityX.attachCursorOwner(this, viewModel.request(source)) - repositoriesDisposable = Observable.just(Unit) - //.concatWith(Database.observable(Database.Subject.Repositories)) TODO have to be replaced like whole rxJava + viewModel.db.repositoryDao.allFlowable .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.trueData } } } + .flatMapSingle { list -> RxUtils.querySingle { list.mapNotNull { it.trueData } } } .map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { repositories = it } @@ -89,8 +85,6 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { super.onDestroyView() mainActivityX.detachCursorOwner(this) - repositoriesDisposable?.dispose() - repositoriesDisposable = null } override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) { From d95fa0425712f15557ed976603091accba1d3792 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 3 Jan 2022 09:22:46 +0100 Subject: [PATCH 61/67] Add: Room RxJava dependency --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 30f4ec23..970b4c4c 100644 --- a/build.gradle +++ b/build.gradle @@ -168,6 +168,7 @@ dependencies { // Room implementation 'androidx.room:room-runtime:2.4.0' implementation 'androidx.room:room-ktx:2.4.0' + implementation 'androidx.room:room-rxjava3:2.4.0' kapt 'androidx.room:room-compiler:2.4.0' } From b7ab8ab98e8f0d42ad0572ee3b43cbf0f8aeca03 Mon Sep 17 00:00:00 2001 From: Takumi Date: Mon, 3 Jan 2022 12:51:49 +0000 Subject: [PATCH 62/67] Translated using Weblate (Japanese) Currently translated at 89.1% (156 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/ja/ --- src/main/res/values-ja/strings.xml | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index b67ab627..0e7bba84 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -115,4 +115,47 @@ プロジェクトのウェブサイト プロキシ リポジトリ + このリポジトリは未だ使用されていません。オンにすると、アプリケーションを見ることができます。 + 同期中 + 不明 + 不明なエラーです。 + ユーザーネーム + 上流のソースコードは不自由です + インストール済みのアプリケーション + Wi-Fi接続時と充電時 + サイレントインストール + 古いバージョンを見る + 安全ではないアルゴリズムで署名されています + サイズ + スキップ + 並び変え + ソースコード + ソースコードは使用できません + 提案 + リポジトリを同期 + 自動的にリポジトリを同期 + システム + タップしてインストールします。 + あなたのアクティビティを追跡、報告します + アンインストール + 不安定なアップデート + ウェブサイト + 言語 + 表示を減らす + 最新 + 全てをアップデート + 新しいアプリケーション + サイレントインストールのためにスーパーユーザー権限を許可 + 共有 + もっと見る + アップデート + 不安定なバージョンのインストールを提案する + バージョン + ダウンロード開始を待っています… + 保存 + テーマ + スクリーンショット + ミラーを選択 + 検索 + インデックスが検証できません。 \ No newline at end of file From c059b54078bdca3a7b2ae5e172b01bd958a0f5ae Mon Sep 17 00:00:00 2001 From: MrWooltrest Date: Sun, 2 Jan 2022 16:03:03 +0000 Subject: [PATCH 63/67] Translated using Weblate (Russian) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/ru/ --- src/main/res/values-ru/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index f0a41571..a14e1dd3 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -180,4 +180,5 @@ Установленные приложения Последние Сортировать и фильтровать + Только при Wi-Fi и подключении к сети \ No newline at end of file From bd581814ca3306046d9becabbbd65d3732d2bc93 Mon Sep 17 00:00:00 2001 From: MrWooltrest Date: Sun, 2 Jan 2022 16:04:59 +0000 Subject: [PATCH 64/67] Translated using Weblate (Ukrainian) Currently translated at 100.0% (175 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/uk/ --- src/main/res/values-uk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index ed11c5da..fd964d90 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -179,4 +179,5 @@ Нові додатки Останні Сортувати та фільтрувати + Лише при Wi-Fi і підключеному до мережі \ No newline at end of file From bedf5cad3f3461a02452faf81d5251909b4d25e9 Mon Sep 17 00:00:00 2001 From: Takumi Date: Mon, 3 Jan 2022 13:39:54 +0000 Subject: [PATCH 65/67] Translated using Weblate (Japanese) Currently translated at 94.8% (166 of 175 strings) Translation: Droidify/Localization Translate-URL: https://hosted.weblate.org/projects/droidify/localization/ja/ --- src/main/res/values-ja/strings.xml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 0e7bba84..44d24b4c 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -120,9 +120,9 @@ 不明 不明なエラーです。 ユーザーネーム - 上流のソースコードは不自由です + 上流のソースコードは非フリーです インストール済みのアプリケーション - Wi-Fi接続時と充電時 + Wi-Fi接続時と充電時のみ サイレントインストール 古いバージョンを見る 安全ではないアルゴリズムで署名されています @@ -158,4 +158,16 @@ ミラーを選択 検索 インデックスが検証できません。 + SOCKSプロキシ + ユーザーネームがありません + 並べ替えとフィルター + 詳細を保存しています… + 署名 %s + %sを同期中… + 署名なし + アップデート + 新機能 + バージョン + 未確認 + テーマ \ No newline at end of file From 3082e7153da46331d65a2c0edda63e390a00a380 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 3 Jan 2022 23:36:04 +0100 Subject: [PATCH 66/67] Update: Replace CursorOwner's de-/attaching with VM's fillList --- .../com/looker/droidify/database/DAOs.kt | 91 +++++++++++++++++++ .../droidify/ui/activities/MainActivityX.kt | 29 ------ .../droidify/ui/fragments/ExploreFragment.kt | 8 +- .../ui/fragments/InstalledFragment.kt | 8 +- .../droidify/ui/fragments/LatestFragment.kt | 8 +- .../droidify/ui/fragments/MainNavFragmentX.kt | 39 +++++++- .../viewmodels/MainNavFragmentViewModelX.kt | 53 ++++++++++- 7 files changed, 178 insertions(+), 58 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index da2ab7b3..cd4fb593 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -171,6 +171,97 @@ interface ProductDao : BaseDao { return query(SimpleSQLiteQuery(builder.build())) } + + @RawQuery + fun queryList( + query: SupportSQLiteQuery + ): List + + // TODO optimize and simplify + @Transaction + fun queryList( + installed: Boolean, updates: Boolean, searchQuery: String, + section: ProductItem.Section, order: ProductItem.Order + ): List { + val builder = QueryBuilder() + + val signatureMatches = """installed.${ROW_SIGNATURE} IS NOT NULL AND + product.${ROW_SIGNATURES} LIKE ('%.' || installed.${ROW_SIGNATURE} || '.%') AND + product.${ROW_SIGNATURES} != ''""" + + builder += """SELECT product.rowid AS _id, product.${ROW_REPOSITORY_ID}, + product.${ROW_PACKAGE_NAME}, product.${ROW_NAME}, + product.${ROW_SUMMARY}, installed.${ROW_VERSION}, + (COALESCE(lock.${ROW_VERSION_CODE}, -1) NOT IN (0, product.${ROW_VERSION_CODE}) AND + product.${ROW_COMPATIBLE} != 0 AND product.${ROW_VERSION_CODE} > + COALESCE(installed.${ROW_VERSION_CODE}, 0xffffffff) AND $signatureMatches) + AS ${ROW_CAN_UPDATE}, product.${ROW_COMPATIBLE}, + product.${ROW_DATA_ITEM},""" + + if (searchQuery.isNotEmpty()) { + builder += """(((product.${ROW_NAME} LIKE ? OR + product.${ROW_SUMMARY} LIKE ?) * 7) | + ((product.${ROW_PACKAGE_NAME} LIKE ?) * 3) | + (product.${ROW_DESCRIPTION} LIKE ?)) AS ${ROW_MATCH_RANK},""" + builder %= List(4) { "%$searchQuery%" } + } else { + builder += "0 AS ${ROW_MATCH_RANK}," + } + + builder += """MAX((product.${ROW_COMPATIBLE} AND + (installed.${ROW_SIGNATURE} IS NULL OR $signatureMatches)) || + PRINTF('%016X', product.${ROW_VERSION_CODE})) FROM $ROW_PRODUCT_NAME AS product""" + builder += """JOIN $ROW_REPOSITORY_NAME AS repository + ON product.${ROW_REPOSITORY_ID} = repository.${ROW_ID}""" + builder += """LEFT JOIN $ROW_LOCK_NAME AS lock + ON product.${ROW_PACKAGE_NAME} = lock.${ROW_PACKAGE_NAME}""" + + if (!installed && !updates) { + builder += "LEFT" + } + builder += """JOIN $ROW_INSTALLED_NAME AS installed + ON product.${ROW_PACKAGE_NAME} = installed.${ROW_PACKAGE_NAME}""" + + if (section is ProductItem.Section.Category) { + builder += """JOIN $ROW_CATEGORY_NAME AS category + ON product.${ROW_PACKAGE_NAME} = category.${ROW_PACKAGE_NAME}""" + } + + builder += """WHERE repository.${ROW_ENABLED} != 0 AND + repository.${ROW_DELETED} == 0""" + + if (section is ProductItem.Section.Category) { + builder += "AND category.${ROW_NAME} = ?" + builder %= section.name + } else if (section is ProductItem.Section.Repository) { + builder += "AND product.${ROW_REPOSITORY_ID} = ?" + builder %= section.id.toString() + } + + if (searchQuery.isNotEmpty()) { + builder += """AND $ROW_MATCH_RANK > 0""" + } + + builder += "GROUP BY product.${ROW_PACKAGE_NAME} HAVING 1" + + if (updates) { + builder += "AND $ROW_CAN_UPDATE" + } + builder += "ORDER BY" + + if (searchQuery.isNotEmpty()) { + builder += """$ROW_MATCH_RANK DESC,""" + } + + when (order) { + ProductItem.Order.NAME -> Unit + ProductItem.Order.DATE_ADDED -> builder += "product.${ROW_ADDED} DESC," + ProductItem.Order.LAST_UPDATE -> builder += "product.${ROW_UPDATED} DESC," + }::class + builder += "product.${ROW_NAME} COLLATE LOCALIZED ASC" + + return queryList(SimpleSQLiteQuery(builder.build())) + } } @Dao diff --git a/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt b/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt index 4cd78275..09503f4b 100644 --- a/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt @@ -234,35 +234,6 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks syncConnection.binder?.setUpdateNotificationBlocker(blockerFragment) } - fun attachCursorOwner(callback: CursorOwner.Callback, request: CursorOwner.Request) { - val oldActiveRequest = viewModel.activeRequests[request.id] - if (oldActiveRequest?.callback != null && - oldActiveRequest.callback != callback && oldActiveRequest.cursor != null - ) { - oldActiveRequest.callback.onCursorData(oldActiveRequest.request, null) - } - val cursor = if (oldActiveRequest?.request == request && oldActiveRequest.cursor != null) { - callback.onCursorData(request, oldActiveRequest.cursor) - oldActiveRequest.cursor - } else { - null - } - viewModel.activeRequests[request.id] = CursorOwner.ActiveRequest(request, callback, cursor) - if (cursor == null) { - LoaderManager.getInstance(this).restartLoader(request.id, null, this) - } - } - - - fun detachCursorOwner(callback: CursorOwner.Callback) { - for (id in viewModel.activeRequests.keys) { - val activeRequest = viewModel.activeRequests[id]!! - if (activeRequest.callback == callback) { - viewModel.activeRequests[id] = activeRequest.copy(callback = null) - } - } - } - override fun onCreateLoader(id: Int, args: Bundle?): Loader { val request = viewModel.activeRequests[id]!!.request return QueryLoader(this) { diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt index 215aea47..dc07584a 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt @@ -59,7 +59,7 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - mainActivityX.attachCursorOwner(this, viewModel.request(source)) + viewModel.fillList(source) viewModel.db.repositoryDao.allFlowable .observeOn(Schedulers.io()) .flatMapSingle { list -> RxUtils.querySingle { list.mapNotNull { it.trueData } } } @@ -68,12 +68,6 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback { .subscribe { repositories = it } } - override fun onDestroyView() { - super.onDestroyView() - - mainActivityX.detachCursorOwner(this) - } - override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) { (binding.recyclerView.adapter as? AppListAdapter)?.apply { this.cursor = cursor diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt index bb51af66..f09a3440 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt @@ -72,7 +72,7 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - mainActivityX.attachCursorOwner(this, viewModel.request(source)) + viewModel.fillList(source) viewModel.db.repositoryDao.allFlowable .observeOn(Schedulers.io()) .flatMapSingle { list -> RxUtils.querySingle { list.mapNotNull { it.trueData } } } @@ -81,12 +81,6 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback { .subscribe { repositories = it } } - override fun onDestroyView() { - super.onDestroyView() - - mainActivityX.detachCursorOwner(this) - } - override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) { // TODO get a list instead of the cursor // TODO use LiveData and observers instead of listeners diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt index f0cd127b..df7cc7b3 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt @@ -72,7 +72,7 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - mainActivityX.attachCursorOwner(this, viewModel.request(source)) + viewModel.fillList(source) viewModel.db.repositoryDao.allFlowable .observeOn(Schedulers.io()) .flatMapSingle { list -> RxUtils.querySingle { list.mapNotNull { it.trueData } } } @@ -81,12 +81,6 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback { .subscribe { repositories = it } } - override fun onDestroyView() { - super.onDestroyView() - - mainActivityX.detachCursorOwner(this) - } - override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) { // TODO get a list instead of the cursor // TODO use LiveData and observers instead of listeners diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/MainNavFragmentX.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/MainNavFragmentX.kt index 56686390..0b25f808 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/MainNavFragmentX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/MainNavFragmentX.kt @@ -18,7 +18,7 @@ abstract class MainNavFragmentX : Fragment(), CursorOwner.Callback { internal fun setSearchQuery(searchQuery: String) { viewModel.setSearchQuery(searchQuery) { if (view != null) { - mainActivityX.attachCursorOwner(this, viewModel.request(source)) + viewModel.fillList(source) } } } @@ -26,7 +26,7 @@ abstract class MainNavFragmentX : Fragment(), CursorOwner.Callback { internal fun setSection(section: ProductItem.Section) { viewModel.setSection(section) { if (view != null) { - mainActivityX.attachCursorOwner(this, viewModel.request(source)) + viewModel.fillList(source) } } } @@ -34,7 +34,7 @@ abstract class MainNavFragmentX : Fragment(), CursorOwner.Callback { internal fun setOrder(order: ProductItem.Order) { viewModel.setOrder(order) { if (view != null) { - mainActivityX.attachCursorOwner(this, viewModel.request(source)) + viewModel.fillList(source) } } } @@ -44,4 +44,37 @@ enum class Source(val titleResId: Int, val sections: Boolean, val order: Boolean AVAILABLE(R.string.available, true, true), INSTALLED(R.string.installed, false, true), UPDATES(R.string.updates, false, false) +} + +sealed class Request { + internal abstract val id: Int + + data class ProductsAvailable( + val searchQuery: String, val section: ProductItem.Section, + val order: ProductItem.Order, + ) : Request() { + override val id: Int + get() = 1 + } + + data class ProductsInstalled( + val searchQuery: String, val section: ProductItem.Section, + val order: ProductItem.Order, + ) : Request() { + override val id: Int + get() = 2 + } + + data class ProductsUpdates( + val searchQuery: String, val section: ProductItem.Section, + val order: ProductItem.Order, + ) : Request() { + override val id: Int + get() = 3 + } + + object Repositories : Request() { + override val id: Int + get() = 4 + } } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt b/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt index ad6e8457..6d463a91 100644 --- a/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt @@ -1,11 +1,13 @@ package com.looker.droidify.ui.viewmodels +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import com.looker.droidify.database.CursorOwner import com.looker.droidify.database.DatabaseX +import com.looker.droidify.database.Product import com.looker.droidify.entity.ProductItem +import com.looker.droidify.ui.fragments.Request import com.looker.droidify.ui.fragments.Source import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -13,6 +15,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class MainNavFragmentViewModelX(val db: DatabaseX) : ViewModel() { @@ -37,7 +40,7 @@ class MainNavFragmentViewModelX(val db: DatabaseX) : ViewModel() { started = SharingStarted.WhileSubscribed(5000) ) - fun request(source: Source): CursorOwner.Request { + fun request(source: Source): Request { var mSearchQuery = "" var mSections: ProductItem.Section = ProductItem.Section.All var mOrder: ProductItem.Order = ProductItem.Order.NAME @@ -47,17 +50,17 @@ class MainNavFragmentViewModelX(val db: DatabaseX) : ViewModel() { launch { order.collect { if (source.order) mOrder = it } } } return when (source) { - Source.AVAILABLE -> CursorOwner.Request.ProductsAvailable( + Source.AVAILABLE -> Request.ProductsAvailable( mSearchQuery, mSections, mOrder ) - Source.INSTALLED -> CursorOwner.Request.ProductsInstalled( + Source.INSTALLED -> Request.ProductsInstalled( mSearchQuery, mSections, mOrder ) - Source.UPDATES -> CursorOwner.Request.ProductsUpdates( + Source.UPDATES -> Request.ProductsUpdates( mSearchQuery, mSections, mOrder @@ -65,6 +68,46 @@ class MainNavFragmentViewModelX(val db: DatabaseX) : ViewModel() { } } + var productsList = MediatorLiveData>() + + fun fillList(source: Source) { + viewModelScope.launch { + productsList.value = query(request(source))?.toMutableList() + } + } + + private suspend fun query(request: Request): List? { + return withContext(Dispatchers.IO) { + when (request) { + is Request.ProductsAvailable -> db.productDao + .queryList( + installed = false, + updates = false, + searchQuery = request.searchQuery, + section = request.section, + order = request.order + ) + is Request.ProductsInstalled -> db.productDao + .queryList( + installed = true, + updates = false, + searchQuery = request.searchQuery, + section = request.section, + order = request.order + ) + is Request.ProductsUpdates -> db.productDao + .queryList( + installed = true, + updates = true, + searchQuery = request.searchQuery, + section = request.section, + order = request.order + ) + else -> listOf() + } + } + } + fun setSection(newSection: ProductItem.Section, perform: () -> Unit) { viewModelScope.launch { if (newSection != sections.value) { From 07bc4c3cffd178339bbcb36ef645cf075dbbee54 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Tue, 4 Jan 2022 00:30:20 +0100 Subject: [PATCH 67/67] Remove: MainActivityX's Cursor LoaderCallbacks' implementation --- .../droidify/ui/activities/MainActivityX.kt | 52 +------------------ 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt b/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt index 09503f4b..aa7191fa 100644 --- a/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/activities/MainActivityX.kt @@ -2,15 +2,12 @@ package com.looker.droidify.ui.activities import android.content.Context import android.content.Intent -import android.database.Cursor import android.os.Bundle import android.view.* import android.view.inputmethod.InputMethodManager import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope -import androidx.loader.app.LoaderManager -import androidx.loader.content.Loader import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.AppBarConfiguration @@ -24,7 +21,6 @@ import com.looker.droidify.MainApplication import com.looker.droidify.R import com.looker.droidify.content.Preferences import com.looker.droidify.database.CursorOwner -import com.looker.droidify.database.QueryLoader import com.looker.droidify.databinding.ActivityMainXBinding import com.looker.droidify.installer.AppInstaller import com.looker.droidify.screen.* @@ -37,7 +33,7 @@ import com.looker.droidify.utility.extension.android.Android import com.looker.droidify.utility.extension.text.nullIfEmpty import kotlinx.coroutines.launch -class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks { +class MainActivityX : AppCompatActivity() { companion object { const val ACTION_UPDATES = "${BuildConfig.APPLICATION_ID}.intent.action.UPDATES" const val ACTION_INSTALL = "${BuildConfig.APPLICATION_ID}.intent.action.INSTALL" @@ -233,50 +229,4 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks } syncConnection.binder?.setUpdateNotificationBlocker(blockerFragment) } - - override fun onCreateLoader(id: Int, args: Bundle?): Loader { - val request = viewModel.activeRequests[id]!!.request - return QueryLoader(this) { - when (request) { - is CursorOwner.Request.ProductsAvailable -> db.productDao - .query( - installed = false, - updates = false, - searchQuery = request.searchQuery, - section = request.section, - order = request.order, - signal = it - ) - is CursorOwner.Request.ProductsInstalled -> db.productDao - .query( - installed = true, - updates = false, - searchQuery = request.searchQuery, - section = request.section, - order = request.order, - signal = it - ) - is CursorOwner.Request.ProductsUpdates -> db.productDao - .query( - installed = true, - updates = true, - searchQuery = request.searchQuery, - section = request.section, - order = request.order, - signal = it - ) - is CursorOwner.Request.Repositories -> db.repositoryDao.allCursor - } - } - } - - override fun onLoadFinished(loader: Loader, data: Cursor?) { - val activeRequest = viewModel.activeRequests[loader.id] - if (activeRequest != null) { - viewModel.activeRequests[loader.id] = activeRequest.copy(cursor = data) - activeRequest.callback?.onCursorData(activeRequest.request, data) - } - } - - override fun onLoaderReset(loader: Loader) = onLoadFinished(loader, null) }