mirror of
https://github.com/f-droid/fdroidclient.git
synced 2025-12-23 23:27:44 -05:00
[db] New AppOverviewItems AppDao methods
This commit is contained in:
@@ -1,16 +1,20 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.fdroid.LocaleChooser.getBestLocale
|
||||
import org.fdroid.database.TestUtils.getOrAwaitValue
|
||||
import org.fdroid.database.TestUtils.getOrFail
|
||||
import org.fdroid.index.v2.MetadataV2
|
||||
import org.fdroid.test.TestAppUtils.getRandomMetadataV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomRepo
|
||||
import org.fdroid.test.TestUtils.getRandomString
|
||||
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
@@ -19,13 +23,13 @@ import kotlin.test.assertTrue
|
||||
internal class AppOverviewItemsTest : AppTest() {
|
||||
|
||||
@Test
|
||||
fun testAntiFeatures() {
|
||||
// insert one apps with without version
|
||||
fun testAntiFeatures() = runBlocking {
|
||||
// insert one app with without version
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName, app1, locales)
|
||||
|
||||
// without version, anti-features are empty
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertNull(apps[0].antiFeatures)
|
||||
}
|
||||
@@ -33,7 +37,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
// with one version, the app has those anti-features
|
||||
val version = getRandomPackageVersionV2(versionCode = 42)
|
||||
versionDao.insert(repoId, packageName, "1", version, true)
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(version.antiFeatures, apps[0].antiFeatures)
|
||||
}
|
||||
@@ -41,7 +45,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
// with two versions, the app has the anti-features of the highest version
|
||||
val version2 = getRandomPackageVersionV2(versionCode = 23)
|
||||
versionDao.insert(repoId, packageName, "2", version2, true)
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(version.antiFeatures, apps[0].antiFeatures)
|
||||
}
|
||||
@@ -49,20 +53,20 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
// with three versions, the app has the anti-features of the highest version
|
||||
val version3 = getRandomPackageVersionV2(versionCode = 1337)
|
||||
versionDao.insert(repoId, packageName, "3", version3, true)
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(version3.antiFeatures, apps[0].antiFeatures)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIcons() {
|
||||
fun testIcons() = runBlocking {
|
||||
// insert one app
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName, app1, locales)
|
||||
|
||||
// icon is returned correctly
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
||||
}
|
||||
@@ -72,14 +76,14 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
appDao.insert(repoId2, packageName, app2, locales)
|
||||
|
||||
// app is still returned as before
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
||||
}
|
||||
|
||||
// after preferring second repo, icon is returned from app in second repo
|
||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app2.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
||||
}
|
||||
@@ -99,17 +103,18 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIncompatibleFlag() {
|
||||
fun testIncompatibleFlag() = runBlocking {
|
||||
// insert two apps
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName1, app1, locales)
|
||||
appDao.insert(repoId, packageName2, app2, locales)
|
||||
|
||||
// both apps are not compatible
|
||||
appDao.getAppOverviewItems().getOrFail().also {
|
||||
assertEquals(2, it.size)
|
||||
}.forEach {
|
||||
assertFalse(it.isCompatible)
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(2, apps.size)
|
||||
apps.forEach {
|
||||
assertFalse(it.isCompatible)
|
||||
}
|
||||
}
|
||||
// both apps, in the same category, are not compatible
|
||||
appDao.getAppOverviewItems("A").getOrFail().also {
|
||||
@@ -128,10 +133,10 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
appDao.updateCompatibility(repoId)
|
||||
|
||||
// now only one is not compatible
|
||||
appDao.getAppOverviewItems().getOrFail().also {
|
||||
assertEquals(2, it.size)
|
||||
assertFalse(it[0].isCompatible)
|
||||
assertTrue(it[1].isCompatible)
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(2, apps.size)
|
||||
assertFalse(apps[0].isCompatible)
|
||||
assertTrue(apps[1].isCompatible)
|
||||
}
|
||||
appDao.getAppOverviewItems("A").getOrFail().also {
|
||||
assertEquals(2, it.size)
|
||||
@@ -143,14 +148,14 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetByRepoWeight() {
|
||||
fun testGetByRepoWeight() = runBlocking {
|
||||
// insert one app with one version
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName, app1, locales)
|
||||
versionDao.insert(repoId, packageName, "1", getRandomPackageVersionV2(2), true)
|
||||
|
||||
// app is returned correctly
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app1, apps[0])
|
||||
}
|
||||
@@ -160,21 +165,21 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
appDao.insert(repoId2, packageName, app2, locales)
|
||||
|
||||
// app is still returned as before, new repo doesn't override old one
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app1, apps[0])
|
||||
}
|
||||
|
||||
// now second app from second repo is returned after preferring it explicitly
|
||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app2, apps[0])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetByRepoPref() {
|
||||
fun testGetByRepoPref() = runBlocking {
|
||||
// insert same app into three repos (repoId1 has highest weight)
|
||||
val repoId1 = repoDao.insertOrReplace(getRandomRepo())
|
||||
val repoId3 = repoDao.insertOrReplace(getRandomRepo())
|
||||
@@ -184,7 +189,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
appDao.insert(repoId3, packageName, app3, locales)
|
||||
|
||||
// app is returned correctly from repo1
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app1, apps[0])
|
||||
}
|
||||
@@ -195,7 +200,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
|
||||
// prefer repo3 for this app
|
||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId3))
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app3, apps[0])
|
||||
}
|
||||
@@ -206,7 +211,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
|
||||
// prefer repo2 for this app
|
||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app2, apps[0])
|
||||
}
|
||||
@@ -220,7 +225,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
|
||||
// prefer repo1 for this app
|
||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app1, apps[0])
|
||||
}
|
||||
@@ -341,7 +346,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyFromEnabledRepos() {
|
||||
fun testOnlyFromEnabledRepos() = runBlocking {
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName1, app1, locales)
|
||||
appDao.insert(repoId, packageName2, app2, locales)
|
||||
@@ -349,18 +354,24 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
appDao.insert(repoId2, packageName3, app3, locales)
|
||||
|
||||
// 3 apps from 2 repos
|
||||
assertEquals(3, appDao.getAppOverviewItems().getOrAwaitValue()?.size)
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(3, apps.size)
|
||||
}
|
||||
assertEquals(3, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
||||
|
||||
// only 1 app after disabling first repo
|
||||
repoDao.setRepositoryEnabled(repoId, false)
|
||||
assertEquals(1, appDao.getAppOverviewItems().getOrAwaitValue()?.size)
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
}
|
||||
assertEquals(1, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
||||
assertEquals(1, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
|
||||
|
||||
// no more apps after disabling all repos
|
||||
repoDao.setRepositoryEnabled(repoId2, false)
|
||||
assertEquals(0, appDao.getAppOverviewItems().getOrAwaitValue()?.size)
|
||||
getItems().forEach { apps ->
|
||||
assertEquals(0, apps.size)
|
||||
}
|
||||
assertEquals(0, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
||||
assertEquals(0, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
|
||||
}
|
||||
@@ -405,6 +416,65 @@ internal class AppOverviewItemsTest : AppTest() {
|
||||
assertEquals(app2, appDao.getAppOverviewItem(repoId2, packageName))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testByAuthor() = runBlocking {
|
||||
val author = getRandomString()
|
||||
val packageName = getRandomString()
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName, getRandomMetadataV2(author), locales)
|
||||
|
||||
// author has only one app
|
||||
assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
|
||||
assertEquals(0, appDao.getAppsByAuthor("foo bar").size)
|
||||
appDao.getAppsByAuthor(author).let { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(packageName, apps[0].packageName)
|
||||
}
|
||||
|
||||
// now add 49 more apps
|
||||
(1 until 50).forEach { _ ->
|
||||
appDao.insert(repoId, getRandomString(), getRandomMetadataV2(author), locales)
|
||||
}
|
||||
assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
|
||||
assertEquals(50, appDao.getAppsByAuthor(author).size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testByCategory() = runBlocking {
|
||||
// insert three apps
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName1, app1, locales)
|
||||
appDao.insert(repoId, packageName2, app2, locales)
|
||||
appDao.insert(repoId, packageName3, app3, locales)
|
||||
|
||||
// only two apps are in category B
|
||||
appDao.getAppsByCategory("B").let { apps ->
|
||||
assertEquals(2, apps.size)
|
||||
assertNotEquals(packageName2, apps[0].packageName)
|
||||
assertNotEquals(packageName2, apps[1].packageName)
|
||||
}
|
||||
|
||||
// no app is in category C
|
||||
assertEquals(0, appDao.getAppsByCategory("C").size)
|
||||
|
||||
// we'll add app1 as a variant of app2, but its repo has lower weight, so no effect
|
||||
val repoId2 = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId2, packageName2, app1, locales)
|
||||
appDao.getAppsByCategory("B").let { apps ->
|
||||
assertEquals(2, apps.size)
|
||||
assertNotEquals(packageName2, apps[0].packageName)
|
||||
assertNotEquals(packageName2, apps[1].packageName)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getItems(): List<List<AppOverviewItem>> {
|
||||
return listOf(
|
||||
appDao.getAppOverviewItems().getOrFail(),
|
||||
// manually sort the second list, so both results are comparable
|
||||
appDao.getAllApps().sortedByDescending { it.lastUpdated },
|
||||
)
|
||||
}
|
||||
|
||||
private fun assertEquals(expected: MetadataV2, actual: AppOverviewItem?) {
|
||||
assertNotNull(actual)
|
||||
assertEquals(expected.added, actual.added)
|
||||
|
||||
@@ -19,6 +19,8 @@ import androidx.room.RoomRawQuery
|
||||
import androidx.room.RoomWarnings.Companion.QUERY_MISMATCH
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import androidx.sqlite.SQLiteStatement
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
@@ -34,6 +36,7 @@ import org.fdroid.index.v2.LocalizedFileListV2
|
||||
import org.fdroid.index.v2.LocalizedFileV2
|
||||
import org.fdroid.index.v2.MetadataV2
|
||||
import org.fdroid.index.v2.ReflectionDiffer.applyDiff
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
public interface AppDao {
|
||||
/**
|
||||
@@ -83,16 +86,62 @@ public interface AppDao {
|
||||
* Apps without name, icon or summary are at the end (or excluded if limit is too small).
|
||||
* Includes anti-features from the version with the highest version code.
|
||||
*/
|
||||
@Deprecated("Use getNewAppsFlow and getRecentlyUpdatedAppsFlow instead")
|
||||
public fun getAppOverviewItems(limit: Int = 200): LiveData<List<AppOverviewItem>>
|
||||
|
||||
/**
|
||||
* Returns a limited number of apps with limited data within the given [category].
|
||||
*/
|
||||
@Deprecated("Use getAppsByCategory instead")
|
||||
public fun getAppOverviewItems(
|
||||
category: String,
|
||||
limit: Int = 50,
|
||||
): LiveData<List<AppOverviewItem>>
|
||||
|
||||
/**
|
||||
* Returns all apps from the database.
|
||||
*/
|
||||
public suspend fun getAllApps(): List<AppOverviewItem>
|
||||
|
||||
/**
|
||||
* Returns all apps whose author is set exactly to [authorName].
|
||||
*/
|
||||
public suspend fun getAppsByAuthor(authorName: String): List<AppOverviewItem>
|
||||
|
||||
/**
|
||||
* Returns all apps that are in the category with [categoryId].
|
||||
*/
|
||||
public suspend fun getAppsByCategory(categoryId: String): List<AppOverviewItem>
|
||||
|
||||
/**
|
||||
* Returns apps that are new. This means that they were added and last updated at the same time.
|
||||
* @param maxAgeInDays the number of days that is still considered "new".
|
||||
* Apps older than this won't be returned.
|
||||
*/
|
||||
public suspend fun getNewApps(maxAgeInDays: Long = 14): List<AppOverviewItem>
|
||||
|
||||
/**
|
||||
* Get apps that were recently updated.
|
||||
* This excludes apps returned by [getNewApps].
|
||||
* @param limit only return that many apps and not more.
|
||||
*/
|
||||
public suspend fun getRecentlyUpdatedApps(limit: Int = 200): List<AppOverviewItem>
|
||||
|
||||
/**
|
||||
* Get all apps from the repository identified by [repoId].
|
||||
*/
|
||||
public suspend fun getAppsByRepository(repoId: Long): List<AppOverviewItem>
|
||||
|
||||
/**
|
||||
* Same as [getNewApps], but returns an observable [Flow].
|
||||
*/
|
||||
public fun getNewAppsFlow(maxAgeInDays: Long = 14): Flow<List<AppOverviewItem>>
|
||||
|
||||
/**
|
||||
* Same as [getRecentlyUpdatedApps], but returns an observable [Flow].
|
||||
*/
|
||||
public fun getRecentlyUpdatedAppsFlow(limit: Int = 200): Flow<List<AppOverviewItem>>
|
||||
|
||||
/**
|
||||
* Returns a list of all [AppListItem] sorted by the given [sortOrder],
|
||||
* or a subset of [AppListItem]s filtered by the given [searchQuery] if it is non-null.
|
||||
@@ -413,6 +462,111 @@ internal interface AppDaoInt : AppDao {
|
||||
FROM ${AppMetadata.TABLE} AS app WHERE repoId = :repoId AND packageName = :packageName""")
|
||||
fun getAppOverviewItem(repoId: Long, packageName: String): AppOverviewItem?
|
||||
|
||||
@Transaction
|
||||
override suspend fun getAllApps(): List<AppOverviewItem> {
|
||||
val query = getAppsQuery("") {}
|
||||
return getApps(query)
|
||||
}
|
||||
|
||||
@Transaction
|
||||
override suspend fun getAppsByAuthor(authorName: String): List<AppOverviewItem> {
|
||||
val query = getAppsQuery("authorName = ?") { statement ->
|
||||
statement.bindText(1, authorName)
|
||||
}
|
||||
return getApps(query)
|
||||
}
|
||||
|
||||
@Transaction
|
||||
override suspend fun getAppsByCategory(categoryId: String): List<AppOverviewItem> {
|
||||
val query = getAppsQuery("categories LIKE '%,' || ? || ',%'") { statement ->
|
||||
statement.bindText(1, categoryId)
|
||||
}
|
||||
return getApps(query)
|
||||
}
|
||||
|
||||
@Transaction
|
||||
override suspend fun getNewApps(maxAgeInDays: Long): List<AppOverviewItem> {
|
||||
val query =
|
||||
getAppsQuery("app.added = app.lastUpdated AND app.lastUpdated > ?") { statement ->
|
||||
statement.bindLong(
|
||||
1,
|
||||
System.currentTimeMillis() - TimeUnit.DAYS.toMillis(maxAgeInDays)
|
||||
)
|
||||
}
|
||||
return getApps(query)
|
||||
}
|
||||
|
||||
@Transaction
|
||||
override suspend fun getRecentlyUpdatedApps(limit: Int): List<AppOverviewItem> {
|
||||
val query = getAppsQuery(
|
||||
"app.added != app.lastUpdated ORDER BY app.lastUpdated DESC LIMIT ?"
|
||||
) { statement ->
|
||||
statement.bindInt(1, limit)
|
||||
}
|
||||
return getApps(query)
|
||||
}
|
||||
|
||||
@Transaction
|
||||
@Query("""SELECT repoId, packageName, app.added, app.lastUpdated, localizedName,
|
||||
localizedSummary, categories, version.antiFeatures, app.isCompatible
|
||||
FROM ${AppMetadata.TABLE} AS app
|
||||
LEFT JOIN ${HighestVersion.TABLE} AS version USING (repoId, packageName)
|
||||
WHERE repoId = :repoId""")
|
||||
override suspend fun getAppsByRepository(repoId: Long): List<AppOverviewItem>
|
||||
|
||||
override fun getNewAppsFlow(maxAgeInDays: Long): Flow<List<AppOverviewItem>> {
|
||||
val query =
|
||||
getAppsQuery(
|
||||
"app.added = app.lastUpdated AND app.lastUpdated > ? ORDER BY app.added DESC"
|
||||
) { statement ->
|
||||
statement.bindLong(
|
||||
1,
|
||||
System.currentTimeMillis() - TimeUnit.DAYS.toMillis(maxAgeInDays)
|
||||
)
|
||||
}
|
||||
return getAppsFlow(query)
|
||||
}
|
||||
|
||||
override fun getRecentlyUpdatedAppsFlow(limit: Int): Flow<List<AppOverviewItem>> {
|
||||
val query = getAppsQuery(
|
||||
"app.added != app.lastUpdated ORDER BY app.lastUpdated DESC LIMIT ?"
|
||||
) { statement ->
|
||||
statement.bindInt(1, limit)
|
||||
}
|
||||
return getAppsFlow(query)
|
||||
}
|
||||
|
||||
private fun getAppsQuery(
|
||||
whereQuery: String,
|
||||
onBindStatement: (SQLiteStatement) -> Unit,
|
||||
): RoomRawQuery {
|
||||
val queryBuilder = StringBuilder(
|
||||
"""
|
||||
SELECT repoId, packageName, app.added, app.lastUpdated, localizedName,
|
||||
localizedSummary, categories, version.antiFeatures, app.isCompatible
|
||||
FROM ${AppMetadata.TABLE} AS app
|
||||
JOIN PreferredRepo USING (packageName)
|
||||
LEFT JOIN ${HighestVersion.TABLE} AS version USING (repoId, packageName)
|
||||
WHERE repoId = preferredRepoId"""
|
||||
)
|
||||
if (whereQuery.isNotEmpty()) queryBuilder.append(" AND ").append(whereQuery)
|
||||
return RoomRawQuery(
|
||||
sql = queryBuilder.toString().trimIndent(),
|
||||
onBindStatement = onBindStatement,
|
||||
)
|
||||
}
|
||||
|
||||
@RawQuery
|
||||
suspend fun getApps(query: RoomRawQuery): List<AppOverviewItem>
|
||||
|
||||
@Transaction
|
||||
@RawQuery(
|
||||
observedEntities = [
|
||||
AppMetadata::class, Version::class, Repository::class, RepositoryPreferences::class,
|
||||
]
|
||||
)
|
||||
fun getAppsFlow(query: RoomRawQuery): Flow<List<AppOverviewItem>>
|
||||
|
||||
//
|
||||
// AppListItems
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user