mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-04-20 06:47:06 -04:00
Change repo attribute icons to be localized
This affects anti-features and categories. Reflection diffing has been made more robust in the process with the earlier FileV2 hack removed and better error messages.
This commit is contained in:
committed by
Hans-Christoph Steiner
parent
45708edb66
commit
ddb6d46b6f
@@ -11,7 +11,7 @@ import org.fdroid.index.v2.ReleaseChannelV2
|
||||
import org.fdroid.index.v2.RepoV2
|
||||
import org.fdroid.test.DiffUtils.applyDiff
|
||||
import org.fdroid.test.DiffUtils.randomDiff
|
||||
import org.fdroid.test.TestRepoUtils.getRandomFileV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomLocalizedFileV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomLocalizedTextV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomMirror
|
||||
import org.fdroid.test.TestRepoUtils.getRandomRepo
|
||||
@@ -116,13 +116,17 @@ internal class RepositoryDiffTest : DbTest() {
|
||||
fun antiFeaturesDiff() {
|
||||
val repo = getRandomRepo().copy(antiFeatures = getRandomMap {
|
||||
getRandomString() to AntiFeatureV2(
|
||||
icon = getRandomFileV2(),
|
||||
icon = getRandomLocalizedFileV2(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
)
|
||||
})
|
||||
val antiFeatures = repo.antiFeatures.randomDiff {
|
||||
AntiFeatureV2(getRandomFileV2(), getRandomLocalizedTextV2(), getRandomLocalizedTextV2())
|
||||
AntiFeatureV2(
|
||||
icon = getRandomLocalizedFileV2(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
)
|
||||
}
|
||||
val json = """
|
||||
{
|
||||
@@ -141,7 +145,7 @@ internal class RepositoryDiffTest : DbTest() {
|
||||
fun antiFeatureKeyChangeDiff() {
|
||||
val antiFeatureKey = getRandomString()
|
||||
val antiFeature = AntiFeatureV2(
|
||||
icon = getRandomFileV2(),
|
||||
icon = getRandomLocalizedFileV2(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
)
|
||||
@@ -150,7 +154,7 @@ internal class RepositoryDiffTest : DbTest() {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val newAntiFeatures = mapOf(antiFeatureKey to antiFeature.copy(
|
||||
icon = null,
|
||||
icon = emptyMap(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
))
|
||||
@@ -173,13 +177,17 @@ internal class RepositoryDiffTest : DbTest() {
|
||||
fun categoriesDiff() {
|
||||
val repo = getRandomRepo().copy(categories = getRandomMap {
|
||||
getRandomString() to CategoryV2(
|
||||
icon = getRandomFileV2(),
|
||||
icon = getRandomLocalizedFileV2(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
)
|
||||
})
|
||||
val categories = repo.categories.randomDiff {
|
||||
CategoryV2(getRandomFileV2(), getRandomLocalizedTextV2(), getRandomLocalizedTextV2())
|
||||
CategoryV2(
|
||||
icon = getRandomLocalizedFileV2(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
)
|
||||
}
|
||||
val json = """
|
||||
{
|
||||
|
||||
@@ -31,8 +31,8 @@ internal object TestUtils {
|
||||
val expectedAntiFeatures = repoV2.antiFeatures.toRepoAntiFeatures(repoId).toSet()
|
||||
assertEquals(expectedAntiFeatures, repo.antiFeatures.toSet())
|
||||
// categories
|
||||
val expectedCategories = repoV2.categories.toRepoCategories(repoId).toSet()
|
||||
assertEquals(expectedCategories, repo.categories.toSet())
|
||||
val expectedCategories = repoV2.categories.toRepoCategories(repoId).sortedBy { it.id }
|
||||
assertEquals(expectedCategories, repo.categories.sortedBy { it.id })
|
||||
// release channels
|
||||
val expectedReleaseChannels = repoV2.releaseChannels.toRepoReleaseChannel(repoId).toSet()
|
||||
assertEquals(expectedReleaseChannels, repo.releaseChannels.toSet())
|
||||
|
||||
@@ -237,7 +237,7 @@ internal fun MirrorV2.toMirror(repoId: Long) = Mirror(
|
||||
* An attribute belonging to a [Repository].
|
||||
*/
|
||||
public abstract class RepoAttribute {
|
||||
public abstract val icon: FileV2?
|
||||
public abstract val icon: LocalizedFileV2
|
||||
internal abstract val name: LocalizedTextV2
|
||||
internal abstract val description: LocalizedTextV2
|
||||
|
||||
@@ -264,7 +264,7 @@ public abstract class RepoAttribute {
|
||||
public data class AntiFeature internal constructor(
|
||||
internal val repoId: Long,
|
||||
internal val id: String,
|
||||
@Embedded(prefix = "icon_") public override val icon: FileV2? = null,
|
||||
override val icon: LocalizedFileV2,
|
||||
override val name: LocalizedTextV2,
|
||||
override val description: LocalizedTextV2,
|
||||
) : RepoAttribute() {
|
||||
@@ -299,7 +299,7 @@ internal fun Map<String, AntiFeatureV2>.toRepoAntiFeatures(repoId: Long) = map {
|
||||
public data class Category internal constructor(
|
||||
internal val repoId: Long,
|
||||
public val id: String,
|
||||
@Embedded(prefix = "icon_") public override val icon: FileV2? = null,
|
||||
override val icon: LocalizedFileV2,
|
||||
override val name: LocalizedTextV2,
|
||||
override val description: LocalizedTextV2,
|
||||
) : RepoAttribute() {
|
||||
@@ -334,7 +334,7 @@ internal fun Map<String, CategoryV2>.toRepoCategories(repoId: Long) = map {
|
||||
public data class ReleaseChannel(
|
||||
internal val repoId: Long,
|
||||
internal val id: String,
|
||||
@Embedded(prefix = "icon_") public override val icon: FileV2? = null,
|
||||
override val icon: LocalizedFileV2 = emptyMap(),
|
||||
override val name: LocalizedTextV2,
|
||||
override val description: LocalizedTextV2,
|
||||
) : RepoAttribute() {
|
||||
|
||||
@@ -272,7 +272,7 @@ internal interface RepositoryDaoInt : RepositoryDao {
|
||||
jsonObjectKey = "antiFeatures",
|
||||
itemList = repo.antiFeatures,
|
||||
itemFinder = { key, item -> item.id == key },
|
||||
newItem = { key -> AntiFeature(repoId, key, null, emptyMap(), emptyMap()) },
|
||||
newItem = { key -> AntiFeature(repoId, key, emptyMap(), emptyMap(), emptyMap()) },
|
||||
deleteAll = { deleteAntiFeatures(repoId) },
|
||||
deleteOne = { key -> deleteAntiFeature(repoId, key) },
|
||||
insertReplace = { list -> insertAntiFeatures(list) },
|
||||
@@ -283,7 +283,7 @@ internal interface RepositoryDaoInt : RepositoryDao {
|
||||
jsonObjectKey = "categories",
|
||||
itemList = repo.categories,
|
||||
itemFinder = { key, item -> item.id == key },
|
||||
newItem = { key -> Category(repoId, key, null, emptyMap(), emptyMap()) },
|
||||
newItem = { key -> Category(repoId, key, emptyMap(), emptyMap(), emptyMap()) },
|
||||
deleteAll = { deleteCategories(repoId) },
|
||||
deleteOne = { key -> deleteCategory(repoId, key) },
|
||||
insertReplace = { list -> insertCategories(list) },
|
||||
@@ -294,7 +294,7 @@ internal interface RepositoryDaoInt : RepositoryDao {
|
||||
jsonObjectKey = "releaseChannels",
|
||||
itemList = repo.releaseChannels,
|
||||
itemFinder = { key, item -> item.id == key },
|
||||
newItem = { key -> ReleaseChannel(repoId, key, null, emptyMap(), emptyMap()) },
|
||||
newItem = { key -> ReleaseChannel(repoId, key, emptyMap(), emptyMap(), emptyMap()) },
|
||||
deleteAll = { deleteReleaseChannels(repoId) },
|
||||
deleteOne = { key -> deleteReleaseChannel(repoId, key) },
|
||||
insertReplace = { list -> insertReleaseChannels(list) },
|
||||
|
||||
@@ -70,6 +70,8 @@ kotlin {
|
||||
}
|
||||
}
|
||||
androidTest {
|
||||
// needed because of https://issuetracker.google.com/issues/231701341
|
||||
kotlin.srcDir("src/commonTest/kotlin")
|
||||
dependencies {
|
||||
implementation 'junit:junit:4.13.2'
|
||||
implementation 'io.mockk:mockk:1.12.4'
|
||||
|
||||
@@ -17,8 +17,10 @@ import kotlinx.serialization.serializer
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
/**
|
||||
* A class using Kotlin reflection to implement JSON Merge Patch (RFC 7386) against data classes.
|
||||
@@ -28,32 +30,9 @@ import kotlin.reflect.full.primaryConstructor
|
||||
*/
|
||||
public object ReflectionDiffer {
|
||||
|
||||
@Throws(SerializationException::class)
|
||||
public fun applyDiff(
|
||||
obj: Map<String, *>,
|
||||
diff: JsonObject,
|
||||
isFileV2: Boolean = false,
|
||||
): Map<String, *> = obj.toMutableMap().apply {
|
||||
diff.entries.forEach { (key, value) ->
|
||||
when (value) {
|
||||
is JsonNull -> remove(key)
|
||||
is JsonPrimitive -> set(key, value.jsonPrimitive.content)
|
||||
is JsonObject -> {
|
||||
val newValue: Any = if (isFileV2) {
|
||||
constructFromJson(FileV2::class.primaryConstructor!!, value.jsonObject)
|
||||
} else {
|
||||
applyDiff(HashMap<String, LocalizedTextV2>(), value.jsonObject)
|
||||
}
|
||||
set(key, newValue)
|
||||
}
|
||||
else -> e("unsupported map value: $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SerializationException::class)
|
||||
public fun <T : Any> applyDiff(obj: T, diff: JsonObject): T {
|
||||
val constructor = obj::class.primaryConstructor ?: e("no primary constructor")
|
||||
val constructor = obj::class.primaryConstructor ?: e("no primary constructor ${obj::class}")
|
||||
val params = HashMap<KParameter, Any?>()
|
||||
constructor.parameters.forEach { parameter ->
|
||||
val prop = obj::class.memberProperties.find { memberProperty ->
|
||||
@@ -79,14 +58,7 @@ public object ReflectionDiffer {
|
||||
List::class -> diff[prop.name]?.jsonArrayOrNull()?.map {
|
||||
it.primitiveOrNull()?.contentOrNull ?: e("${prop.name} non-primitive array")
|
||||
} ?: e("${prop.name} no array")
|
||||
Map::class -> if (prop.name == "icon") applyDiff(
|
||||
obj = prop.getter.call(obj) as? Map<String, *> ?: emptyMap<String, FileV2>(),
|
||||
diff = diff[prop.name]?.jsonObjectOrNull() ?: e("${prop.name} no map"),
|
||||
isFileV2 = true, // yes this is super hacky
|
||||
) else applyDiff(
|
||||
obj = prop.getter.call(obj) as? Map<String, *> ?: emptyMap<String, String>(),
|
||||
diff = diff[prop.name]?.jsonObjectOrNull() ?: e("${prop.name} no map"),
|
||||
)
|
||||
Map::class -> diffMap(prop.returnType, prop.getter.call(obj), prop.name, diff)
|
||||
else -> {
|
||||
val newObj = prop.getter.call(obj)
|
||||
val jsonObject = diff[prop.name] as? JsonObject ?: e("${prop.name} no dict")
|
||||
@@ -134,10 +106,7 @@ public object ReflectionDiffer {
|
||||
List::class -> diff[prop.name]?.jsonArrayOrNull()?.map {
|
||||
it.primitiveOrNull()?.contentOrNull ?: e("${prop.name} non-primitive array")
|
||||
} ?: e("${prop.name} no array")
|
||||
Map::class -> applyDiff(
|
||||
obj = HashMap<String, String>(),
|
||||
diff = diff[prop.name]?.jsonObjectOrNull() ?: e("${prop.name} no dict"),
|
||||
)
|
||||
Map::class -> diffMap(prop.type, null, prop.name, diff)
|
||||
else -> constructFromJson(
|
||||
factory = (prop.type.classifier as KClass<*>).primaryConstructor!!,
|
||||
diff = diff[prop.name]?.jsonObjectOrNull() ?: e("${prop.name} no dict"),
|
||||
@@ -147,6 +116,93 @@ public object ReflectionDiffer {
|
||||
return factory.callBy(params)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> diffMap(type: KType, obj: T?, key: String?, diff: JsonObject) = when (type) {
|
||||
typeOf<LocalizedTextV2>() -> applyTextDiff(
|
||||
obj = obj as? LocalizedTextV2 ?: HashMap(),
|
||||
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
|
||||
)
|
||||
typeOf<LocalizedTextV2?>() -> applyTextDiff(
|
||||
obj = obj as? LocalizedTextV2? ?: HashMap(),
|
||||
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
|
||||
)
|
||||
typeOf<LocalizedFileV2>() -> applyFileDiff(
|
||||
obj = obj as? LocalizedFileV2 ?: HashMap(),
|
||||
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
|
||||
)
|
||||
typeOf<LocalizedFileV2?>() -> applyFileDiff(
|
||||
obj = obj as? LocalizedFileV2? ?: HashMap(),
|
||||
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
|
||||
)
|
||||
typeOf<Map<String, LocalizedTextV2>>() -> applyMapTextDiff(
|
||||
obj = obj as? Map<String, LocalizedTextV2> ?: HashMap(),
|
||||
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
|
||||
)
|
||||
typeOf<Map<String, LocalizedTextV2>?>() -> applyMapTextDiff(
|
||||
obj = obj as? Map<String, LocalizedTextV2>? ?: HashMap(),
|
||||
diff = diff[key]?.jsonObjectOrNull() ?: e("$key no map"),
|
||||
)
|
||||
else -> e("Unknown map: $key: $type = ${diff[key]}")
|
||||
}
|
||||
|
||||
@Throws(SerializationException::class)
|
||||
private fun applyTextDiff(
|
||||
obj: LocalizedTextV2,
|
||||
diff: JsonObject,
|
||||
): LocalizedTextV2 = obj.toMutableMap().apply {
|
||||
diff.entries.forEach { (locale, textElement) ->
|
||||
if (textElement is JsonNull) {
|
||||
remove(locale)
|
||||
return@forEach
|
||||
}
|
||||
val text = textElement.primitiveOrNull()?.contentOrNull
|
||||
?: throw SerializationException("no string: $textElement")
|
||||
set(locale, text)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SerializationException::class)
|
||||
private fun applyFileDiff(
|
||||
obj: LocalizedFileV2,
|
||||
diff: JsonObject,
|
||||
): LocalizedFileV2 = obj.toMutableMap().apply {
|
||||
diff.entries.forEach { (locale, fileV2Element) ->
|
||||
if (fileV2Element is JsonNull) {
|
||||
remove(locale)
|
||||
return@forEach
|
||||
}
|
||||
val fileV2Object = fileV2Element.jsonObjectOrNull()
|
||||
?: throw SerializationException("no FileV2: $fileV2Element")
|
||||
val fileV2 = if (locale in obj) {
|
||||
applyDiff(obj[locale] as FileV2, fileV2Object)
|
||||
} else {
|
||||
constructFromJson(FileV2::class.primaryConstructor!!, fileV2Object)
|
||||
}
|
||||
set(locale, fileV2)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SerializationException::class)
|
||||
private fun applyMapTextDiff(
|
||||
obj: Map<String, LocalizedTextV2>,
|
||||
diff: JsonObject,
|
||||
): Map<String, LocalizedTextV2> = obj.toMutableMap().apply {
|
||||
diff.entries.forEach { (key, localizedTextElement) ->
|
||||
if (localizedTextElement is JsonNull) {
|
||||
remove(key)
|
||||
return@forEach
|
||||
}
|
||||
val localizedTextObject = localizedTextElement.jsonObjectOrNull()
|
||||
?: throw SerializationException("no FileV2: $localizedTextElement")
|
||||
val localizedText = if (key in obj) {
|
||||
applyTextDiff(obj[key] as LocalizedTextV2, localizedTextObject)
|
||||
} else {
|
||||
applyTextDiff(HashMap(), localizedTextObject)
|
||||
}
|
||||
set(key, localizedText)
|
||||
}
|
||||
}
|
||||
|
||||
private fun JsonElement.primitiveOrNull(): JsonPrimitive? = try {
|
||||
jsonPrimitive
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.fdroid.test.DiffUtils.clean
|
||||
import org.fdroid.test.DiffUtils.cleanMetadata
|
||||
import org.fdroid.test.DiffUtils.cleanRepo
|
||||
import org.fdroid.test.DiffUtils.cleanVersion
|
||||
import org.fdroid.test.LOCALE
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
@@ -66,6 +67,55 @@ internal class ReflectionDifferTest {
|
||||
endPath = "$assetPath/index-max-v2.json",
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testLocalizedFileV2() {
|
||||
val category1 = CategoryV2(
|
||||
name = mapOf(LOCALE to "Cat1"),
|
||||
icon = mapOf(LOCALE to FileV2(
|
||||
name = "file1",
|
||||
sha256 = "hash",
|
||||
size = 1,
|
||||
)),
|
||||
)
|
||||
val category2 = CategoryV2(
|
||||
name = mapOf(LOCALE to "Cat2"),
|
||||
)
|
||||
val diff1 = JsonObject(
|
||||
mapOf("icon" to JsonObject(
|
||||
mapOf(LOCALE to JsonObject(
|
||||
mapOf("name" to JsonPrimitive("file1b"))
|
||||
))
|
||||
))
|
||||
)
|
||||
val diff2 = JsonObject(
|
||||
mapOf("icon" to JsonObject(
|
||||
mapOf(LOCALE to JsonObject(
|
||||
mapOf(
|
||||
"name" to JsonPrimitive("file2"),
|
||||
"sha256" to JsonPrimitive("hash2"),
|
||||
"size" to JsonPrimitive(2L),
|
||||
)
|
||||
))
|
||||
))
|
||||
)
|
||||
val diffedCat1 = ReflectionDiffer.applyDiff(category1, diff1)
|
||||
val diffedCat2 = ReflectionDiffer.applyDiff(category2, diff2)
|
||||
val diffedIcon1 = diffedCat1.icon[LOCALE]
|
||||
val diffedIcon2 = diffedCat2.icon[LOCALE]
|
||||
val expectedIcon1 = FileV2(
|
||||
name = "file1b",
|
||||
sha256 = "hash",
|
||||
size = 1,
|
||||
)
|
||||
val expectedIcon2 = FileV2(
|
||||
name = "file2",
|
||||
sha256 = "hash2",
|
||||
size = 2,
|
||||
)
|
||||
assertEquals(expectedIcon1, diffedIcon1)
|
||||
assertEquals(expectedIcon2, diffedIcon2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClassWithoutPrimaryConstructor() {
|
||||
class NoConstructor {
|
||||
|
||||
@@ -58,6 +58,7 @@ public data class FileV2(
|
||||
if (string == null) return null
|
||||
return json.decodeFromString(string)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun fromPath(path: String): FileV2 = FileV2(path)
|
||||
}
|
||||
@@ -93,8 +94,8 @@ public data class RepoV2(
|
||||
) {
|
||||
public fun walkFiles(fileConsumer: (FileV2?) -> Unit) {
|
||||
icon.values.forEach { fileConsumer(it) }
|
||||
antiFeatures.values.forEach { fileConsumer(it.icon) }
|
||||
categories.values.forEach { fileConsumer(it.icon) }
|
||||
antiFeatures.values.forEach { it.icon.values.forEach { icon -> fileConsumer(icon) } }
|
||||
categories.values.forEach { it.icon.values.forEach { icon -> fileConsumer(icon) } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,14 +111,14 @@ public data class MirrorV2(
|
||||
|
||||
@Serializable
|
||||
public data class AntiFeatureV2(
|
||||
val icon: FileV2? = null,
|
||||
val icon: LocalizedFileV2 = emptyMap(),
|
||||
val name: LocalizedTextV2,
|
||||
val description: LocalizedTextV2 = emptyMap(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class CategoryV2(
|
||||
val icon: FileV2? = null,
|
||||
val icon: LocalizedFileV2 = emptyMap(),
|
||||
val name: LocalizedTextV2,
|
||||
val description: LocalizedTextV2 = emptyMap(),
|
||||
)
|
||||
|
||||
@@ -47,9 +47,11 @@
|
||||
},
|
||||
"AntiFeature": {
|
||||
"icon": {
|
||||
"name": "/icons/antifeature.png",
|
||||
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 254916
|
||||
"en-US": {
|
||||
"name": "/icons/antifeature.png",
|
||||
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 254916
|
||||
}
|
||||
}
|
||||
},
|
||||
"NonFreeNet": {
|
||||
@@ -70,9 +72,11 @@
|
||||
"System": null,
|
||||
"Cat3": {
|
||||
"icon": {
|
||||
"name": "/icons/cat3.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat3.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat3"
|
||||
@@ -83,9 +87,11 @@
|
||||
},
|
||||
"Cat2": {
|
||||
"icon": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat3"
|
||||
@@ -96,7 +102,9 @@
|
||||
},
|
||||
"Cat1": {
|
||||
"icon": {
|
||||
"name": "/icons/cat1.png"
|
||||
"en-US": {
|
||||
"name": "/icons/cat1.png"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"en-US": "Cat1"
|
||||
|
||||
@@ -45,9 +45,11 @@
|
||||
},
|
||||
"AntiFeature": {
|
||||
"icon": {
|
||||
"name": "/icons/antifeature.png",
|
||||
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 254916
|
||||
"en-US": {
|
||||
"name": "/icons/antifeature.png",
|
||||
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 254916
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "AntiFeature"
|
||||
@@ -73,9 +75,11 @@
|
||||
"categories": {
|
||||
"Cat3": {
|
||||
"icon": {
|
||||
"name": "/icons/cat3.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat3.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat3"
|
||||
@@ -86,9 +90,11 @@
|
||||
},
|
||||
"Cat2": {
|
||||
"icon": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat3"
|
||||
@@ -99,9 +105,11 @@
|
||||
},
|
||||
"Cat1": {
|
||||
"icon": {
|
||||
"name": "/icons/cat1.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat1.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat1"
|
||||
|
||||
@@ -45,9 +45,11 @@
|
||||
},
|
||||
"AntiFeature": {
|
||||
"icon": {
|
||||
"name": "/icons/antifeature.png",
|
||||
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 254916
|
||||
"en-US": {
|
||||
"name": "/icons/antifeature.png",
|
||||
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 254916
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "AntiFeature"
|
||||
@@ -73,9 +75,11 @@
|
||||
"categories": {
|
||||
"Cat3": {
|
||||
"icon": {
|
||||
"name": "/icons/cat3.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat3.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat3"
|
||||
@@ -86,9 +90,11 @@
|
||||
},
|
||||
"Cat2": {
|
||||
"icon": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat3"
|
||||
@@ -99,9 +105,11 @@
|
||||
},
|
||||
"Cat1": {
|
||||
"icon": {
|
||||
"name": "/icons/cat1.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat1.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat1"
|
||||
|
||||
@@ -40,9 +40,11 @@
|
||||
"categories": {
|
||||
"Cat1": {
|
||||
"icon": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat1"
|
||||
|
||||
@@ -40,9 +40,11 @@
|
||||
"categories": {
|
||||
"Cat1": {
|
||||
"icon": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat1"
|
||||
|
||||
@@ -49,9 +49,11 @@
|
||||
},
|
||||
"AntiFeature": {
|
||||
"icon": {
|
||||
"name": "/icons/antifeature.png",
|
||||
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 254916
|
||||
"en-US": {
|
||||
"name": "/icons/antifeature.png",
|
||||
"sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 254916
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "AntiFeature"
|
||||
@@ -77,9 +79,11 @@
|
||||
"categories": {
|
||||
"Cat3": {
|
||||
"icon": {
|
||||
"name": "/icons/cat3.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat3.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat3"
|
||||
@@ -90,9 +94,11 @@
|
||||
},
|
||||
"Cat2": {
|
||||
"icon": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat3"
|
||||
@@ -103,9 +109,11 @@
|
||||
},
|
||||
"Cat1": {
|
||||
"icon": {
|
||||
"name": "/icons/cat1.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat1.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat1"
|
||||
|
||||
@@ -40,9 +40,11 @@
|
||||
"categories": {
|
||||
"Cat1": {
|
||||
"icon": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
"en-US": {
|
||||
"name": "/icons/cat2.png",
|
||||
"sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
"size": 9223372036854775807
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"en-US": "Cat1"
|
||||
|
||||
@@ -187,11 +187,11 @@ object TestDataMidV2 {
|
||||
categories = mapOf(
|
||||
"Cat1" to CategoryV2(
|
||||
name = mapOf(LOCALE to "Cat1"),
|
||||
icon = FileV2(
|
||||
icon = mapOf(LOCALE to FileV2(
|
||||
name = "/icons/cat2.png",
|
||||
sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
size = Long.MAX_VALUE,
|
||||
),
|
||||
)),
|
||||
),
|
||||
"System" to CategoryV2(
|
||||
name = emptyMap(),
|
||||
@@ -737,11 +737,11 @@ object TestDataMaxV2 {
|
||||
name = emptyMap(),
|
||||
),
|
||||
"AntiFeature" to AntiFeatureV2(
|
||||
icon = FileV2(
|
||||
icon = mapOf(LOCALE to FileV2(
|
||||
name = "/icons/antifeature.png",
|
||||
sha256 = "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
size = 254916,
|
||||
),
|
||||
)),
|
||||
name = mapOf(LOCALE to "AntiFeature"),
|
||||
description = mapOf(LOCALE to "A bad anti-feature, we can't show to users."),
|
||||
),
|
||||
@@ -756,29 +756,29 @@ object TestDataMaxV2 {
|
||||
categories = mapOf(
|
||||
"Cat3" to CategoryV2(
|
||||
name = mapOf(LOCALE to "Cat3"),
|
||||
icon = FileV2(
|
||||
icon = mapOf(LOCALE to FileV2(
|
||||
name = "/icons/cat3.png",
|
||||
sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
size = Long.MAX_VALUE,
|
||||
),
|
||||
)),
|
||||
description = mapOf(LOCALE to "Cat3"),
|
||||
),
|
||||
"Cat2" to CategoryV2(
|
||||
name = mapOf(LOCALE to "Cat3"),
|
||||
icon = FileV2(
|
||||
icon = mapOf(LOCALE to FileV2(
|
||||
name = "/icons/cat2.png",
|
||||
sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
size = Long.MAX_VALUE,
|
||||
),
|
||||
)),
|
||||
description = mapOf(LOCALE to "Cat3"),
|
||||
),
|
||||
"Cat1" to CategoryV2(
|
||||
name = mapOf(LOCALE to "Cat1"),
|
||||
icon = FileV2(
|
||||
icon = mapOf(LOCALE to FileV2(
|
||||
name = "/icons/cat1.png",
|
||||
sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def",
|
||||
size = Long.MAX_VALUE,
|
||||
),
|
||||
)),
|
||||
description = mapOf(LOCALE to "Cat1"),
|
||||
),
|
||||
"NoMoreSystem" to CategoryV2(
|
||||
|
||||
@@ -46,14 +46,14 @@ object TestRepoUtils {
|
||||
timestamp = System.currentTimeMillis(),
|
||||
antiFeatures = TestUtils.getRandomMap {
|
||||
getRandomString() to AntiFeatureV2(
|
||||
icon = getRandomFileV2(),
|
||||
icon = getRandomLocalizedFileV2(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
)
|
||||
},
|
||||
categories = TestUtils.getRandomMap {
|
||||
getRandomString() to CategoryV2(
|
||||
icon = getRandomFileV2(),
|
||||
icon = getRandomLocalizedFileV2(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user