1091 lines
41 KiB
Kotlin
1091 lines
41 KiB
Kotlin
package com.simplemobiletools.gallery.pro.extensions
|
|
|
|
import android.appwidget.AppWidgetManager
|
|
import android.content.ComponentName
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.database.Cursor
|
|
import android.graphics.Bitmap
|
|
import android.graphics.drawable.Drawable
|
|
import android.graphics.drawable.PictureDrawable
|
|
import android.media.AudioManager
|
|
import android.os.Process
|
|
import android.provider.MediaStore.Files
|
|
import android.provider.MediaStore.Images
|
|
import android.widget.ImageView
|
|
import com.bumptech.glide.Glide
|
|
import com.bumptech.glide.Priority
|
|
import com.bumptech.glide.integration.webp.decoder.WebpDrawable
|
|
import com.bumptech.glide.integration.webp.decoder.WebpDrawableTransformation
|
|
import com.bumptech.glide.load.DataSource
|
|
import com.bumptech.glide.load.DecodeFormat
|
|
import com.bumptech.glide.load.MultiTransformation
|
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
import com.bumptech.glide.load.engine.GlideException
|
|
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
|
import com.bumptech.glide.load.resource.bitmap.FitCenter
|
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|
import com.bumptech.glide.request.RequestListener
|
|
import com.bumptech.glide.request.RequestOptions
|
|
import com.bumptech.glide.request.target.Target
|
|
import com.bumptech.glide.signature.ObjectKey
|
|
import com.simplemobiletools.commons.extensions.*
|
|
import com.simplemobiletools.commons.helpers.*
|
|
import com.simplemobiletools.commons.views.MySquareImageView
|
|
import com.simplemobiletools.gallery.pro.R
|
|
import com.simplemobiletools.gallery.pro.asynctasks.GetMediaAsynctask
|
|
import com.simplemobiletools.gallery.pro.databases.GalleryDatabase
|
|
import com.simplemobiletools.gallery.pro.helpers.*
|
|
import com.simplemobiletools.gallery.pro.interfaces.*
|
|
import com.simplemobiletools.gallery.pro.models.*
|
|
import com.simplemobiletools.gallery.pro.svg.SvgSoftwareLayerSetter
|
|
import com.squareup.picasso.Picasso
|
|
import java.io.File
|
|
import java.io.FileInputStream
|
|
import java.nio.ByteBuffer
|
|
import java.nio.channels.FileChannel
|
|
import kotlin.collections.set
|
|
import kotlin.math.max
|
|
|
|
val Context.audioManager get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
|
|
fun Context.getHumanizedFilename(path: String): String {
|
|
val humanized = humanizePath(path)
|
|
return humanized.substring(humanized.lastIndexOf("/") + 1)
|
|
}
|
|
|
|
val Context.config: Config get() = Config.newInstance(applicationContext)
|
|
|
|
val Context.widgetsDB: WidgetsDao get() = GalleryDatabase.getInstance(applicationContext).WidgetsDao()
|
|
|
|
val Context.mediaDB: MediumDao get() = GalleryDatabase.getInstance(applicationContext).MediumDao()
|
|
|
|
val Context.directoryDB: DirectoryDao get() = GalleryDatabase.getInstance(applicationContext).DirectoryDao()
|
|
|
|
val Context.favoritesDB: FavoritesDao get() = GalleryDatabase.getInstance(applicationContext).FavoritesDao()
|
|
|
|
val Context.dateTakensDB: DateTakensDao get() = GalleryDatabase.getInstance(applicationContext).DateTakensDao()
|
|
|
|
val Context.recycleBin: File get() = filesDir
|
|
|
|
fun Context.movePinnedDirectoriesToFront(dirs: ArrayList<Directory>): ArrayList<Directory> {
|
|
val foundFolders = ArrayList<Directory>()
|
|
val pinnedFolders = config.pinnedFolders
|
|
|
|
dirs.forEach {
|
|
if (pinnedFolders.contains(it.path)) {
|
|
foundFolders.add(it)
|
|
}
|
|
}
|
|
|
|
dirs.removeAll(foundFolders)
|
|
dirs.addAll(0, foundFolders)
|
|
if (config.tempFolderPath.isNotEmpty()) {
|
|
val newFolder = dirs.firstOrNull { it.path == config.tempFolderPath }
|
|
if (newFolder != null) {
|
|
dirs.remove(newFolder)
|
|
dirs.add(0, newFolder)
|
|
}
|
|
}
|
|
|
|
if (config.showRecycleBinAtFolders && config.showRecycleBinLast) {
|
|
val binIndex = dirs.indexOfFirst { it.isRecycleBin() }
|
|
if (binIndex != -1) {
|
|
val bin = dirs.removeAt(binIndex)
|
|
dirs.add(bin)
|
|
}
|
|
}
|
|
return dirs
|
|
}
|
|
|
|
@Suppress("UNCHECKED_CAST")
|
|
fun Context.getSortedDirectories(source: ArrayList<Directory>): ArrayList<Directory> {
|
|
val sorting = config.directorySorting
|
|
val dirs = source.clone() as ArrayList<Directory>
|
|
|
|
if (sorting and SORT_BY_RANDOM != 0) {
|
|
dirs.shuffle()
|
|
return movePinnedDirectoriesToFront(dirs)
|
|
} else if (sorting and SORT_BY_CUSTOM != 0) {
|
|
val newDirsOrdered = ArrayList<Directory>()
|
|
config.customFoldersOrder.split("|||").forEach { path ->
|
|
val index = dirs.indexOfFirst { it.path == path }
|
|
if (index != -1) {
|
|
val dir = dirs.removeAt(index)
|
|
newDirsOrdered.add(dir)
|
|
}
|
|
}
|
|
|
|
dirs.mapTo(newDirsOrdered, { it })
|
|
return newDirsOrdered
|
|
}
|
|
|
|
dirs.sortWith(Comparator { o1, o2 ->
|
|
o1 as Directory
|
|
o2 as Directory
|
|
|
|
var result = when {
|
|
sorting and SORT_BY_NAME != 0 -> {
|
|
if (o1.sortValue.isEmpty()) {
|
|
o1.sortValue = o1.name.toLowerCase()
|
|
}
|
|
|
|
if (o2.sortValue.isEmpty()) {
|
|
o2.sortValue = o2.name.toLowerCase()
|
|
}
|
|
|
|
if (sorting and SORT_USE_NUMERIC_VALUE != 0) {
|
|
AlphanumericComparator().compare(o1.sortValue.normalizeString().toLowerCase(), o2.sortValue.normalizeString().toLowerCase())
|
|
} else {
|
|
o1.sortValue.normalizeString().toLowerCase().compareTo(o2.sortValue.normalizeString().toLowerCase())
|
|
}
|
|
}
|
|
|
|
sorting and SORT_BY_PATH != 0 -> {
|
|
if (o1.sortValue.isEmpty()) {
|
|
o1.sortValue = o1.path.toLowerCase()
|
|
}
|
|
|
|
if (o2.sortValue.isEmpty()) {
|
|
o2.sortValue = o2.path.toLowerCase()
|
|
}
|
|
|
|
if (sorting and SORT_USE_NUMERIC_VALUE != 0) {
|
|
AlphanumericComparator().compare(o1.sortValue.toLowerCase(), o2.sortValue.toLowerCase())
|
|
} else {
|
|
o1.sortValue.toLowerCase().compareTo(o2.sortValue.toLowerCase())
|
|
}
|
|
}
|
|
|
|
sorting and SORT_BY_PATH != 0 -> AlphanumericComparator().compare(o1.sortValue.toLowerCase(), o2.sortValue.toLowerCase())
|
|
sorting and SORT_BY_SIZE != 0 -> (o1.sortValue.toLongOrNull() ?: 0).compareTo(o2.sortValue.toLongOrNull() ?: 0)
|
|
sorting and SORT_BY_DATE_MODIFIED != 0 -> (o1.sortValue.toLongOrNull() ?: 0).compareTo(o2.sortValue.toLongOrNull() ?: 0)
|
|
else -> (o1.sortValue.toLongOrNull() ?: 0).compareTo(o2.sortValue.toLongOrNull() ?: 0)
|
|
}
|
|
|
|
if (sorting and SORT_DESCENDING != 0) {
|
|
result *= -1
|
|
}
|
|
result
|
|
})
|
|
|
|
return movePinnedDirectoriesToFront(dirs)
|
|
}
|
|
|
|
fun Context.getDirsToShow(dirs: ArrayList<Directory>, allDirs: ArrayList<Directory>, currentPathPrefix: String): ArrayList<Directory> {
|
|
return if (config.groupDirectSubfolders) {
|
|
dirs.forEach {
|
|
it.subfoldersCount = 0
|
|
it.subfoldersMediaCount = it.mediaCnt
|
|
}
|
|
|
|
val filledDirs = fillWithSharedDirectParents(dirs)
|
|
val parentDirs = getDirectParentSubfolders(filledDirs, currentPathPrefix)
|
|
updateSubfolderCounts(filledDirs, parentDirs)
|
|
|
|
// show the current folder as an available option too, not just subfolders
|
|
if (currentPathPrefix.isNotEmpty()) {
|
|
val currentFolder =
|
|
allDirs.firstOrNull { parentDirs.firstOrNull { it.path.equals(currentPathPrefix, true) } == null && it.path.equals(currentPathPrefix, true) }
|
|
currentFolder?.apply {
|
|
subfoldersCount = 1
|
|
parentDirs.add(this)
|
|
}
|
|
}
|
|
|
|
getSortedDirectories(parentDirs)
|
|
} else {
|
|
dirs.forEach { it.subfoldersMediaCount = it.mediaCnt }
|
|
dirs
|
|
}
|
|
}
|
|
|
|
private fun Context.addParentWithoutMediaFiles(into: ArrayList<Directory>, path: String): Boolean {
|
|
val isSortingAscending = config.sorting.isSortingAscending()
|
|
val subDirs = into.filter { File(it.path).parent.equals(path, true) } as ArrayList<Directory>
|
|
val newDirId = max(1000L, into.maxOf { it.id ?: 0L })
|
|
if (subDirs.isNotEmpty()) {
|
|
val lastModified = if (isSortingAscending) {
|
|
subDirs.minByOrNull { it.modified }?.modified
|
|
} else {
|
|
subDirs.maxByOrNull { it.modified }?.modified
|
|
} ?: 0
|
|
|
|
val dateTaken = if (isSortingAscending) {
|
|
subDirs.minByOrNull { it.taken }?.taken
|
|
} else {
|
|
subDirs.maxByOrNull { it.taken }?.taken
|
|
} ?: 0
|
|
|
|
var mediaTypes = 0
|
|
subDirs.forEach {
|
|
mediaTypes = mediaTypes or it.types
|
|
}
|
|
|
|
val directory = Directory(
|
|
newDirId + 1,
|
|
path,
|
|
subDirs.first().tmb,
|
|
getFolderNameFromPath(path),
|
|
subDirs.sumBy { it.mediaCnt },
|
|
lastModified,
|
|
dateTaken,
|
|
subDirs.sumByLong { it.size },
|
|
getPathLocation(path),
|
|
mediaTypes,
|
|
""
|
|
)
|
|
|
|
directory.containsMediaFilesDirectly = false
|
|
into.add(directory)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
fun Context.fillWithSharedDirectParents(dirs: ArrayList<Directory>): ArrayList<Directory> {
|
|
val allDirs = ArrayList<Directory>(dirs)
|
|
val childCounts = mutableMapOf<String, Int>()
|
|
for (dir in dirs) {
|
|
File(dir.path).parent?.let {
|
|
val current = childCounts[it] ?: 0
|
|
childCounts.put(it, current + 1)
|
|
}
|
|
}
|
|
|
|
childCounts
|
|
.filter { dir -> dir.value > 1 && dirs.none { it.path.equals(dir.key, true) } }
|
|
.toList()
|
|
.sortedByDescending { it.first.length }
|
|
.forEach { (parent, _) ->
|
|
addParentWithoutMediaFiles(allDirs, parent)
|
|
}
|
|
return allDirs
|
|
}
|
|
|
|
fun Context.getDirectParentSubfolders(dirs: ArrayList<Directory>, currentPathPrefix: String): ArrayList<Directory> {
|
|
val folders = dirs.map { it.path }.sorted().toMutableSet() as HashSet<String>
|
|
val currentPaths = LinkedHashSet<String>()
|
|
val foldersWithoutMediaFiles = ArrayList<String>()
|
|
|
|
for (path in folders) {
|
|
if (path == RECYCLE_BIN || path == FAVORITES) {
|
|
continue
|
|
}
|
|
|
|
if (currentPathPrefix.isNotEmpty()) {
|
|
if (!path.startsWith(currentPathPrefix, true)) {
|
|
continue
|
|
}
|
|
|
|
if (!File(path).parent.equals(currentPathPrefix, true)) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if (currentPathPrefix.isNotEmpty() && path.equals(currentPathPrefix, true) || File(path).parent.equals(currentPathPrefix, true)) {
|
|
currentPaths.add(path)
|
|
} else if (folders.any { !it.equals(path, true) && (File(path).parent.equals(it, true) || File(it).parent.equals(File(path).parent, true)) }) {
|
|
// if we have folders like
|
|
// /storage/emulated/0/Pictures/Images and
|
|
// /storage/emulated/0/Pictures/Screenshots,
|
|
// but /storage/emulated/0/Pictures is empty, still Pictures with the first folders thumbnails and proper other info
|
|
val parent = File(path).parent
|
|
if (parent != null && !folders.contains(parent) && dirs.none { it.path.equals(parent, true) }) {
|
|
currentPaths.add(parent)
|
|
if (addParentWithoutMediaFiles(dirs, parent)) {
|
|
foldersWithoutMediaFiles.add(parent)
|
|
}
|
|
}
|
|
} else {
|
|
currentPaths.add(path)
|
|
}
|
|
}
|
|
|
|
var areDirectSubfoldersAvailable = false
|
|
currentPaths.forEach {
|
|
val path = it
|
|
currentPaths.forEach {
|
|
if (!foldersWithoutMediaFiles.contains(it) && !it.equals(path, true) && File(it).parent?.equals(path, true) == true) {
|
|
areDirectSubfoldersAvailable = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currentPathPrefix.isEmpty() && folders.contains(RECYCLE_BIN)) {
|
|
currentPaths.add(RECYCLE_BIN)
|
|
}
|
|
|
|
if (currentPathPrefix.isEmpty() && folders.contains(FAVORITES)) {
|
|
currentPaths.add(FAVORITES)
|
|
}
|
|
|
|
if (folders.size == currentPaths.size) {
|
|
return dirs.filter { currentPaths.contains(it.path) } as ArrayList<Directory>
|
|
}
|
|
|
|
folders.clear()
|
|
folders.addAll(currentPaths)
|
|
|
|
val dirsToShow = dirs.filter { folders.contains(it.path) } as ArrayList<Directory>
|
|
return if (areDirectSubfoldersAvailable) {
|
|
getDirectParentSubfolders(dirsToShow, currentPathPrefix)
|
|
} else {
|
|
dirsToShow
|
|
}
|
|
}
|
|
|
|
fun Context.updateSubfolderCounts(children: ArrayList<Directory>, parentDirs: ArrayList<Directory>) {
|
|
for (child in children) {
|
|
var longestSharedPath = ""
|
|
for (parentDir in parentDirs) {
|
|
if (parentDir.path == child.path) {
|
|
longestSharedPath = child.path
|
|
continue
|
|
}
|
|
|
|
if (child.path.startsWith(parentDir.path, true) && parentDir.path.length > longestSharedPath.length) {
|
|
longestSharedPath = parentDir.path
|
|
}
|
|
}
|
|
|
|
// make sure we count only the proper direct subfolders, grouped the same way as on the main screen
|
|
parentDirs.firstOrNull { it.path == longestSharedPath }?.apply {
|
|
if (path.equals(child.path, true) || path.equals(File(child.path).parent, true) || children.any { it.path.equals(File(child.path).parent, true) }) {
|
|
if (child.containsMediaFilesDirectly) {
|
|
subfoldersCount++
|
|
}
|
|
|
|
if (path != child.path) {
|
|
subfoldersMediaCount += child.mediaCnt
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun Context.getNoMediaFolders(callback: (folders: ArrayList<String>) -> Unit) {
|
|
ensureBackgroundThread {
|
|
callback(getNoMediaFoldersSync())
|
|
}
|
|
}
|
|
|
|
fun Context.getNoMediaFoldersSync(): ArrayList<String> {
|
|
val folders = ArrayList<String>()
|
|
|
|
val uri = Files.getContentUri("external")
|
|
val projection = arrayOf(Files.FileColumns.DATA)
|
|
val selection = "${Files.FileColumns.MEDIA_TYPE} = ? AND ${Files.FileColumns.TITLE} LIKE ?"
|
|
val selectionArgs = arrayOf(Files.FileColumns.MEDIA_TYPE_NONE.toString(), "%$NOMEDIA%")
|
|
val sortOrder = "${Files.FileColumns.DATE_MODIFIED} DESC"
|
|
val OTGPath = config.OTGPath
|
|
|
|
var cursor: Cursor? = null
|
|
try {
|
|
cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
|
|
if (cursor?.moveToFirst() == true) {
|
|
do {
|
|
val path = cursor.getStringValue(Files.FileColumns.DATA) ?: continue
|
|
val noMediaFile = File(path)
|
|
if (getDoesFilePathExist(noMediaFile.absolutePath, OTGPath) && noMediaFile.name == NOMEDIA) {
|
|
folders.add(noMediaFile.parent)
|
|
}
|
|
} while (cursor.moveToNext())
|
|
}
|
|
} catch (ignored: Exception) {
|
|
} finally {
|
|
cursor?.close()
|
|
}
|
|
|
|
return folders
|
|
}
|
|
|
|
fun Context.rescanFolderMedia(path: String) {
|
|
ensureBackgroundThread {
|
|
rescanFolderMediaSync(path)
|
|
}
|
|
}
|
|
|
|
fun Context.rescanFolderMediaSync(path: String) {
|
|
getCachedMedia(path) { cached ->
|
|
GetMediaAsynctask(applicationContext, path, isPickImage = false, isPickVideo = false, showAll = false) { newMedia ->
|
|
ensureBackgroundThread {
|
|
val media = newMedia.filterIsInstance<Medium>() as ArrayList<Medium>
|
|
try {
|
|
mediaDB.insertAll(media)
|
|
|
|
cached.forEach { thumbnailItem ->
|
|
if (!newMedia.contains(thumbnailItem)) {
|
|
val mediumPath = (thumbnailItem as? Medium)?.path
|
|
if (mediumPath != null) {
|
|
deleteDBPath(mediumPath)
|
|
}
|
|
}
|
|
}
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
}.execute()
|
|
}
|
|
}
|
|
|
|
fun Context.storeDirectoryItems(items: ArrayList<Directory>) {
|
|
ensureBackgroundThread {
|
|
directoryDB.insertAll(items)
|
|
}
|
|
}
|
|
|
|
fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders: MutableSet<String>, noMediaFolders: ArrayList<String>): String {
|
|
val dirName = getFolderNameFromPath(path)
|
|
val folderNoMediaStatuses = HashMap<String, Boolean>()
|
|
noMediaFolders.forEach { folder ->
|
|
folderNoMediaStatuses["$folder/$NOMEDIA"] = true
|
|
}
|
|
|
|
return if (path.doesThisOrParentHaveNoMedia(folderNoMediaStatuses, null) && !path.isThisOrParentIncluded(includedFolders)) {
|
|
"$dirName $hidden"
|
|
} else {
|
|
dirName
|
|
}
|
|
}
|
|
|
|
fun Context.getFolderNameFromPath(path: String): String {
|
|
return when (path) {
|
|
internalStoragePath -> getString(com.simplemobiletools.commons.R.string.internal)
|
|
sdCardPath -> getString(com.simplemobiletools.commons.R.string.sd_card)
|
|
otgPath -> getString(com.simplemobiletools.commons.R.string.usb)
|
|
FAVORITES -> getString(com.simplemobiletools.commons.R.string.favorites)
|
|
RECYCLE_BIN -> getString(com.simplemobiletools.commons.R.string.recycle_bin)
|
|
else -> path.getFilenameFromPath()
|
|
}
|
|
}
|
|
|
|
fun Context.loadImage(
|
|
type: Int, path: String, target: MySquareImageView, horizontalScroll: Boolean, animateGifs: Boolean, cropThumbnails: Boolean,
|
|
roundCorners: Int, signature: ObjectKey, skipMemoryCacheAtPaths: ArrayList<String>? = null
|
|
) {
|
|
target.isHorizontalScrolling = horizontalScroll
|
|
if (type == TYPE_SVGS) {
|
|
loadSVG(path, target, cropThumbnails, roundCorners, signature)
|
|
} else {
|
|
val tryLoadingWithPicasso = type == TYPE_IMAGES && path.isPng()
|
|
loadImageBase(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths, animateGifs, tryLoadingWithPicasso)
|
|
}
|
|
}
|
|
|
|
fun Context.addTempFolderIfNeeded(dirs: ArrayList<Directory>): ArrayList<Directory> {
|
|
val tempFolderPath = config.tempFolderPath
|
|
return if (tempFolderPath.isNotEmpty()) {
|
|
val directories = ArrayList<Directory>()
|
|
val newFolder = Directory(null, tempFolderPath, "", tempFolderPath.getFilenameFromPath(), 0, 0, 0, 0L, getPathLocation(tempFolderPath), 0, "")
|
|
directories.add(newFolder)
|
|
directories.addAll(dirs)
|
|
directories
|
|
} else {
|
|
dirs
|
|
}
|
|
}
|
|
|
|
fun Context.getPathLocation(path: String): Int {
|
|
return when {
|
|
isPathOnSD(path) -> LOCATION_SD
|
|
isPathOnOTG(path) -> LOCATION_OTG
|
|
else -> LOCATION_INTERNAL
|
|
}
|
|
}
|
|
|
|
fun Context.loadImageBase(
|
|
path: String,
|
|
target: MySquareImageView,
|
|
cropThumbnails: Boolean,
|
|
roundCorners: Int,
|
|
signature: ObjectKey,
|
|
skipMemoryCacheAtPaths: ArrayList<String>? = null,
|
|
animate: Boolean = false,
|
|
tryLoadingWithPicasso: Boolean = false,
|
|
crossFadeDuration: Int = 300
|
|
) {
|
|
val options = RequestOptions()
|
|
.signature(signature)
|
|
.skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true)
|
|
.priority(Priority.LOW)
|
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
.format(DecodeFormat.PREFER_ARGB_8888)
|
|
|
|
if (cropThumbnails) {
|
|
options.optionalTransform(CenterCrop())
|
|
options.optionalTransform(WebpDrawable::class.java, WebpDrawableTransformation(CenterCrop()))
|
|
} else {
|
|
options.optionalTransform(FitCenter())
|
|
options.optionalTransform(WebpDrawable::class.java, WebpDrawableTransformation(FitCenter()))
|
|
}
|
|
|
|
// animation is only supported without rounded corners
|
|
if (animate && roundCorners == ROUNDED_CORNERS_NONE) {
|
|
// this is required to make glide cache aware of changes
|
|
options.decode(Drawable::class.java)
|
|
} else {
|
|
options.dontAnimate()
|
|
// don't animate is not enough for webp files, decode as bitmap forces first frame use in animated webps
|
|
options.decode(Bitmap::class.java)
|
|
}
|
|
|
|
if (roundCorners != ROUNDED_CORNERS_NONE) {
|
|
val cornerSize = if (roundCorners == ROUNDED_CORNERS_SMALL) com.simplemobiletools.commons.R.dimen.rounded_corner_radius_small else com.simplemobiletools.commons.R.dimen.rounded_corner_radius_big
|
|
val cornerRadius = resources.getDimension(cornerSize).toInt()
|
|
val roundedCornersTransform = RoundedCorners(cornerRadius)
|
|
options.optionalTransform(MultiTransformation(CenterCrop(), roundedCornersTransform))
|
|
options.optionalTransform(WebpDrawable::class.java, MultiTransformation(WebpDrawableTransformation(CenterCrop()), WebpDrawableTransformation(roundedCornersTransform)))
|
|
}
|
|
|
|
var builder = Glide.with(applicationContext)
|
|
.load(path)
|
|
.apply(options)
|
|
.transition(DrawableTransitionOptions.withCrossFade(crossFadeDuration))
|
|
|
|
if (tryLoadingWithPicasso) {
|
|
builder = builder.listener(object : RequestListener<Drawable> {
|
|
override fun onLoadFailed(e: GlideException?, model: Any?, targetBitmap: Target<Drawable>, isFirstResource: Boolean): Boolean {
|
|
tryLoadingWithPicasso(path, target, cropThumbnails, roundCorners, signature)
|
|
return true
|
|
}
|
|
|
|
override fun onResourceReady(
|
|
resource: Drawable,
|
|
model: Any,
|
|
targetBitmap: Target<Drawable>,
|
|
dataSource: DataSource,
|
|
isFirstResource: Boolean
|
|
): Boolean {
|
|
return false
|
|
}
|
|
})
|
|
}
|
|
|
|
builder.into(target)
|
|
}
|
|
|
|
fun Context.loadSVG(path: String, target: MySquareImageView, cropThumbnails: Boolean, roundCorners: Int, signature: ObjectKey) {
|
|
target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER
|
|
|
|
val options = RequestOptions().signature(signature)
|
|
var builder = Glide.with(applicationContext)
|
|
.`as`(PictureDrawable::class.java)
|
|
.listener(SvgSoftwareLayerSetter())
|
|
.load(path)
|
|
.apply(options)
|
|
.transition(DrawableTransitionOptions.withCrossFade())
|
|
|
|
if (roundCorners != ROUNDED_CORNERS_NONE) {
|
|
val cornerSize =
|
|
if (roundCorners == ROUNDED_CORNERS_SMALL) com.simplemobiletools.commons.R.dimen.rounded_corner_radius_small else com.simplemobiletools.commons.R.dimen.rounded_corner_radius_big
|
|
val cornerRadius = resources.getDimension(cornerSize).toInt()
|
|
builder = builder.transform(CenterCrop(), RoundedCorners(cornerRadius))
|
|
}
|
|
|
|
builder.into(target)
|
|
}
|
|
|
|
// intended mostly for Android 11 issues, that fail loading PNG files bigger than 10 MB
|
|
fun Context.tryLoadingWithPicasso(path: String, view: MySquareImageView, cropThumbnails: Boolean, roundCorners: Int, signature: ObjectKey) {
|
|
var pathToLoad = "file://$path"
|
|
pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23")
|
|
|
|
try {
|
|
var builder = Picasso.get()
|
|
.load(pathToLoad)
|
|
.stableKey(signature.toString())
|
|
|
|
builder = if (cropThumbnails) {
|
|
builder.centerCrop().fit()
|
|
} else {
|
|
builder.centerInside()
|
|
}
|
|
|
|
if (roundCorners != ROUNDED_CORNERS_NONE) {
|
|
val cornerSize =
|
|
if (roundCorners == ROUNDED_CORNERS_SMALL) com.simplemobiletools.commons.R.dimen.rounded_corner_radius_small else com.simplemobiletools.commons.R.dimen.rounded_corner_radius_big
|
|
val cornerRadius = resources.getDimension(cornerSize).toInt()
|
|
builder = builder.transform(PicassoRoundedCornersTransformation(cornerRadius.toFloat()))
|
|
}
|
|
|
|
builder.into(view)
|
|
} catch (e: Exception) {
|
|
}
|
|
}
|
|
|
|
fun Context.getCachedDirectories(
|
|
getVideosOnly: Boolean = false,
|
|
getImagesOnly: Boolean = false,
|
|
forceShowHidden: Boolean = false,
|
|
forceShowExcluded: Boolean = false,
|
|
callback: (ArrayList<Directory>) -> Unit
|
|
) {
|
|
ensureBackgroundThread {
|
|
try {
|
|
Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE)
|
|
} catch (ignored: Exception) {
|
|
}
|
|
|
|
val directories = try {
|
|
directoryDB.getAll() as ArrayList<Directory>
|
|
} catch (e: Exception) {
|
|
ArrayList()
|
|
}
|
|
|
|
if (!config.showRecycleBinAtFolders) {
|
|
directories.removeAll { it.isRecycleBin() }
|
|
}
|
|
|
|
val shouldShowHidden = config.shouldShowHidden || forceShowHidden
|
|
val excludedPaths = if (config.temporarilyShowExcluded || forceShowExcluded) {
|
|
HashSet()
|
|
} else {
|
|
config.excludedFolders
|
|
}
|
|
|
|
val includedPaths = config.includedFolders
|
|
|
|
val folderNoMediaStatuses = HashMap<String, Boolean>()
|
|
val noMediaFolders = getNoMediaFoldersSync()
|
|
noMediaFolders.forEach { folder ->
|
|
folderNoMediaStatuses["$folder/$NOMEDIA"] = true
|
|
}
|
|
|
|
var filteredDirectories = directories.filter {
|
|
it.path.shouldFolderBeVisible(excludedPaths, includedPaths, shouldShowHidden, folderNoMediaStatuses) { path, hasNoMedia ->
|
|
folderNoMediaStatuses[path] = hasNoMedia
|
|
}
|
|
} as ArrayList<Directory>
|
|
val filterMedia = config.filterMedia
|
|
|
|
filteredDirectories = (when {
|
|
getVideosOnly -> filteredDirectories.filter { it.types and TYPE_VIDEOS != 0 }
|
|
getImagesOnly -> filteredDirectories.filter { it.types and TYPE_IMAGES != 0 }
|
|
else -> filteredDirectories.filter {
|
|
(filterMedia and TYPE_IMAGES != 0 && it.types and TYPE_IMAGES != 0) ||
|
|
(filterMedia and TYPE_VIDEOS != 0 && it.types and TYPE_VIDEOS != 0) ||
|
|
(filterMedia and TYPE_GIFS != 0 && it.types and TYPE_GIFS != 0) ||
|
|
(filterMedia and TYPE_RAWS != 0 && it.types and TYPE_RAWS != 0) ||
|
|
(filterMedia and TYPE_SVGS != 0 && it.types and TYPE_SVGS != 0) ||
|
|
(filterMedia and TYPE_PORTRAITS != 0 && it.types and TYPE_PORTRAITS != 0)
|
|
}
|
|
}) as ArrayList<Directory>
|
|
|
|
if (shouldShowHidden) {
|
|
val hiddenString = resources.getString(R.string.hidden)
|
|
filteredDirectories.forEach {
|
|
val noMediaPath = "${it.path}/$NOMEDIA"
|
|
val hasNoMedia = if (folderNoMediaStatuses.keys.contains(noMediaPath)) {
|
|
folderNoMediaStatuses[noMediaPath]!!
|
|
} else {
|
|
it.path.doesThisOrParentHaveNoMedia(folderNoMediaStatuses) { path, hasNoMedia ->
|
|
val newPath = "$path/$NOMEDIA"
|
|
folderNoMediaStatuses[newPath] = hasNoMedia
|
|
}
|
|
}
|
|
|
|
it.name = if (hasNoMedia && !it.path.isThisOrParentIncluded(includedPaths)) {
|
|
"${it.name.removeSuffix(hiddenString).trim()} $hiddenString"
|
|
} else {
|
|
it.name.removeSuffix(hiddenString).trim()
|
|
}
|
|
}
|
|
}
|
|
|
|
val clone = filteredDirectories.clone() as ArrayList<Directory>
|
|
callback(clone.distinctBy { it.path.getDistinctPath() } as ArrayList<Directory>)
|
|
removeInvalidDBDirectories(filteredDirectories)
|
|
}
|
|
}
|
|
|
|
fun Context.getCachedMedia(path: String, getVideosOnly: Boolean = false, getImagesOnly: Boolean = false, callback: (ArrayList<ThumbnailItem>) -> Unit) {
|
|
ensureBackgroundThread {
|
|
val mediaFetcher = MediaFetcher(this)
|
|
val foldersToScan = if (path.isEmpty()) mediaFetcher.getFoldersToScan() else arrayListOf(path)
|
|
var media = ArrayList<Medium>()
|
|
if (path == FAVORITES) {
|
|
media.addAll(mediaDB.getFavorites())
|
|
}
|
|
|
|
if (path == RECYCLE_BIN) {
|
|
media.addAll(getUpdatedDeletedMedia())
|
|
}
|
|
|
|
if (config.filterMedia and TYPE_PORTRAITS != 0) {
|
|
val foldersToAdd = ArrayList<String>()
|
|
for (folder in foldersToScan) {
|
|
val allFiles = File(folder).listFiles() ?: continue
|
|
allFiles.filter { it.name.startsWith("img_", true) && it.isDirectory }.forEach {
|
|
foldersToAdd.add(it.absolutePath)
|
|
}
|
|
}
|
|
foldersToScan.addAll(foldersToAdd)
|
|
}
|
|
|
|
val shouldShowHidden = config.shouldShowHidden
|
|
foldersToScan.filter { path.isNotEmpty() || !config.isFolderProtected(it) }.forEach {
|
|
try {
|
|
val currMedia = mediaDB.getMediaFromPath(it)
|
|
media.addAll(currMedia)
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
|
|
if (!shouldShowHidden) {
|
|
media = media.filter { !it.path.contains("/.") } as ArrayList<Medium>
|
|
}
|
|
|
|
val filterMedia = config.filterMedia
|
|
media = (when {
|
|
getVideosOnly -> media.filter { it.type == TYPE_VIDEOS }
|
|
getImagesOnly -> media.filter { it.type == TYPE_IMAGES }
|
|
else -> media.filter {
|
|
(filterMedia and TYPE_IMAGES != 0 && it.type == TYPE_IMAGES) ||
|
|
(filterMedia and TYPE_VIDEOS != 0 && it.type == TYPE_VIDEOS) ||
|
|
(filterMedia and TYPE_GIFS != 0 && it.type == TYPE_GIFS) ||
|
|
(filterMedia and TYPE_RAWS != 0 && it.type == TYPE_RAWS) ||
|
|
(filterMedia and TYPE_SVGS != 0 && it.type == TYPE_SVGS) ||
|
|
(filterMedia and TYPE_PORTRAITS != 0 && it.type == TYPE_PORTRAITS)
|
|
}
|
|
}) as ArrayList<Medium>
|
|
|
|
val pathToUse = if (path.isEmpty()) SHOW_ALL else path
|
|
mediaFetcher.sortMedia(media, config.getFolderSorting(pathToUse))
|
|
val grouped = mediaFetcher.groupMedia(media, pathToUse)
|
|
callback(grouped.clone() as ArrayList<ThumbnailItem>)
|
|
val OTGPath = config.OTGPath
|
|
|
|
try {
|
|
val mediaToDelete = ArrayList<Medium>()
|
|
// creating a new thread intentionally, do not reuse the common background thread
|
|
Thread {
|
|
media.filter { !getDoesFilePathExist(it.path, OTGPath) }.forEach {
|
|
if (it.path.startsWith(recycleBinPath)) {
|
|
deleteDBPath(it.path)
|
|
} else {
|
|
mediaToDelete.add(it)
|
|
}
|
|
}
|
|
|
|
if (mediaToDelete.isNotEmpty()) {
|
|
try {
|
|
mediaDB.deleteMedia(*mediaToDelete.toTypedArray())
|
|
|
|
mediaToDelete.filter { it.isFavorite }.forEach {
|
|
favoritesDB.deleteFavoritePath(it.path)
|
|
}
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
}.start()
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
}
|
|
|
|
fun Context.removeInvalidDBDirectories(dirs: ArrayList<Directory>? = null) {
|
|
val dirsToCheck = dirs ?: directoryDB.getAll()
|
|
val OTGPath = config.OTGPath
|
|
dirsToCheck.filter { !it.areFavorites() && !it.isRecycleBin() && !getDoesFilePathExist(it.path, OTGPath) && it.path != config.tempFolderPath }.forEach {
|
|
try {
|
|
directoryDB.deleteDirPath(it.path)
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
}
|
|
|
|
fun Context.updateDBMediaPath(oldPath: String, newPath: String) {
|
|
val newFilename = newPath.getFilenameFromPath()
|
|
val newParentPath = newPath.getParentPath()
|
|
try {
|
|
mediaDB.updateMedium(newFilename, newPath, newParentPath, oldPath)
|
|
favoritesDB.updateFavorite(newFilename, newPath, newParentPath, oldPath)
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
|
|
fun Context.updateDBDirectory(directory: Directory) {
|
|
try {
|
|
directoryDB.updateDirectory(
|
|
directory.path,
|
|
directory.tmb,
|
|
directory.mediaCnt,
|
|
directory.modified,
|
|
directory.taken,
|
|
directory.size,
|
|
directory.types,
|
|
directory.sortValue
|
|
)
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
|
|
fun Context.getOTGFolderChildren(path: String) = getDocumentFile(path)?.listFiles()
|
|
|
|
fun Context.getOTGFolderChildrenNames(path: String) = getOTGFolderChildren(path)?.map { it.name }?.toMutableList()
|
|
|
|
fun Context.getFavoritePaths(): ArrayList<String> {
|
|
return try {
|
|
favoritesDB.getValidFavoritePaths() as ArrayList<String>
|
|
} catch (e: Exception) {
|
|
ArrayList()
|
|
}
|
|
}
|
|
|
|
fun Context.getFavoriteFromPath(path: String) = Favorite(null, path, path.getFilenameFromPath(), path.getParentPath())
|
|
|
|
fun Context.updateFavorite(path: String, isFavorite: Boolean) {
|
|
try {
|
|
if (isFavorite) {
|
|
favoritesDB.insert(getFavoriteFromPath(path))
|
|
} else {
|
|
favoritesDB.deleteFavoritePath(path)
|
|
}
|
|
} catch (e: Exception) {
|
|
toast(com.simplemobiletools.commons.R.string.unknown_error_occurred)
|
|
}
|
|
}
|
|
|
|
// remove the "recycle_bin" from the file path prefix, replace it with real bin path /data/user...
|
|
fun Context.getUpdatedDeletedMedia(): ArrayList<Medium> {
|
|
val media = try {
|
|
mediaDB.getDeletedMedia() as ArrayList<Medium>
|
|
} catch (ignored: Exception) {
|
|
ArrayList()
|
|
}
|
|
|
|
media.forEach {
|
|
it.path = File(recycleBinPath, it.path.removePrefix(RECYCLE_BIN)).toString()
|
|
}
|
|
return media
|
|
}
|
|
|
|
fun Context.deleteDBPath(path: String) {
|
|
deleteMediumWithPath(path.replaceFirst(recycleBinPath, RECYCLE_BIN))
|
|
}
|
|
|
|
fun Context.deleteMediumWithPath(path: String) {
|
|
try {
|
|
mediaDB.deleteMediumPath(path)
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
|
|
fun Context.updateWidgets() {
|
|
val widgetIDs = AppWidgetManager.getInstance(applicationContext)?.getAppWidgetIds(ComponentName(applicationContext, MyWidgetProvider::class.java))
|
|
?: return
|
|
if (widgetIDs.isNotEmpty()) {
|
|
Intent(applicationContext, MyWidgetProvider::class.java).apply {
|
|
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIDs)
|
|
sendBroadcast(this)
|
|
}
|
|
}
|
|
}
|
|
|
|
// based on https://github.com/sannies/mp4parser/blob/master/examples/src/main/java/com/google/code/mp4parser/example/PrintStructure.java
|
|
fun Context.parseFileChannel(path: String, fc: FileChannel, level: Int, start: Long, end: Long, callback: () -> Unit) {
|
|
val FILE_CHANNEL_CONTAINERS = arrayListOf("moov", "trak", "mdia", "minf", "udta", "stbl")
|
|
try {
|
|
var iteration = 0
|
|
var currEnd = end
|
|
fc.position(start)
|
|
if (currEnd <= 0) {
|
|
currEnd = start + fc.size()
|
|
}
|
|
|
|
while (currEnd - fc.position() > 8) {
|
|
// just a check to avoid deadloop at some videos
|
|
if (iteration++ > 50) {
|
|
return
|
|
}
|
|
|
|
val begin = fc.position()
|
|
val byteBuffer = ByteBuffer.allocate(8)
|
|
fc.read(byteBuffer)
|
|
byteBuffer.rewind()
|
|
val size = IsoTypeReader.readUInt32(byteBuffer)
|
|
val type = IsoTypeReader.read4cc(byteBuffer)
|
|
val newEnd = begin + size
|
|
|
|
if (type == "uuid") {
|
|
val fis = FileInputStream(File(path))
|
|
fis.skip(begin)
|
|
|
|
val sb = StringBuilder()
|
|
val buffer = ByteArray(1024)
|
|
while (sb.length < size) {
|
|
val n = fis.read(buffer)
|
|
if (n != -1) {
|
|
sb.append(String(buffer, 0, n))
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
val xmlString = sb.toString().toLowerCase()
|
|
if (xmlString.contains("gspherical:projectiontype>equirectangular") || xmlString.contains("gspherical:projectiontype=\"equirectangular\"")) {
|
|
callback.invoke()
|
|
}
|
|
return
|
|
}
|
|
|
|
if (FILE_CHANNEL_CONTAINERS.contains(type)) {
|
|
parseFileChannel(path, fc, level + 1, begin + 8, newEnd, callback)
|
|
}
|
|
|
|
fc.position(newEnd)
|
|
}
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
|
|
fun Context.addPathToDB(path: String) {
|
|
ensureBackgroundThread {
|
|
if (!getDoesFilePathExist(path)) {
|
|
return@ensureBackgroundThread
|
|
}
|
|
|
|
val type = when {
|
|
path.isVideoFast() -> TYPE_VIDEOS
|
|
path.isGif() -> TYPE_GIFS
|
|
path.isRawFast() -> TYPE_RAWS
|
|
path.isSvg() -> TYPE_SVGS
|
|
path.isPortrait() -> TYPE_PORTRAITS
|
|
else -> TYPE_IMAGES
|
|
}
|
|
|
|
try {
|
|
val isFavorite = favoritesDB.isFavorite(path)
|
|
val videoDuration = if (type == TYPE_VIDEOS) getDuration(path) ?: 0 else 0
|
|
val medium = Medium(
|
|
null, path.getFilenameFromPath(), path, path.getParentPath(), System.currentTimeMillis(), System.currentTimeMillis(),
|
|
File(path).length(), type, videoDuration, isFavorite, 0L, 0L
|
|
)
|
|
|
|
mediaDB.insert(medium)
|
|
} catch (ignored: Exception) {
|
|
}
|
|
}
|
|
}
|
|
|
|
fun Context.createDirectoryFromMedia(
|
|
path: String, curMedia: ArrayList<Medium>, albumCovers: ArrayList<AlbumCover>, hiddenString: String,
|
|
includedFolders: MutableSet<String>, getProperFileSize: Boolean, noMediaFolders: ArrayList<String>
|
|
): Directory {
|
|
val OTGPath = config.OTGPath
|
|
val grouped = MediaFetcher(this).groupMedia(curMedia, path)
|
|
var thumbnail: String? = null
|
|
|
|
albumCovers.forEach {
|
|
if (it.path == path && getDoesFilePathExist(it.tmb, OTGPath)) {
|
|
thumbnail = it.tmb
|
|
}
|
|
}
|
|
|
|
if (thumbnail == null) {
|
|
val sortedMedia = grouped.filter { it is Medium }.toMutableList() as ArrayList<Medium>
|
|
thumbnail = sortedMedia.firstOrNull { getDoesFilePathExist(it.path, OTGPath) }?.path ?: ""
|
|
}
|
|
|
|
if (config.OTGPath.isNotEmpty() && thumbnail!!.startsWith(config.OTGPath)) {
|
|
thumbnail = thumbnail!!.getOTGPublicPath(applicationContext)
|
|
}
|
|
|
|
val isSortingAscending = config.directorySorting.isSortingAscending()
|
|
val defaultMedium = Medium(0, "", "", "", 0L, 0L, 0L, 0, 0, false, 0L, 0L)
|
|
val firstItem = curMedia.firstOrNull() ?: defaultMedium
|
|
val lastItem = curMedia.lastOrNull() ?: defaultMedium
|
|
val dirName = checkAppendingHidden(path, hiddenString, includedFolders, noMediaFolders)
|
|
val lastModified = if (isSortingAscending) Math.min(firstItem.modified, lastItem.modified) else Math.max(firstItem.modified, lastItem.modified)
|
|
val dateTaken = if (isSortingAscending) Math.min(firstItem.taken, lastItem.taken) else Math.max(firstItem.taken, lastItem.taken)
|
|
val size = if (getProperFileSize) curMedia.sumByLong { it.size } else 0L
|
|
val mediaTypes = curMedia.getDirMediaTypes()
|
|
val sortValue = getDirectorySortingValue(curMedia, path, dirName, size)
|
|
return Directory(null, path, thumbnail!!, dirName, curMedia.size, lastModified, dateTaken, size, getPathLocation(path), mediaTypes, sortValue)
|
|
}
|
|
|
|
fun Context.getDirectorySortingValue(media: ArrayList<Medium>, path: String, name: String, size: Long): String {
|
|
val sorting = config.directorySorting
|
|
val sorted = when {
|
|
sorting and SORT_BY_NAME != 0 -> return name
|
|
sorting and SORT_BY_PATH != 0 -> return path
|
|
sorting and SORT_BY_SIZE != 0 -> return size.toString()
|
|
sorting and SORT_BY_DATE_MODIFIED != 0 -> media.sortedBy { it.modified }
|
|
sorting and SORT_BY_DATE_TAKEN != 0 -> media.sortedBy { it.taken }
|
|
else -> media
|
|
}
|
|
|
|
val relevantMedium = if (sorting.isSortingAscending()) {
|
|
sorted.firstOrNull() ?: return ""
|
|
} else {
|
|
sorted.lastOrNull() ?: return ""
|
|
}
|
|
|
|
val result: Any = when {
|
|
sorting and SORT_BY_DATE_MODIFIED != 0 -> relevantMedium.modified
|
|
sorting and SORT_BY_DATE_TAKEN != 0 -> relevantMedium.taken
|
|
else -> 0
|
|
}
|
|
|
|
return result.toString()
|
|
}
|
|
|
|
fun Context.updateDirectoryPath(path: String) {
|
|
val mediaFetcher = MediaFetcher(applicationContext)
|
|
val getImagesOnly = false
|
|
val getVideosOnly = false
|
|
val hiddenString = getString(R.string.hidden)
|
|
val albumCovers = config.parseAlbumCovers()
|
|
val includedFolders = config.includedFolders
|
|
val noMediaFolders = getNoMediaFoldersSync()
|
|
|
|
val sorting = config.getFolderSorting(path)
|
|
val grouping = config.getFolderGrouping(path)
|
|
val getProperDateTaken = config.directorySorting and SORT_BY_DATE_TAKEN != 0 ||
|
|
sorting and SORT_BY_DATE_TAKEN != 0 ||
|
|
grouping and GROUP_BY_DATE_TAKEN_DAILY != 0 ||
|
|
grouping and GROUP_BY_DATE_TAKEN_MONTHLY != 0
|
|
|
|
val getProperLastModified = config.directorySorting and SORT_BY_DATE_MODIFIED != 0 ||
|
|
sorting and SORT_BY_DATE_MODIFIED != 0 ||
|
|
grouping and GROUP_BY_LAST_MODIFIED_DAILY != 0 ||
|
|
grouping and GROUP_BY_LAST_MODIFIED_MONTHLY != 0
|
|
|
|
val getProperFileSize = config.directorySorting and SORT_BY_SIZE != 0
|
|
|
|
val lastModifieds = if (getProperLastModified) mediaFetcher.getFolderLastModifieds(path) else HashMap()
|
|
val dateTakens = mediaFetcher.getFolderDateTakens(path)
|
|
val favoritePaths = getFavoritePaths()
|
|
val curMedia = mediaFetcher.getFilesFrom(
|
|
path, getImagesOnly, getVideosOnly, getProperDateTaken, getProperLastModified, getProperFileSize,
|
|
favoritePaths, false, lastModifieds, dateTakens, null
|
|
)
|
|
val directory = createDirectoryFromMedia(path, curMedia, albumCovers, hiddenString, includedFolders, getProperFileSize, noMediaFolders)
|
|
updateDBDirectory(directory)
|
|
}
|
|
|
|
fun Context.getFileDateTaken(path: String): Long {
|
|
val projection = arrayOf(
|
|
Images.Media.DATE_TAKEN
|
|
)
|
|
|
|
val uri = Files.getContentUri("external")
|
|
val selection = "${Images.Media.DATA} = ?"
|
|
val selectionArgs = arrayOf(path)
|
|
|
|
try {
|
|
val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)
|
|
cursor?.use {
|
|
if (cursor.moveToFirst()) {
|
|
return cursor.getLongValue(Images.Media.DATE_TAKEN)
|
|
}
|
|
}
|
|
} catch (ignored: Exception) {
|
|
}
|
|
|
|
return 0L
|
|
}
|