Refactor files
This commit is contained in:
parent
e46d5a7d7e
commit
183ba586b3
@ -44,8 +44,8 @@ val favoritesModule = module {
|
|||||||
if (searchable is NextcloudFile) {
|
if (searchable is NextcloudFile) {
|
||||||
return@factory NextcloudFileSerializer()
|
return@factory NextcloudFileSerializer()
|
||||||
}
|
}
|
||||||
if (searchable is File) {
|
if (searchable is LocalFile) {
|
||||||
return@factory FileSerializer()
|
return@factory LocalFileSerializer()
|
||||||
}
|
}
|
||||||
if (searchable is Website) {
|
if (searchable is Website) {
|
||||||
return@factory WebsiteSerializer()
|
return@factory WebsiteSerializer()
|
||||||
@ -83,7 +83,7 @@ val favoritesModule = module {
|
|||||||
return@factory OwncloudFileDeserializer()
|
return@factory OwncloudFileDeserializer()
|
||||||
}
|
}
|
||||||
if (type == "file") {
|
if (type == "file") {
|
||||||
return@factory FileDeserializer(androidContext())
|
return@factory LocalFileDeserializer(androidContext())
|
||||||
}
|
}
|
||||||
if (type == "website") {
|
if (type == "website") {
|
||||||
return@factory WebsiteDeserializer()
|
return@factory WebsiteDeserializer()
|
||||||
|
|||||||
@ -10,9 +10,9 @@ import de.mm20.launcher2.search.SearchableSerializer
|
|||||||
import de.mm20.launcher2.search.data.*
|
import de.mm20.launcher2.search.data.*
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
class FileSerializer : SearchableSerializer {
|
class LocalFileSerializer : SearchableSerializer {
|
||||||
override fun serialize(searchable: Searchable): String {
|
override fun serialize(searchable: Searchable): String {
|
||||||
searchable as File
|
searchable as LocalFile
|
||||||
return jsonObjectOf(
|
return jsonObjectOf(
|
||||||
"id" to searchable.id
|
"id" to searchable.id
|
||||||
).toString()
|
).toString()
|
||||||
@ -22,7 +22,7 @@ class FileSerializer : SearchableSerializer {
|
|||||||
get() = "file"
|
get() = "file"
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileDeserializer(
|
class LocalFileDeserializer(
|
||||||
val context: Context
|
val context: Context
|
||||||
) : SearchableDeserializer {
|
) : SearchableDeserializer {
|
||||||
override fun deserialize(serialized: String): Searchable? {
|
override fun deserialize(serialized: String): Searchable? {
|
||||||
@ -48,20 +48,20 @@ class FileDeserializer(
|
|||||||
val directory = java.io.File(path).isDirectory
|
val directory = java.io.File(path).isDirectory
|
||||||
val id = cursor.getLong(0)
|
val id = cursor.getLong(0)
|
||||||
val mimeType = cursor.getStringOrNull(3)
|
val mimeType = cursor.getStringOrNull(3)
|
||||||
?: if (directory) "inode/directory" else File.getMimetypeByFileExtension(
|
?: if (directory) "inode/directory" else LocalFile.getMimetypeByFileExtension(
|
||||||
path.substringAfterLast(
|
path.substringAfterLast(
|
||||||
'.'
|
'.'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val size = cursor.getLong(1)
|
val size = cursor.getLong(1)
|
||||||
cursor.close()
|
cursor.close()
|
||||||
return File(
|
return LocalFile(
|
||||||
path = path,
|
path = path,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
size = size,
|
size = size,
|
||||||
isDirectory = directory,
|
isDirectory = directory,
|
||||||
id = id,
|
id = id,
|
||||||
metaData = File.getMetaData(context, mimeType, path)
|
metaData = LocalFile.getMetaData(context, mimeType, path)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|||||||
@ -15,6 +15,8 @@ class FilesRepository(
|
|||||||
hiddenItemsRepository: HiddenItemsRepository
|
hiddenItemsRepository: HiddenItemsRepository
|
||||||
) : BaseSearchableRepository() {
|
) : BaseSearchableRepository() {
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
val files = MediatorLiveData<List<File>?>()
|
val files = MediatorLiveData<List<File>?>()
|
||||||
|
|
||||||
private val allFiles = MutableLiveData<List<File>?>(emptyList())
|
private val allFiles = MutableLiveData<List<File>?>(emptyList())
|
||||||
@ -42,7 +44,7 @@ class FilesRepository(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
val localFiles = withContext(Dispatchers.IO) {
|
val localFiles = withContext(Dispatchers.IO) {
|
||||||
File.search(context, query).sorted().toMutableList()
|
LocalFile.search(context, query).sorted().toMutableList()
|
||||||
}
|
}
|
||||||
allFiles.value = localFiles
|
allFiles.value = localFiles
|
||||||
|
|
||||||
@ -59,7 +61,12 @@ class FilesRepository(
|
|||||||
allFiles.value = localFiles + cloudFiles
|
allFiles.value = localFiles + cloudFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeFile(file: File) {
|
fun deleteFile(file: File) {
|
||||||
|
if (file.isDeletable) {
|
||||||
|
scope.launch {
|
||||||
|
file.delete(context)
|
||||||
allFiles.value = allFiles.value?.filter { it != file }
|
allFiles.value = allFiles.value?.filter { it != file }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -10,7 +10,7 @@ class FilesViewModel(
|
|||||||
|
|
||||||
val files = filesRepository.files
|
val files = filesRepository.files
|
||||||
|
|
||||||
fun removeFile(file: File) {
|
fun deleteFile(file: File) {
|
||||||
filesRepository.removeFile(file)
|
filesRepository.deleteFile(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,35 +1,13 @@
|
|||||||
package de.mm20.launcher2.search.data
|
package de.mm20.launcher2.search.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.graphics.drawable.AdaptiveIconDrawable
|
|
||||||
import android.graphics.drawable.BitmapDrawable
|
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.location.Geocoder
|
|
||||||
import android.media.MediaMetadataRetriever
|
|
||||||
import android.media.ThumbnailUtils
|
|
||||||
import android.os.Build
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import android.util.Size
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import androidx.core.database.getStringOrNull
|
|
||||||
import androidx.exifinterface.media.ExifInterface
|
|
||||||
import de.mm20.launcher2.files.R
|
import de.mm20.launcher2.files.R
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
import de.mm20.launcher2.ktx.formatToString
|
|
||||||
import de.mm20.launcher2.media.ThumbnailUtilsCompat
|
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
|
||||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.io.File as JavaIOFile
|
|
||||||
|
|
||||||
open class File(
|
abstract class File(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val path: String,
|
val path: String,
|
||||||
val mimeType: String,
|
val mimeType: String,
|
||||||
@ -37,95 +15,7 @@ open class File(
|
|||||||
val isDirectory: Boolean,
|
val isDirectory: Boolean,
|
||||||
val metaData: List<Pair<Int, String>>
|
val metaData: List<Pair<Int, String>>
|
||||||
) : Searchable() {
|
) : Searchable() {
|
||||||
|
abstract val isStoredInCloud: Boolean
|
||||||
override val label = path.substringAfterLast('/')
|
|
||||||
|
|
||||||
override val key = "file://$path"
|
|
||||||
|
|
||||||
open val isStoredInCloud = false
|
|
||||||
|
|
||||||
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? {
|
|
||||||
if (!JavaIOFile(path).exists()) return null
|
|
||||||
when {
|
|
||||||
mimeType.startsWith("image/") -> {
|
|
||||||
val thumbnail = withContext(Dispatchers.IO) {
|
|
||||||
ThumbnailUtils.extractThumbnail(
|
|
||||||
BitmapFactory.decodeFile(path),
|
|
||||||
size, size
|
|
||||||
)
|
|
||||||
} ?: return null
|
|
||||||
|
|
||||||
return LauncherIcon(
|
|
||||||
foreground = BitmapDrawable(context.resources, thumbnail),
|
|
||||||
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
|
|
||||||
)
|
|
||||||
}
|
|
||||||
mimeType.startsWith("video/") -> {
|
|
||||||
val thumbnail = withContext(Dispatchers.IO) {
|
|
||||||
ThumbnailUtilsCompat.createVideoThumbnail(
|
|
||||||
JavaIOFile(path),
|
|
||||||
Size(size, size)
|
|
||||||
)
|
|
||||||
} ?: return null
|
|
||||||
return LauncherIcon(
|
|
||||||
foreground = BitmapDrawable(context.resources, thumbnail),
|
|
||||||
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
|
|
||||||
)
|
|
||||||
}
|
|
||||||
mimeType.startsWith("audio/") -> {
|
|
||||||
val thumbnail = withContext(Dispatchers.IO) {
|
|
||||||
val mediaMetadataRetriever = MediaMetadataRetriever()
|
|
||||||
try {
|
|
||||||
mediaMetadataRetriever.setDataSource(path)
|
|
||||||
val thumbData = mediaMetadataRetriever.embeddedPicture
|
|
||||||
if (thumbData != null) {
|
|
||||||
val thumbnail = ThumbnailUtils.extractThumbnail(
|
|
||||||
BitmapFactory.decodeByteArray(thumbData, 0, thumbData.size),
|
|
||||||
size,
|
|
||||||
size
|
|
||||||
)
|
|
||||||
mediaMetadataRetriever.release()
|
|
||||||
return@withContext thumbnail
|
|
||||||
}
|
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
}
|
|
||||||
mediaMetadataRetriever.release()
|
|
||||||
return@withContext null
|
|
||||||
|
|
||||||
}
|
|
||||||
thumbnail ?: return null
|
|
||||||
return LauncherIcon(
|
|
||||||
foreground = BitmapDrawable(context.resources, thumbnail),
|
|
||||||
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
mimeType == "application/vnd.android.package-archive" -> {
|
|
||||||
val pkgInfo = context.packageManager.getPackageArchiveInfo(path, 0)
|
|
||||||
val icon = withContext(Dispatchers.IO) {
|
|
||||||
pkgInfo?.applicationInfo?.loadIcon(context.packageManager)
|
|
||||||
} ?: return null
|
|
||||||
when {
|
|
||||||
Build.VERSION.SDK_INT > Build.VERSION_CODES.O && icon is AdaptiveIconDrawable -> {
|
|
||||||
return LauncherIcon(
|
|
||||||
foreground = icon.foreground,
|
|
||||||
background = icon.background,
|
|
||||||
foregroundScale = 1.5f,
|
|
||||||
backgroundScale = 1.5f
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return LauncherIcon(
|
|
||||||
foreground = icon,
|
|
||||||
foregroundScale = 0.7f,
|
|
||||||
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPlaceholderIcon(context: Context): LauncherIcon {
|
override fun getPlaceholderIcon(context: Context): LauncherIcon {
|
||||||
val (resId, bgColor) = when {
|
val (resId, bgColor) = when {
|
||||||
@ -162,16 +52,6 @@ open class File(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLaunchIntent(context: Context): Intent? {
|
|
||||||
val uri = FileProvider.getUriForFile(
|
|
||||||
context,
|
|
||||||
context.applicationContext.packageName + ".fileprovider", JavaIOFile(path)
|
|
||||||
)
|
|
||||||
return Intent(Intent.ACTION_VIEW)
|
|
||||||
.setDataAndType(uri, mimeType)
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getFileType(context: Context): String {
|
fun getFileType(context: Context): String {
|
||||||
if (isDirectory) return context.getString(R.string.file_type_directory)
|
if (isDirectory) return context.getString(R.string.file_type_directory)
|
||||||
val resource = when (mimeType) {
|
val resource = when (mimeType) {
|
||||||
@ -227,186 +107,7 @@ open class File(
|
|||||||
return context.getString(resource)
|
return context.getString(resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
open val isDeletable: Boolean = false
|
||||||
fun search(context: Context, query: String): List<File> {
|
open suspend fun delete(context: Context) {}
|
||||||
val results = mutableListOf<File>()
|
|
||||||
if (!LauncherPreferences.instance.searchFiles) return results
|
|
||||||
if (query.isBlank()) return results
|
|
||||||
if (!PermissionsManager.checkPermission(
|
|
||||||
context,
|
|
||||||
PermissionsManager.EXTERNAL_STORAGE
|
|
||||||
)
|
|
||||||
) return results
|
|
||||||
val uri = MediaStore.Files.getContentUri("external").buildUpon()
|
|
||||||
.appendQueryParameter("limit", "10").build()
|
|
||||||
val projection = arrayOf(
|
|
||||||
MediaStore.Files.FileColumns.DISPLAY_NAME,
|
|
||||||
MediaStore.Files.FileColumns._ID,
|
|
||||||
MediaStore.Files.FileColumns.SIZE,
|
|
||||||
MediaStore.Files.FileColumns.DATA,
|
|
||||||
MediaStore.Files.FileColumns.MIME_TYPE
|
|
||||||
)
|
|
||||||
val selection =
|
|
||||||
if (query.length > 3) "${MediaStore.Files.FileColumns.TITLE} LIKE ?" else "${MediaStore.Files.FileColumns.TITLE} = ?"
|
|
||||||
val selArgs = if (query.length > 3) arrayOf("%$query%") else arrayOf(query)
|
|
||||||
val sort = "${MediaStore.Files.FileColumns.DISPLAY_NAME} COLLATE NOCASE ASC"
|
|
||||||
|
|
||||||
|
|
||||||
val cursor = context.contentResolver.query(uri, projection, selection, selArgs, sort)
|
|
||||||
?: return results
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
if (results.size >= 10) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
val path = cursor.getString(3)
|
|
||||||
if (!JavaIOFile(path).exists()) continue
|
|
||||||
val directory = JavaIOFile(path).isDirectory
|
|
||||||
val mimeType = (cursor.getStringOrNull(4)
|
|
||||||
?: if (directory) "inode/directory" else getMimetypeByFileExtension(
|
|
||||||
path.substringAfterLast(
|
|
||||||
'.'
|
|
||||||
)
|
|
||||||
))
|
|
||||||
val file = File(
|
|
||||||
path = path,
|
|
||||||
mimeType = mimeType,
|
|
||||||
size = cursor.getLong(2),
|
|
||||||
isDirectory = directory,
|
|
||||||
id = cursor.getLong(1),
|
|
||||||
metaData = getMetaData(context, mimeType, path)
|
|
||||||
)
|
|
||||||
results.add(file)
|
|
||||||
}
|
|
||||||
cursor.close()
|
|
||||||
return results.sortedBy { it }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun getMimetypeByFileExtension(extension: String): String {
|
|
||||||
return when (extension) {
|
|
||||||
"apk" -> "application/vnd.android.package-archive"
|
|
||||||
"zip" -> "application/zip"
|
|
||||||
"jar" -> "application/java-archive"
|
|
||||||
"txt" -> "text/plain"
|
|
||||||
"js" -> "text/javascript"
|
|
||||||
"html", "htm" -> "text/html"
|
|
||||||
"css" -> "text/css"
|
|
||||||
"gif" -> "image/gif"
|
|
||||||
"png" -> "image/png"
|
|
||||||
"jpg", "jpeg" -> "image/jpeg"
|
|
||||||
"bmp" -> "image/bmp"
|
|
||||||
"webp" -> "image/webp"
|
|
||||||
"ico" -> "image/x-icon"
|
|
||||||
"midi" -> "audio/midi"
|
|
||||||
"mp3" -> "audio/mpeg3"
|
|
||||||
"webm" -> "audio/webm"
|
|
||||||
"ogg" -> "audio/ogg"
|
|
||||||
"wav" -> "audio/wav"
|
|
||||||
"mp4" -> "video/mp4"
|
|
||||||
else -> "application/octet-stream"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun getMetaData(
|
|
||||||
context: Context,
|
|
||||||
mimeType: String,
|
|
||||||
path: String
|
|
||||||
): List<Pair<Int, String>> {
|
|
||||||
val metaData = mutableListOf<Pair<Int, String>>()
|
|
||||||
when {
|
|
||||||
mimeType.startsWith("audio/") -> {
|
|
||||||
val retriever = MediaMetadataRetriever()
|
|
||||||
try {
|
|
||||||
retriever.setDataSource(path)
|
|
||||||
arrayOf(
|
|
||||||
R.string.file_meta_title to MediaMetadataRetriever.METADATA_KEY_TITLE,
|
|
||||||
R.string.file_meta_artist to MediaMetadataRetriever.METADATA_KEY_ARTIST,
|
|
||||||
R.string.file_meta_album to MediaMetadataRetriever.METADATA_KEY_ALBUM,
|
|
||||||
R.string.file_meta_year to MediaMetadataRetriever.METADATA_KEY_YEAR
|
|
||||||
).forEach {
|
|
||||||
retriever.extractMetadata(it.second)
|
|
||||||
?.let { m -> metaData.add(it.first to m) }
|
|
||||||
}
|
|
||||||
val duration =
|
|
||||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
|
||||||
?.toLong() ?: 0
|
|
||||||
val d = DateUtils.formatElapsedTime((duration) / 1000)
|
|
||||||
metaData.add(3, R.string.file_meta_duration to d)
|
|
||||||
retriever.release()
|
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
retriever.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mimeType.startsWith("video/") -> {
|
|
||||||
val retriever = MediaMetadataRetriever()
|
|
||||||
try {
|
|
||||||
retriever.setDataSource(path)
|
|
||||||
val width =
|
|
||||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
|
|
||||||
?.toLong() ?: 0
|
|
||||||
val height =
|
|
||||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
|
|
||||||
?.toLong() ?: 0
|
|
||||||
metaData.add(R.string.file_meta_dimensions to "${width}x$height")
|
|
||||||
val duration =
|
|
||||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
|
||||||
?.toLong() ?: 0
|
|
||||||
val d = DateUtils.formatElapsedTime(duration / 1000)
|
|
||||||
metaData.add(R.string.file_meta_duration to d)
|
|
||||||
val loc =
|
|
||||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
|
|
||||||
if (Geocoder.isPresent() && loc != null) {
|
|
||||||
val lon =
|
|
||||||
loc.substring(0, loc.lastIndexOfAny(charArrayOf('+', '-')))
|
|
||||||
.toDouble()
|
|
||||||
val lat = loc.substring(
|
|
||||||
loc.lastIndexOfAny(charArrayOf('+', '-')),
|
|
||||||
loc.indexOf('/')
|
|
||||||
).toDouble()
|
|
||||||
val list = Geocoder(context).getFromLocation(lon, lat, 1)
|
|
||||||
if (list.size > 0) {
|
|
||||||
metaData.add(R.string.file_meta_location to list[0].formatToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
retriever.release()
|
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
retriever.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mimeType.startsWith("image/") -> {
|
|
||||||
val options = BitmapFactory.Options()
|
|
||||||
options.inJustDecodeBounds = true
|
|
||||||
BitmapFactory.decodeFile(path, options)
|
|
||||||
val width = options.outWidth
|
|
||||||
val height = options.outHeight
|
|
||||||
metaData.add(R.string.file_meta_dimensions to "${width}x$height")
|
|
||||||
try {
|
|
||||||
val exif = ExifInterface(path)
|
|
||||||
val loc = exif.latLong
|
|
||||||
if (loc != null && Geocoder.isPresent()) {
|
|
||||||
val list = Geocoder(context).getFromLocation(loc[0], loc[1], 1)
|
|
||||||
if (list.size > 0) {
|
|
||||||
metaData.add(R.string.file_meta_location to list[0].formatToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_: IOException) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mimeType == "application/vnd.android.package-archive" -> {
|
|
||||||
val pkgInfo = context.packageManager.getPackageArchiveInfo(path, 0)
|
|
||||||
?: return metaData
|
|
||||||
metaData.add(
|
|
||||||
R.string.file_meta_app_name to pkgInfo.applicationInfo.loadLabel(
|
|
||||||
context.packageManager
|
|
||||||
).toString()
|
|
||||||
)
|
|
||||||
metaData.add(R.string.file_meta_app_pkgname to pkgInfo.packageName)
|
|
||||||
metaData.add(R.string.file_meta_app_version to pkgInfo.versionName)
|
|
||||||
metaData.add(R.string.file_meta_app_min_sdk to pkgInfo.applicationInfo.minSdkVersion.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return metaData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
346
files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt
Normal file
346
files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
package de.mm20.launcher2.search.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.drawable.AdaptiveIconDrawable
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.location.Geocoder
|
||||||
|
import android.media.MediaMetadataRetriever
|
||||||
|
import android.media.ThumbnailUtils
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.util.Size
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.core.database.getStringOrNull
|
||||||
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
import de.mm20.launcher2.files.R
|
||||||
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
|
import de.mm20.launcher2.ktx.formatToString
|
||||||
|
import de.mm20.launcher2.media.ThumbnailUtilsCompat
|
||||||
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
|
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import java.io.File as JavaIOFile
|
||||||
|
|
||||||
|
open class LocalFile(
|
||||||
|
id: Long,
|
||||||
|
path: String,
|
||||||
|
mimeType: String,
|
||||||
|
size: Long,
|
||||||
|
isDirectory: Boolean,
|
||||||
|
metaData: List<Pair<Int, String>>
|
||||||
|
) : File(id, path, mimeType, size, isDirectory, metaData) {
|
||||||
|
|
||||||
|
override val label = path.substringAfterLast('/')
|
||||||
|
|
||||||
|
override val key = "file://$path"
|
||||||
|
|
||||||
|
override val isStoredInCloud = false
|
||||||
|
|
||||||
|
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? {
|
||||||
|
if (!JavaIOFile(path).exists()) return null
|
||||||
|
when {
|
||||||
|
mimeType.startsWith("image/") -> {
|
||||||
|
val thumbnail = withContext(Dispatchers.IO) {
|
||||||
|
ThumbnailUtils.extractThumbnail(
|
||||||
|
BitmapFactory.decodeFile(path),
|
||||||
|
size, size
|
||||||
|
)
|
||||||
|
} ?: return null
|
||||||
|
|
||||||
|
return LauncherIcon(
|
||||||
|
foreground = BitmapDrawable(context.resources, thumbnail),
|
||||||
|
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mimeType.startsWith("video/") -> {
|
||||||
|
val thumbnail = withContext(Dispatchers.IO) {
|
||||||
|
ThumbnailUtilsCompat.createVideoThumbnail(
|
||||||
|
JavaIOFile(path),
|
||||||
|
Size(size, size)
|
||||||
|
)
|
||||||
|
} ?: return null
|
||||||
|
return LauncherIcon(
|
||||||
|
foreground = BitmapDrawable(context.resources, thumbnail),
|
||||||
|
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mimeType.startsWith("audio/") -> {
|
||||||
|
val thumbnail = withContext(Dispatchers.IO) {
|
||||||
|
val mediaMetadataRetriever = MediaMetadataRetriever()
|
||||||
|
try {
|
||||||
|
mediaMetadataRetriever.setDataSource(path)
|
||||||
|
val thumbData = mediaMetadataRetriever.embeddedPicture
|
||||||
|
if (thumbData != null) {
|
||||||
|
val thumbnail = ThumbnailUtils.extractThumbnail(
|
||||||
|
BitmapFactory.decodeByteArray(thumbData, 0, thumbData.size),
|
||||||
|
size,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
mediaMetadataRetriever.release()
|
||||||
|
return@withContext thumbnail
|
||||||
|
}
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
}
|
||||||
|
mediaMetadataRetriever.release()
|
||||||
|
return@withContext null
|
||||||
|
|
||||||
|
}
|
||||||
|
thumbnail ?: return null
|
||||||
|
return LauncherIcon(
|
||||||
|
foreground = BitmapDrawable(context.resources, thumbnail),
|
||||||
|
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
mimeType == "application/vnd.android.package-archive" -> {
|
||||||
|
val pkgInfo = context.packageManager.getPackageArchiveInfo(path, 0)
|
||||||
|
val icon = withContext(Dispatchers.IO) {
|
||||||
|
pkgInfo?.applicationInfo?.loadIcon(context.packageManager)
|
||||||
|
} ?: return null
|
||||||
|
when {
|
||||||
|
Build.VERSION.SDK_INT > Build.VERSION_CODES.O && icon is AdaptiveIconDrawable -> {
|
||||||
|
return LauncherIcon(
|
||||||
|
foreground = icon.foreground,
|
||||||
|
background = icon.background,
|
||||||
|
foregroundScale = 1.5f,
|
||||||
|
backgroundScale = 1.5f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
return LauncherIcon(
|
||||||
|
foreground = icon,
|
||||||
|
foregroundScale = 0.7f,
|
||||||
|
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getLaunchIntent(context: Context): Intent? {
|
||||||
|
val uri = FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
context.applicationContext.packageName + ".fileprovider", JavaIOFile(path)
|
||||||
|
)
|
||||||
|
return Intent(Intent.ACTION_VIEW)
|
||||||
|
.setDataAndType(uri, mimeType)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isDeletable: Boolean
|
||||||
|
get() {
|
||||||
|
val file = java.io.File(path)
|
||||||
|
return file.canWrite() && file.parentFile?.canWrite() == true
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(context: Context) {
|
||||||
|
super.delete(context)
|
||||||
|
|
||||||
|
val file = java.io.File(path)
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
file.deleteRecursively()
|
||||||
|
|
||||||
|
context.contentResolver.delete(
|
||||||
|
MediaStore.Files.getContentUri("external"),
|
||||||
|
"${MediaStore.Files.FileColumns._ID} = ?",
|
||||||
|
arrayOf(id.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun search(context: Context, query: String): List<LocalFile> {
|
||||||
|
val results = mutableListOf<LocalFile>()
|
||||||
|
if (!LauncherPreferences.instance.searchFiles) return results
|
||||||
|
if (query.isBlank()) return results
|
||||||
|
if (!PermissionsManager.checkPermission(
|
||||||
|
context,
|
||||||
|
PermissionsManager.EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
) return results
|
||||||
|
val uri = MediaStore.Files.getContentUri("external").buildUpon()
|
||||||
|
.appendQueryParameter("limit", "10").build()
|
||||||
|
val projection = arrayOf(
|
||||||
|
MediaStore.Files.FileColumns.DISPLAY_NAME,
|
||||||
|
MediaStore.Files.FileColumns._ID,
|
||||||
|
MediaStore.Files.FileColumns.SIZE,
|
||||||
|
MediaStore.Files.FileColumns.DATA,
|
||||||
|
MediaStore.Files.FileColumns.MIME_TYPE
|
||||||
|
)
|
||||||
|
val selection =
|
||||||
|
if (query.length > 3) "${MediaStore.Files.FileColumns.TITLE} LIKE ?" else "${MediaStore.Files.FileColumns.TITLE} = ?"
|
||||||
|
val selArgs = if (query.length > 3) arrayOf("%$query%") else arrayOf(query)
|
||||||
|
val sort = "${MediaStore.Files.FileColumns.DISPLAY_NAME} COLLATE NOCASE ASC"
|
||||||
|
|
||||||
|
|
||||||
|
val cursor = context.contentResolver.query(uri, projection, selection, selArgs, sort)
|
||||||
|
?: return results
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
if (results.size >= 10) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val path = cursor.getString(3)
|
||||||
|
if (!JavaIOFile(path).exists()) continue
|
||||||
|
val directory = JavaIOFile(path).isDirectory
|
||||||
|
val mimeType = (cursor.getStringOrNull(4)
|
||||||
|
?: if (directory) "inode/directory" else getMimetypeByFileExtension(
|
||||||
|
path.substringAfterLast(
|
||||||
|
'.'
|
||||||
|
)
|
||||||
|
))
|
||||||
|
val file = LocalFile(
|
||||||
|
path = path,
|
||||||
|
mimeType = mimeType,
|
||||||
|
size = cursor.getLong(2),
|
||||||
|
isDirectory = directory,
|
||||||
|
id = cursor.getLong(1),
|
||||||
|
metaData = getMetaData(context, mimeType, path)
|
||||||
|
)
|
||||||
|
results.add(file)
|
||||||
|
}
|
||||||
|
cursor.close()
|
||||||
|
return results.sortedBy { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getMimetypeByFileExtension(extension: String): String {
|
||||||
|
return when (extension) {
|
||||||
|
"apk" -> "application/vnd.android.package-archive"
|
||||||
|
"zip" -> "application/zip"
|
||||||
|
"jar" -> "application/java-archive"
|
||||||
|
"txt" -> "text/plain"
|
||||||
|
"js" -> "text/javascript"
|
||||||
|
"html", "htm" -> "text/html"
|
||||||
|
"css" -> "text/css"
|
||||||
|
"gif" -> "image/gif"
|
||||||
|
"png" -> "image/png"
|
||||||
|
"jpg", "jpeg" -> "image/jpeg"
|
||||||
|
"bmp" -> "image/bmp"
|
||||||
|
"webp" -> "image/webp"
|
||||||
|
"ico" -> "image/x-icon"
|
||||||
|
"midi" -> "audio/midi"
|
||||||
|
"mp3" -> "audio/mpeg3"
|
||||||
|
"webm" -> "audio/webm"
|
||||||
|
"ogg" -> "audio/ogg"
|
||||||
|
"wav" -> "audio/wav"
|
||||||
|
"mp4" -> "video/mp4"
|
||||||
|
else -> "application/octet-stream"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun getMetaData(
|
||||||
|
context: Context,
|
||||||
|
mimeType: String,
|
||||||
|
path: String
|
||||||
|
): List<Pair<Int, String>> {
|
||||||
|
val metaData = mutableListOf<Pair<Int, String>>()
|
||||||
|
when {
|
||||||
|
mimeType.startsWith("audio/") -> {
|
||||||
|
val retriever = MediaMetadataRetriever()
|
||||||
|
try {
|
||||||
|
retriever.setDataSource(path)
|
||||||
|
arrayOf(
|
||||||
|
R.string.file_meta_title to MediaMetadataRetriever.METADATA_KEY_TITLE,
|
||||||
|
R.string.file_meta_artist to MediaMetadataRetriever.METADATA_KEY_ARTIST,
|
||||||
|
R.string.file_meta_album to MediaMetadataRetriever.METADATA_KEY_ALBUM,
|
||||||
|
R.string.file_meta_year to MediaMetadataRetriever.METADATA_KEY_YEAR
|
||||||
|
).forEach {
|
||||||
|
retriever.extractMetadata(it.second)
|
||||||
|
?.let { m -> metaData.add(it.first to m) }
|
||||||
|
}
|
||||||
|
val duration =
|
||||||
|
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
||||||
|
?.toLong() ?: 0
|
||||||
|
val d = DateUtils.formatElapsedTime((duration) / 1000)
|
||||||
|
metaData.add(3, R.string.file_meta_duration to d)
|
||||||
|
retriever.release()
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
retriever.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mimeType.startsWith("video/") -> {
|
||||||
|
val retriever = MediaMetadataRetriever()
|
||||||
|
try {
|
||||||
|
retriever.setDataSource(path)
|
||||||
|
val width =
|
||||||
|
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
|
||||||
|
?.toLong() ?: 0
|
||||||
|
val height =
|
||||||
|
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
|
||||||
|
?.toLong() ?: 0
|
||||||
|
metaData.add(R.string.file_meta_dimensions to "${width}x$height")
|
||||||
|
val duration =
|
||||||
|
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
||||||
|
?.toLong() ?: 0
|
||||||
|
val d = DateUtils.formatElapsedTime(duration / 1000)
|
||||||
|
metaData.add(R.string.file_meta_duration to d)
|
||||||
|
val loc =
|
||||||
|
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
|
||||||
|
if (Geocoder.isPresent() && loc != null) {
|
||||||
|
val lon =
|
||||||
|
loc.substring(0, loc.lastIndexOfAny(charArrayOf('+', '-')))
|
||||||
|
.toDouble()
|
||||||
|
val lat = loc.substring(
|
||||||
|
loc.lastIndexOfAny(charArrayOf('+', '-')),
|
||||||
|
loc.indexOf('/')
|
||||||
|
).toDouble()
|
||||||
|
val list = Geocoder(context).getFromLocation(lon, lat, 1)
|
||||||
|
if (list.size > 0) {
|
||||||
|
metaData.add(R.string.file_meta_location to list[0].formatToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retriever.release()
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
retriever.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mimeType.startsWith("image/") -> {
|
||||||
|
val options = BitmapFactory.Options()
|
||||||
|
options.inJustDecodeBounds = true
|
||||||
|
BitmapFactory.decodeFile(path, options)
|
||||||
|
val width = options.outWidth
|
||||||
|
val height = options.outHeight
|
||||||
|
metaData.add(R.string.file_meta_dimensions to "${width}x$height")
|
||||||
|
try {
|
||||||
|
val exif = ExifInterface(path)
|
||||||
|
val loc = exif.latLong
|
||||||
|
if (loc != null && Geocoder.isPresent()) {
|
||||||
|
val list = Geocoder(context).getFromLocation(loc[0], loc[1], 1)
|
||||||
|
if (list.size > 0) {
|
||||||
|
metaData.add(R.string.file_meta_location to list[0].formatToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: IOException) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mimeType == "application/vnd.android.package-archive" -> {
|
||||||
|
val pkgInfo = context.packageManager.getPackageArchiveInfo(path, 0)
|
||||||
|
?: return metaData
|
||||||
|
metaData.add(
|
||||||
|
R.string.file_meta_app_name to pkgInfo.applicationInfo.loadLabel(
|
||||||
|
context.packageManager
|
||||||
|
).toString()
|
||||||
|
)
|
||||||
|
metaData.add(R.string.file_meta_app_pkgname to pkgInfo.packageName)
|
||||||
|
metaData.add(R.string.file_meta_app_version to pkgInfo.versionName)
|
||||||
|
metaData.add(R.string.file_meta_app_min_sdk to pkgInfo.applicationInfo.minSdkVersion.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return metaData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,10 +5,8 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import de.mm20.launcher2.files.R
|
import de.mm20.launcher2.files.R
|
||||||
import de.mm20.launcher2.helper.NetworkUtils
|
import de.mm20.launcher2.helper.NetworkUtils
|
||||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
|
||||||
import de.mm20.launcher2.nextcloud.NextcloudApiHelper
|
import de.mm20.launcher2.nextcloud.NextcloudApiHelper
|
||||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||||
import org.json.JSONObject
|
|
||||||
|
|
||||||
class NextcloudFile(
|
class NextcloudFile(
|
||||||
fileId: Long,
|
fileId: Long,
|
||||||
|
|||||||
@ -5,10 +5,8 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import de.mm20.launcher2.files.R
|
import de.mm20.launcher2.files.R
|
||||||
import de.mm20.launcher2.helper.NetworkUtils
|
import de.mm20.launcher2.helper.NetworkUtils
|
||||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
|
||||||
import de.mm20.launcher2.owncloud.OwncloudClient
|
import de.mm20.launcher2.owncloud.OwncloudClient
|
||||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||||
import org.json.JSONObject
|
|
||||||
|
|
||||||
class OwncloudFile(
|
class OwncloudFile(
|
||||||
fileId: Long,
|
fileId: Long,
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package de.mm20.launcher2.ui.icons
|
package de.mm20.launcher2.ui.icons
|
||||||
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.*
|
import androidx.compose.material.icons.rounded.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import android.view.ViewGroup
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import de.mm20.launcher2.files.FilesViewModel
|
import de.mm20.launcher2.files.FilesViewModel
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.search.data.File
|
import de.mm20.launcher2.search.data.File
|
||||||
|
|||||||
@ -72,11 +72,10 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
|||||||
val favAction = FavoriteToolbarAction(context, file)
|
val favAction = FavoriteToolbarAction(context, file)
|
||||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||||
|
|
||||||
val jFile = java.io.File(file.path)
|
if (file.isDeletable) {
|
||||||
if (jFile.canWrite() && jFile.parentFile.canWrite()) {
|
|
||||||
val deleteAction = ToolbarAction(R.drawable.ic_delete, context.getString(R.string.menu_delete))
|
val deleteAction = ToolbarAction(R.drawable.ic_delete, context.getString(R.string.menu_delete))
|
||||||
deleteAction.clickAction = {
|
deleteAction.clickAction = {
|
||||||
delete(context, file, jFile)
|
delete(context, file)
|
||||||
}
|
}
|
||||||
toolbar.addAction(deleteAction, ToolbarView.PLACEMENT_END)
|
toolbar.addAction(deleteAction, ToolbarView.PLACEMENT_END)
|
||||||
}
|
}
|
||||||
@ -93,21 +92,16 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delete(context: Context, file: File, jFile: java.io.File) {
|
private fun delete(context: Context, file: File) {
|
||||||
MaterialDialog(context).show {
|
MaterialDialog(context).show {
|
||||||
message(text = context.getString(
|
message(text = context.getString(
|
||||||
if (file.isDirectory) R.string.alert_delete_directory
|
if (file.isDirectory) R.string.alert_delete_directory
|
||||||
else R.string.alert_delete_file,
|
else R.string.alert_delete_file,
|
||||||
file.path))
|
file.path))
|
||||||
positiveButton(android.R.string.yes) {
|
positiveButton(android.R.string.yes) {
|
||||||
Thread { jFile.deleteRecursively() }.start()
|
|
||||||
context.contentResolver.delete(
|
|
||||||
MediaStore.Files.getContentUri("external"),
|
|
||||||
"${MediaStore.Files.FileColumns._ID} = ?",
|
|
||||||
arrayOf(file.id.toString()))
|
|
||||||
it.dismiss()
|
|
||||||
val fileViewModel: FilesViewModel by (context as AppCompatActivity).viewModel()
|
val fileViewModel: FilesViewModel by (context as AppCompatActivity).viewModel()
|
||||||
fileViewModel.removeFile(file)
|
it.dismiss()
|
||||||
|
fileViewModel.deleteFile(file)
|
||||||
}
|
}
|
||||||
negativeButton(android.R.string.no) {
|
negativeButton(android.R.string.no) {
|
||||||
it.dismiss()
|
it.dismiss()
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class FileListRepresentation : Representation, KoinComponent {
|
|||||||
scene.setEnterAction {
|
scene.setEnterAction {
|
||||||
with(rootView) {
|
with(rootView) {
|
||||||
findViewById<TextView>(R.id.fileLabel).text = file.label
|
findViewById<TextView>(R.id.fileLabel).text = file.label
|
||||||
findViewById<TextView>(R.id.fileInfo).text = getFileType(context, file)
|
findViewById<TextView>(R.id.fileInfo).text = file.getFileType(context)
|
||||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||||
badge = badgeProvider.getLiveBadge(file.badgeKey)
|
badge = badgeProvider.getLiveBadge(file.badgeKey)
|
||||||
shape = LauncherIconView.getDefaultShape(context)
|
shape = LauncherIconView.getDefaultShape(context)
|
||||||
@ -60,40 +60,4 @@ class FileListRepresentation : Representation, KoinComponent {
|
|||||||
}
|
}
|
||||||
return scene
|
return scene
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFileType(context: Context, file: File): String {
|
|
||||||
if (file.isDirectory) return context.getString(R.string.file_type_directory)
|
|
||||||
val mimeType = file.mimeType
|
|
||||||
val resource = when (mimeType) {
|
|
||||||
"application/zip", "application/x-gtar", "application/x-tar",
|
|
||||||
"application/java-archive", "application/x-7z-compressed" -> R.string.file_type_archive
|
|
||||||
"application/x-gzip", "application/x-bzip2" -> R.string.file_type_compressed
|
|
||||||
"application/vnd.android.package-archive" -> R.string.file_type_android
|
|
||||||
"text/x-asm", "text/x-c", "text/x-java-source", "text/x-script.phyton", "text/x-pascal",
|
|
||||||
"text/x-script.perl", "text/javascript", "application/json" ->
|
|
||||||
R.string.file_type_source_code
|
|
||||||
"application/vnd.oasis.opendocument.text",
|
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
||||||
"application/msword", "application/vnd.google-apps.document" -> R.string.file_type_document
|
|
||||||
"application/vnd.oasis.opendocument.spreadsheet",
|
|
||||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
"application/vnd.ms-excel", "application/vnd.google-apps.spreadsheet" -> R.string.file_type_spreadsheet
|
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
||||||
"application/vnd.ms-powerpoint", "application/vnd.google-apps.presentation" -> R.string.file_type_presentation
|
|
||||||
"text/plain" -> R.string.file_type_text
|
|
||||||
"application/vnd.google-apps.drawing" -> R.string.file_type_drawing
|
|
||||||
"application/vnd.google-apps.form" -> R.string.file_type_form
|
|
||||||
else -> when {
|
|
||||||
mimeType.startsWith("image/") -> R.string.file_type_image
|
|
||||||
mimeType.startsWith("video/") -> R.string.file_type_video
|
|
||||||
mimeType.startsWith("audio/") -> R.string.file_type_music
|
|
||||||
else -> R.string.file_type_none
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (resource == R.string.file_type_none && file.label.matches(Regex(".+\\..+"))) {
|
|
||||||
val extension = file.label.substringAfterLast(".").uppercase()
|
|
||||||
return context.getString(R.string.file_type_generic, extension)
|
|
||||||
}
|
|
||||||
return context.getString(resource)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user