Add support for location plugins (#772)
* Cherry pick location refactor * Refactor :data:openstreetmaps to :data:locations * contract, plugin sdk * Implement serialization, module tweaks * Include check for out-of-date departures in SearchableItemVM.requestUpdatedSearchable() * settings for location plugins * Try not to be too lazy * more fiddling with the plugin SDK * add departures in MapView with mock data for debug builds * change icons * add boats * animate departure lazycolumn * Add MarqueeText for text overflow handling * Define height for Departures in case there is no map to display * Don't inclure railway / highway tags for OSM since there will be location plugins for that * sort by time * - Apply pre-merge changes to LocationSettings - Add banner warning about slowed down location search for large search radii * ditch `showLocationOnMap` * LocationItem: make `showOpeningSchedule` toggleable * LocationItem: make Navigation AssistChip work for people that don't have google maps installed * LocationItem: resolve TODOs * MapTiles: ditch unused code, animate userIndicator * Reintroduce departure list * Add LineColor * Add osm tag `stars` as `userRating` https://taginfo.openstreetmap.org/keys/stars#overview * typealias -> import * Don't add Navigation Chip when there is no way to resolve navigation intents * Add settings migration * Set plugin SDK version to 1.2.0-SNAPSHOT * Deduplicate shared plugin classes, use kotlinx.serialization * Fix imports * Use ZonedDateTime for depature times * Add more line types * Rewrite location serialization * Replace street/houseNumber with address * Add attribution field * Add plugin config * Reject location search requests without lat lon parameters * Add default values to plugin location class * Don't crash if column value is null * Add docs comments to LocationCategory values * Refactor OpeningSchedule as polymorphic * remove dead corpse *ahem* code * Split LocationCategory into category and icon (Also update to Kotlin 2.0, please don't do this at home) * Add more location icons * Fix (?) location deserializer * Add more location icons * More icons * Meh * Add Pub * Disable Github Maven repo if credentials are missing * Add location search specific settings to plugin details screen * Add language parameter * Unbreak the build * Refactor plugin SDK (with breaking changes) * Set plugin SDK version to 2.0.0-SNAPSHOT * Document SDK breaking changes * Implement LocationProvider.getQuery * Add a typesafe cursor API * Oops I did it again next time maybe check if the code is actually compiling before pushing * Add missing return statement * Fix list serialization * Departure time UI adjustment * Use typesafe cursor for weather plugins * Add userRatingCount and emailAddress fields * grrr * Rename and extend LineTypes * Add default lineType to Departure to fix serialization errors * Fix refreshing stored plugin locations * Adapt line name column width to available departures * Fix plugin settings screen category overlap * add LocationItem.GenericTransit * Fix crash during deserialization of locations * Update SDK docs * Replace plugin "official" mark with "verified developer" mark before anyone gets sued * show 'now' when departure is in less than one minute * Add typesafe Bundle API * Implement plugin API changes * Plugin SDK: Fix refresh result not being returned * Update docs * apply alpha to departures that have departured * better (maybe): reduce saturation instead of alpha * Add default values for Attribution * Display attribution * Rearrange location result layout * Reduce searchable update interval to 1 minute * Pass last update time to refresh function * Change refresh path and ensure that timestamp is only update when the item was updated * categorize osm location * Update docs * Optimize location search - run providers in parallel - flatten code * add experimental address parsing for OSM * add poi_category_townhall * Fix popup closing when favorites items are updated * Revert "Fix popup closing when favorites items are updated" This reverts commit fc517fd066c7f8109b6d6df2d4f536af66398207. * Fork AndroidAddressFormatter to `:libs:address-formatter` * migrate `:libs:address-formatter` dependencies to version catalog and update them * also consider addr:{suburb,hamlet} for `Address.city` if city tag is missing * Move poi strings back to strings.xml * Update Jetpack Compose * Move address-formatter back to its original package, add license and readme * Move address-formatter back to its original package --------- Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
@@ -24,6 +24,7 @@ import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchableDeserializer
|
||||
import de.mm20.launcher2.search.SearchableSerializer
|
||||
import de.mm20.launcher2.search.UpdateResult
|
||||
import de.mm20.launcher2.search.asUpdateResult
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
@@ -312,8 +313,7 @@ internal class OwncloudFileDeserializer : SearchableDeserializer {
|
||||
}
|
||||
}
|
||||
|
||||
internal class PluginFileSerializer(
|
||||
) : SearchableSerializer {
|
||||
internal class PluginFileSerializer : SearchableSerializer {
|
||||
override fun serialize(searchable: SavableSearchable): String? {
|
||||
searchable as PluginFile
|
||||
if (searchable.storageStrategy == StorageStrategy.StoreReference) {
|
||||
@@ -333,7 +333,8 @@ internal class PluginFileSerializer(
|
||||
"thumbnailUri" to searchable.thumbnailUri?.toString(),
|
||||
"isDirectory" to searchable.isDirectory,
|
||||
"authority" to searchable.authority,
|
||||
"strategy" to if (searchable.storageStrategy == StorageStrategy.StoreCopy) "copy" else "deferred",
|
||||
"timestamp" to searchable.timestamp,
|
||||
"strategy" to "copy",
|
||||
).toString()
|
||||
}
|
||||
}
|
||||
@@ -348,84 +349,46 @@ internal class PluginFileDeserializer(
|
||||
private val pluginRepository: PluginRepository,
|
||||
) : SearchableDeserializer {
|
||||
override suspend fun deserialize(serialized: String): SavableSearchable? {
|
||||
val jsonObject = JSONObject(serialized)
|
||||
val obj = JSONObject(serialized)
|
||||
|
||||
return when (jsonObject.optString("strategy", "copy")) {
|
||||
"ref" -> {
|
||||
getByRef(jsonObject)
|
||||
}
|
||||
|
||||
"deferred" -> {
|
||||
getDeferred(jsonObject)
|
||||
}
|
||||
|
||||
else -> {
|
||||
getByCopy(jsonObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getByRef(obj: JSONObject): File? {
|
||||
val authority = obj.getString("authority")
|
||||
val id = obj.getString("id")
|
||||
val plugin = pluginRepository.get(authority).firstOrNull() ?: return null
|
||||
if (!plugin.enabled) return null
|
||||
val provider = PluginFileProvider(context, authority)
|
||||
try {
|
||||
val authority = obj.getString("authority")
|
||||
val id = obj.getString("id")
|
||||
val plugin = pluginRepository.get(authority).firstOrNull() ?: return null
|
||||
if (!plugin.enabled) return null
|
||||
val provider = PluginFileProvider(context, authority)
|
||||
return provider.getFile(id)
|
||||
} catch (e: Exception) {
|
||||
CrashReporter.logException(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
return when (obj.optString("strategy", "copy")) {
|
||||
"ref" -> {
|
||||
provider.get(id).getOrNull()
|
||||
}
|
||||
|
||||
private fun getDeferred(obj: JSONObject): File? {
|
||||
val cached = getByCopy(obj) ?: return null
|
||||
val timestamp = obj.optLong("timestamp", 0L)
|
||||
return DeferredFile(
|
||||
cachedFile = cached as PluginFile,
|
||||
timestamp = timestamp,
|
||||
updatedSelf = {
|
||||
val plugin = pluginRepository.get(cached.authority).firstOrNull()
|
||||
?: return@DeferredFile UpdateResult.PermanentlyUnavailable()
|
||||
if (!plugin.enabled) return@DeferredFile UpdateResult.PermanentlyUnavailable()
|
||||
val provider = PluginFileProvider(context, cached.authority)
|
||||
try {
|
||||
val file = provider.getFile(cached.id)
|
||||
if (file == null) {
|
||||
UpdateResult.PermanentlyUnavailable()
|
||||
} else {
|
||||
UpdateResult.Success(file)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
CrashReporter.logException(e)
|
||||
UpdateResult.TemporarilyUnavailable(e)
|
||||
else -> {
|
||||
val uri = obj.getString("uri")
|
||||
val thumbnailUri = obj.optString("thumbnailUri")
|
||||
val timestamp = obj.optLong("timestamp", 0L)
|
||||
val file = PluginFile(
|
||||
id = obj.getString("id"),
|
||||
path = obj.getString("path"),
|
||||
mimeType = obj.getString("mimeType"),
|
||||
size = obj.optLong("size", 0L),
|
||||
metaData = persistentMapOf(),
|
||||
label = obj.getString("label"),
|
||||
uri = Uri.parse(uri),
|
||||
thumbnailUri = thumbnailUri.takeIf { it.isNotEmpty() }?.let { Uri.parse(it) },
|
||||
storageStrategy = StorageStrategy.StoreCopy,
|
||||
isDirectory = obj.optBoolean("isDirectory", false),
|
||||
authority = obj.getString("authority"),
|
||||
timestamp = timestamp,
|
||||
updatedSelf = {
|
||||
if (it !is PluginFile) UpdateResult.TemporarilyUnavailable()
|
||||
else provider.refresh(it, timestamp).asUpdateResult()
|
||||
}
|
||||
)
|
||||
return file
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun getByCopy(obj: JSONObject): File? {
|
||||
try {
|
||||
val uri = obj.getString("uri")
|
||||
val thumbnailUri = obj.optString("thumbnailUri")
|
||||
return PluginFile(
|
||||
id = obj.getString("id"),
|
||||
path = obj.getString("path"),
|
||||
mimeType = obj.getString("mimeType"),
|
||||
size = obj.optLong("size", 0L),
|
||||
metaData = persistentMapOf(),
|
||||
label = obj.getString("label"),
|
||||
uri = Uri.parse(uri),
|
||||
thumbnailUri = thumbnailUri.takeIf { it.isNotEmpty() }?.let { Uri.parse(it) },
|
||||
storageStrategy = StorageStrategy.StoreCopy,
|
||||
isDirectory = obj.optBoolean("isDirectory", false),
|
||||
authority = obj.getString("authority"),
|
||||
)
|
||||
} catch (e: JSONException) {
|
||||
CrashReporter.logException(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package de.mm20.launcher2.files.providers
|
||||
|
||||
import de.mm20.launcher2.search.File
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.UpdatableSearchable
|
||||
import de.mm20.launcher2.search.UpdateResult
|
||||
|
||||
class DeferredFile(
|
||||
cachedFile: File,
|
||||
override val timestamp: Long,
|
||||
override var updatedSelf: (suspend () -> UpdateResult<File>)? = null,
|
||||
override var updatedSelf: (suspend (SavableSearchable) -> UpdateResult<File>)? = null,
|
||||
) : File by cachedFile, UpdatableSearchable<File>
|
||||
@@ -18,6 +18,8 @@ import de.mm20.launcher2.search.File
|
||||
import de.mm20.launcher2.search.FileMetaType
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchableSerializer
|
||||
import de.mm20.launcher2.search.UpdatableSearchable
|
||||
import de.mm20.launcher2.search.UpdateResult
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -35,7 +37,9 @@ data class PluginFile(
|
||||
val authority: String,
|
||||
internal val storageStrategy: StorageStrategy,
|
||||
override val labelOverride: String? = null,
|
||||
) : File {
|
||||
override val timestamp: Long,
|
||||
override val updatedSelf: (suspend (SavableSearchable) -> UpdateResult<File>)?,
|
||||
) : File, UpdatableSearchable<File> {
|
||||
override val domain: String = Domain
|
||||
|
||||
override val key: String
|
||||
|
||||
+100
-185
@@ -3,100 +3,35 @@ package de.mm20.launcher2.files.providers
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.CancellationSignal
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateUtils
|
||||
import android.util.Log
|
||||
import androidx.core.database.getIntOrNull
|
||||
import androidx.core.database.getLongOrNull
|
||||
import androidx.core.database.getStringOrNull
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.plugin.PluginApi
|
||||
import de.mm20.launcher2.plugin.config.SearchPluginConfig
|
||||
import de.mm20.launcher2.plugin.contracts.FilePluginContract
|
||||
import de.mm20.launcher2.plugin.contracts.PluginContract
|
||||
import de.mm20.launcher2.plugin.QueryPluginApi
|
||||
import de.mm20.launcher2.plugin.config.QueryPluginConfig
|
||||
import de.mm20.launcher2.plugin.contracts.FilePluginContract.FileColumns
|
||||
import de.mm20.launcher2.plugin.contracts.SearchPluginContract
|
||||
import de.mm20.launcher2.search.File
|
||||
import de.mm20.launcher2.plugin.data.set
|
||||
import de.mm20.launcher2.plugin.data.withColumns
|
||||
import de.mm20.launcher2.search.FileMetaType
|
||||
import de.mm20.launcher2.search.UpdateResult
|
||||
import de.mm20.launcher2.search.asUpdateResult
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
class PluginFileProvider(
|
||||
private val context: Context,
|
||||
private val pluginAuthority: String,
|
||||
) : FileProvider {
|
||||
override suspend fun search(query: String, allowNetwork: Boolean): List<File> = withContext(Dispatchers.IO) {
|
||||
val uri = Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority(pluginAuthority)
|
||||
.path(SearchPluginContract.Paths.Search)
|
||||
.appendQueryParameter(SearchPluginContract.Paths.QueryParam, query)
|
||||
.appendQueryParameter(SearchPluginContract.Paths.AllowNetworkParam, allowNetwork.toString())
|
||||
.build()
|
||||
val cancellationSignal = CancellationSignal()
|
||||
) : QueryPluginApi<String, PluginFile>(
|
||||
context, pluginAuthority
|
||||
), FileProvider {
|
||||
|
||||
return@withContext suspendCancellableCoroutine {
|
||||
it.invokeOnCancellation {
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
val cursor = try {
|
||||
context.contentResolver.query(
|
||||
uri,
|
||||
null,
|
||||
null,
|
||||
cancellationSignal
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MM20", "Plugin ${pluginAuthority} threw exception")
|
||||
CrashReporter.logException(e)
|
||||
it.resume(emptyList())
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
if (cursor == null) {
|
||||
Log.e("MM20", "Plugin ${pluginAuthority} returned null cursor")
|
||||
it.resume(emptyList())
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
val results = fromCursor(cursor) ?: emptyList()
|
||||
it.resume(results)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPluginConfig(): SearchPluginConfig? {
|
||||
private fun getPluginConfig(): QueryPluginConfig? {
|
||||
return PluginApi(pluginAuthority, context.contentResolver).getSearchPluginConfig()
|
||||
}
|
||||
|
||||
suspend fun getFile(id: String): File? {
|
||||
val uri = Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority(pluginAuthority)
|
||||
.path(SearchPluginContract.Paths.Root)
|
||||
.appendPath(id)
|
||||
.build()
|
||||
val cancellationSignal = CancellationSignal()
|
||||
|
||||
return suspendCancellableCoroutine {
|
||||
it.invokeOnCancellation {
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
val cursor = context.contentResolver.query(
|
||||
uri,
|
||||
null,
|
||||
null,
|
||||
cancellationSignal
|
||||
) ?: return@suspendCancellableCoroutine it.resume(null)
|
||||
|
||||
val results = fromCursor(cursor)
|
||||
it.resume(results?.firstOrNull())
|
||||
}
|
||||
}
|
||||
|
||||
private fun fromCursor(cursor: Cursor): List<File>? {
|
||||
override fun Cursor.getData(): List<PluginFile>? {
|
||||
val config = getPluginConfig()
|
||||
val cursor = this
|
||||
|
||||
if (config == null) {
|
||||
Log.e("MM20", "Plugin ${pluginAuthority} returned null config")
|
||||
@@ -104,119 +39,99 @@ class PluginFileProvider(
|
||||
return null
|
||||
}
|
||||
|
||||
val idIndex = cursor
|
||||
.getColumnIndex(FilePluginContract.FileColumns.Id)
|
||||
.takeIf { it >= 0 }
|
||||
?: return null
|
||||
val pathIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.Path).takeIf { it >= 0 }
|
||||
val typeIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MimeType).takeIf { it >= 0 }
|
||||
val sizeIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.Size).takeIf { it >= 0 }
|
||||
val nameIndex = cursor.getColumnIndex(FilePluginContract.FileColumns.DisplayName)
|
||||
.takeIf { it >= 0 }
|
||||
?: return null
|
||||
val contentUriIndex = cursor.getColumnIndex(FilePluginContract.FileColumns.ContentUri)
|
||||
.takeIf { it >= 0 }
|
||||
?: return null
|
||||
val thumbnailUriIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.ThumbnailUri)
|
||||
.takeIf { it >= 0 }
|
||||
val directoryIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.IsDirectory).takeIf { it >= 0 }
|
||||
|
||||
val ownerIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.Owner).takeIf { it >= 0 }
|
||||
|
||||
val metaTitleIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaTitle).takeIf { it >= 0 }
|
||||
|
||||
val metaArtistIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaArtist).takeIf { it >= 0 }
|
||||
|
||||
val metaAlbumIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaAlbum).takeIf { it >= 0 }
|
||||
|
||||
val metaDurationIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaDuration).takeIf { it >= 0 }
|
||||
|
||||
val metaYearIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaYear).takeIf { it >= 0 }
|
||||
|
||||
val metaWidthIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaWidth).takeIf { it >= 0 }
|
||||
|
||||
val metaHeightIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaHeight).takeIf { it >= 0 }
|
||||
|
||||
val metaLocationIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaLocation).takeIf { it >= 0 }
|
||||
|
||||
val metaAppNameIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaAppName).takeIf { it >= 0 }
|
||||
|
||||
val metaAppPackageNameIndex =
|
||||
cursor.getColumnIndex(FilePluginContract.FileColumns.MetaAppPackageName)
|
||||
.takeIf { it >= 0 }
|
||||
|
||||
val results = mutableListOf<File>()
|
||||
while (cursor.moveToNext()) {
|
||||
results.add(
|
||||
PluginFile(
|
||||
id = cursor.getString(idIndex),
|
||||
path = pathIndex?.let { cursor.getString(it) } ?: "",
|
||||
mimeType = typeIndex?.let { cursor.getString(it) }
|
||||
?: "application/octet-stream",
|
||||
size = sizeIndex?.let { cursor.getLong(it) } ?: 0,
|
||||
metaData = buildMap {
|
||||
metaTitleIndex?.let { cursor.getStringOrNull(it) }?.let {
|
||||
put(FileMetaType.Title, it)
|
||||
}
|
||||
metaArtistIndex?.let { cursor.getStringOrNull(it) }?.let {
|
||||
put(FileMetaType.Artist, it)
|
||||
}
|
||||
metaAlbumIndex?.let { cursor.getStringOrNull(it) }?.let {
|
||||
put(FileMetaType.Album, it)
|
||||
}
|
||||
metaDurationIndex?.let { cursor.getLongOrNull(it) }?.let {
|
||||
put(FileMetaType.Duration, DateUtils.formatElapsedTime(it / 1000L))
|
||||
}
|
||||
metaYearIndex?.let { cursor.getIntOrNull(it) }?.let {
|
||||
put(FileMetaType.Year, it.toString())
|
||||
}
|
||||
if (metaWidthIndex != null && metaHeightIndex != null) {
|
||||
val width = cursor.getIntOrNull(metaWidthIndex)
|
||||
val height = cursor.getIntOrNull(metaHeightIndex)
|
||||
val results = mutableListOf<PluginFile>()
|
||||
val timestamp = System.currentTimeMillis()
|
||||
cursor.withColumns(FileColumns) {
|
||||
while (cursor.moveToNext()) {
|
||||
results.add(
|
||||
PluginFile(
|
||||
id = cursor[FileColumns.Id] ?: continue,
|
||||
path = cursor[FileColumns.Path] ?: "",
|
||||
mimeType = cursor[FileColumns.MimeType] ?: "application/octet-stream",
|
||||
size = cursor[FileColumns.Size] ?: 0L,
|
||||
metaData = buildMap {
|
||||
cursor[FileColumns.MetaTitle]?.let {
|
||||
put(FileMetaType.Title, it)
|
||||
}
|
||||
cursor[FileColumns.MetaArtist]?.let {
|
||||
put(FileMetaType.Artist, it)
|
||||
}
|
||||
cursor[FileColumns.MetaAlbum]?.let {
|
||||
put(FileMetaType.Album, it)
|
||||
}
|
||||
cursor[FileColumns.MetaDuration]?.let {
|
||||
put(FileMetaType.Duration, DateUtils.formatElapsedTime(it / 1000L))
|
||||
}
|
||||
cursor[FileColumns.MetaYear]?.let {
|
||||
put(FileMetaType.Year, it.toString())
|
||||
}
|
||||
val width = cursor[FileColumns.MetaWidth]
|
||||
val height = cursor[FileColumns.MetaHeight]
|
||||
if (width != null && height != null) {
|
||||
put(FileMetaType.Dimensions, "${width}x${height}")
|
||||
}
|
||||
cursor[FileColumns.MetaLocation]?.let {
|
||||
put(FileMetaType.Location, it)
|
||||
}
|
||||
cursor[FileColumns.MetaAppName]?.let {
|
||||
put(FileMetaType.AppName, it)
|
||||
}
|
||||
cursor[FileColumns.MetaAppPackageName]?.let {
|
||||
put(FileMetaType.AppPackageName, it)
|
||||
}
|
||||
cursor[FileColumns.Owner]?.let {
|
||||
put(FileMetaType.Owner, it)
|
||||
}
|
||||
}.toPersistentMap(),
|
||||
label = cursor[FileColumns.DisplayName] ?: continue,
|
||||
uri = cursor[FileColumns.DisplayName]?.let { Uri.parse(it) } ?: continue,
|
||||
thumbnailUri = cursor[FileColumns.ThumbnailUri]?.let { Uri.parse(it) },
|
||||
storageStrategy = config.storageStrategy,
|
||||
isDirectory = cursor[FileColumns.IsDirectory] ?: false,
|
||||
authority = pluginAuthority,
|
||||
timestamp = timestamp,
|
||||
updatedSelf = {
|
||||
if (it !is PluginFile) UpdateResult.TemporarilyUnavailable()
|
||||
else refresh(it, timestamp).asUpdateResult()
|
||||
}
|
||||
metaLocationIndex?.let { cursor.getStringOrNull(it) }?.let {
|
||||
put(FileMetaType.Location, it)
|
||||
}
|
||||
metaAppNameIndex?.let { cursor.getStringOrNull(it) }?.let {
|
||||
put(FileMetaType.AppName, it)
|
||||
}
|
||||
metaAppPackageNameIndex?.let { cursor.getStringOrNull(it) }?.let {
|
||||
put(FileMetaType.AppPackageName, it)
|
||||
}
|
||||
ownerIndex?.let { cursor.getStringOrNull(it) }?.let {
|
||||
put(FileMetaType.Owner, it)
|
||||
}
|
||||
}.toPersistentMap(),
|
||||
label = cursor.getString(nameIndex),
|
||||
uri = Uri.parse(cursor.getString(contentUriIndex)),
|
||||
thumbnailUri = thumbnailUriIndex?.let {
|
||||
cursor.getStringOrNull(it)
|
||||
}?.let { Uri.parse(it) },
|
||||
storageStrategy = config.storageStrategy,
|
||||
isDirectory = directoryIndex?.let { cursor.getInt(it) } == 1,
|
||||
authority = pluginAuthority,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
cursor.close()
|
||||
return results
|
||||
}
|
||||
|
||||
override fun PluginFile.toBundle(): Bundle {
|
||||
return Bundle().apply {
|
||||
set(FileColumns.Id, id)
|
||||
set(FileColumns.Path, path)
|
||||
set(FileColumns.MimeType, mimeType)
|
||||
set(FileColumns.Size, size)
|
||||
set(FileColumns.MetaTitle, metaData[FileMetaType.Title])
|
||||
set(FileColumns.MetaArtist, metaData[FileMetaType.Artist])
|
||||
set(FileColumns.MetaAlbum, metaData[FileMetaType.Album])
|
||||
set(FileColumns.MetaDuration, metaData[FileMetaType.Duration]?.toLong())
|
||||
set(FileColumns.MetaYear, metaData[FileMetaType.Year]?.toInt())
|
||||
set(
|
||||
FileColumns.MetaWidth,
|
||||
metaData[FileMetaType.Dimensions]?.split("x")?.getOrNull(0)?.toInt()
|
||||
)
|
||||
set(
|
||||
FileColumns.MetaHeight,
|
||||
metaData[FileMetaType.Dimensions]?.split("x")?.getOrNull(1)?.toInt()
|
||||
)
|
||||
set(FileColumns.MetaLocation, metaData[FileMetaType.Location])
|
||||
set(FileColumns.MetaAppName, metaData[FileMetaType.AppName])
|
||||
set(FileColumns.MetaAppPackageName, metaData[FileMetaType.AppPackageName])
|
||||
set(FileColumns.Owner, metaData[FileMetaType.Owner])
|
||||
set(FileColumns.DisplayName, label)
|
||||
set(FileColumns.ThumbnailUri, thumbnailUri?.toString())
|
||||
set(FileColumns.IsDirectory, isDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Uri.Builder.appendQueryParameters(query: String): Uri.Builder = apply {
|
||||
appendQueryParameter(SearchPluginContract.Params.Query, query)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user