Merge pull request #28 from SimpleMobileTools/master

upd
This commit is contained in:
solokot 2018-04-28 19:19:00 +03:00 committed by GitHub
commit ef90bc8fce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 1200 additions and 729 deletions

View file

@ -1,6 +1,37 @@
Changelog
==========
Version 3.8.2 *(2018-04-26)*
----------------------------
* Rewrote media caching and new file discovery for better performance
* Many OTG file handling improvements
Version 3.8.1 *(2018-04-24)*
----------------------------
* Rewrote media caching and new file discovery for better performance
* Some OTG file handling improvements
Version 3.8.0 *(2018-04-22)*
----------------------------
* Rewrote media caching for better performance
* Cache all media items, not just 80 per folder
* Some additional performance and stability improvements
Version 3.7.3 *(2018-04-15)*
----------------------------
* Show hidden folders when appropriate
Version 3.7.2 *(2018-04-14)*
----------------------------
* Fix Edit intent handled by other apps
* Hide folders containing ".nomedia" file, even if explicitly included
* Remove sorting by Date Taken until proper implementation
Version 3.7.1 *(2018-04-12)*
----------------------------

View file

@ -1,6 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 27
@ -10,8 +11,8 @@ android {
applicationId "com.simplemobiletools.gallery"
minSdkVersion 16
targetSdkVersion 27
versionCode 171
versionName "3.7.1"
versionCode 176
versionName "3.8.2"
multiDexEnabled true
setProperty("archivesBaseName", "gallery")
}
@ -46,13 +47,17 @@ ext {
}
dependencies {
implementation 'com.simplemobiletools:commons:3.18.22'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.6.0'
implementation 'com.simplemobiletools:commons:3.19.21'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.7.0'
implementation 'com.android.support:multidex:1.0.3'
implementation 'it.sephiroth.android.exif:library:1.0.1'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.12'
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
kapt "android.arch.persistence.room:compiler:1.0.0"
implementation "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
//implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.9.0'
implementation 'com.github.tibbi:subsampling-scale-image-view:v3.10.0-fork'

View file

@ -205,14 +205,6 @@
android:resource="@xml/provider_paths"/>
</provider>
<receiver
android:name=".receivers.InstallReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER"/>
</intent-filter>
</receiver>
<receiver
android:name=".receivers.RefreshMediaReceiver"
android:exported="true">

View file

@ -66,7 +66,7 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
if (intent.extras?.containsKey(REAL_FILE_PATH) == true) {
val realPath = intent.extras.getString(REAL_FILE_PATH)
uri = when {
realPath.startsWith(OTG_PATH) -> Uri.parse(realPath)
realPath.startsWith(OTG_PATH) -> uri
realPath.startsWith("file:/") -> Uri.parse(realPath)
else -> Uri.fromFile(File(realPath))
}
@ -188,6 +188,15 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} else if (saveUri.scheme == "content") {
var newPath = applicationContext.getRealPathFromURI(saveUri) ?: ""
var shouldAppendFilename = true
if (newPath.isEmpty()) {
val filename = applicationContext.getFilenameFromContentUri(saveUri) ?: ""
if (filename.isNotEmpty()) {
val path = if (intent.extras?.containsKey(REAL_FILE_PATH) == true) intent.getStringExtra(REAL_FILE_PATH).getParentPath() else internalStoragePath
newPath = "$path/$filename"
shouldAppendFilename = false
}
}
if (newPath.isEmpty()) {
newPath = "$internalStoragePath/${getCurrentFormattedDateTime()}.${saveUri.toString().getFilenameExtension()}"
shouldAppendFilename = false

View file

@ -12,14 +12,12 @@ import android.view.Menu
import android.view.MenuItem
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.Toast
import com.simplemobiletools.commons.dialogs.CreateNewFolderDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PERMISSION_READ_STORAGE
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_TAKEN
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.models.Release
@ -28,12 +26,15 @@ import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.BuildConfig
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.DirectoryAdapter
import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask
import com.simplemobiletools.gallery.databases.GalleryDataBase
import com.simplemobiletools.gallery.dialogs.ChangeSortingDialog
import com.simplemobiletools.gallery.dialogs.FilterMediaDialog
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.interfaces.DirectoryDao
import com.simplemobiletools.gallery.models.AlbumCover
import com.simplemobiletools.gallery.models.Directory
import com.simplemobiletools.gallery.models.Medium
import kotlinx.android.synthetic.main.activity_main.*
import java.io.*
@ -43,8 +44,6 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
private val LAST_MEDIA_CHECK_PERIOD = 3000L
private val NEW_APP_PACKAGE = "com.simplemobiletools.clock"
lateinit var mDirs: ArrayList<Directory>
private var mIsPickImageIntent = false
private var mIsPickVideoIntent = false
private var mIsGetImageContentIntent = false
@ -60,7 +59,6 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
private var mLatestMediaDateId = 0L
private var mLastMediaHandler = Handler()
private var mTempShowHiddenHandler = Handler()
private var mCurrAsyncTask: GetDirectoriesAsynctask? = null
private var mZoomListener: MyRecyclerView.MyZoomListener? = null
private var mStoredAnimateGifs = true
@ -88,7 +86,6 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
removeTempFolder()
directories_refresh_layout.setOnRefreshListener { getDirectories() }
mDirs = ArrayList()
storeStateVariables()
checkWhatsNewDialog()
@ -104,6 +101,10 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
config.wasNewAppShown = true
NewAppDialog(this, NEW_APP_PACKAGE, "Simple Clock")
}*/
if (!config.wasOTGHandled && hasPermission(PERMISSION_WRITE_STORAGE)) {
checkOTGInclusion()
}
}
override fun onStart() {
@ -127,7 +128,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
getRecyclerAdapter()?.updateShowMediaCount(config.showMediaCount)
}
if (mStoredScrollHorizontally != config.scrollHorizontally || mStoredShowInfoBubble != config.showInfoBubble) {
if (mStoredScrollHorizontally != config.scrollHorizontally) {
getRecyclerAdapter()?.updateScrollHorizontally(config.viewTypeFolders != VIEW_TYPE_LIST && config.scrollHorizontally)
setupScrollDirection()
}
@ -144,6 +145,8 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
directories_horizontal_fastscroller.updateBubbleColors()
directories_vertical_fastscroller.updateBubbleColors()
directories_horizontal_fastscroller.allowBubbleDisplay = config.showInfoBubble
directories_vertical_fastscroller.allowBubbleDisplay = config.showInfoBubble
directories_refresh_layout.isEnabled = config.enablePullToRefresh
invalidateOptionsMenu()
directories_empty_text_label.setTextColor(config.textColor)
@ -187,9 +190,8 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
config.temporarilyShowHidden = false
mTempShowHiddenHandler.removeCallbacksAndMessages(null)
removeTempFolder()
if (!mDirs.isEmpty()) {
mCurrAsyncTask?.stopFetching()
if (!isChangingConfigurations) {
GalleryDataBase.destroyInstance()
}
}
@ -244,13 +246,27 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
val newFolder = File(config.tempFolderPath)
if (newFolder.exists() && newFolder.isDirectory) {
if (newFolder.list()?.isEmpty() == true) {
deleteFile(newFolder.toFileDirItem(applicationContext), true)
toast(String.format(getString(R.string.deleting_folder), config.tempFolderPath), Toast.LENGTH_LONG)
tryDeleteFileDirItem(newFolder.toFileDirItem(applicationContext), true)
}
}
config.tempFolderPath = ""
}
}
private fun checkOTGInclusion() {
Thread {
if (hasOTGConnected()) {
runOnUiThread {
handleOTGPermission {
config.addIncludedFolder(OTG_PATH)
}
}
config.wasOTGHandled = true
}
}.start()
}
private fun tryLoadGallery() {
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
@ -274,22 +290,17 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
}
mIsGettingDirs = true
val dirs = getCachedDirectories()
if (dirs.isNotEmpty() && !mLoadedInitialPhotos) {
gotDirectories(dirs, true)
}
val getImagesOnly = mIsPickImageIntent || mIsGetImageContentIntent
val getVideosOnly = mIsPickVideoIntent || mIsGetVideoContentIntent
getCachedDirectories(getVideosOnly, getImagesOnly) {
if (!mLoadedInitialPhotos) {
runOnUiThread {
directories_refresh_layout.isRefreshing = true
}
mLoadedInitialPhotos = true
mCurrAsyncTask?.stopFetching()
mCurrAsyncTask = GetDirectoriesAsynctask(applicationContext, mIsPickVideoIntent || mIsGetVideoContentIntent, mIsPickImageIntent || mIsGetImageContentIntent) {
mCurrAsyncTask = null
gotDirectories(addTempFolderIfNeeded(it), false)
}
mCurrAsyncTask!!.execute()
gotDirectories(addTempFolderIfNeeded(it))
}
}
private fun showSortingDialog() {
@ -297,13 +308,14 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
if (config.directorySorting and SORT_BY_DATE_MODIFIED > 0 || config.directorySorting and SORT_BY_DATE_TAKEN > 0) {
getDirectories()
} else {
gotDirectories(mDirs, true)
gotDirectories(getCurrentlyDisplayedDirs())
}
}
}
private fun showFilterMediaDialog() {
FilterMediaDialog(this) {
mLoadedInitialPhotos = false
directories_refresh_layout.isRefreshing = true
getDirectories()
}
@ -332,8 +344,9 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
config.viewTypeFolders = it as Int
invalidateOptionsMenu()
setupLayoutManager()
val dirs = getCurrentlyDisplayedDirs()
directories_grid.adapter = null
setupAdapter()
setupAdapter(dirs)
}
}
@ -348,6 +361,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
}
private fun toggleTemporarilyShowHidden(show: Boolean) {
mLoadedInitialPhotos = false
config.temporarilyShowHidden = show
getDirectories()
invalidateOptionsMenu()
@ -359,6 +373,13 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
runOnUiThread {
refreshItems()
}
Thread {
val directoryDao = galleryDB.DirectoryDao()
folders.filter { !it.exists() }.forEach {
directoryDao.deleteDirPath(it.absolutePath)
}
}.start()
}
}
@ -418,7 +439,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
FilePickerDialog(this, internalStoragePath, false, config.shouldShowHidden) {
CreateNewFolderDialog(this, it) {
config.tempFolderPath = it
gotDirectories(addTempFolderIfNeeded(mDirs), true)
gotDirectories(addTempFolderIfNeeded(getCurrentlyDisplayedDirs()))
}
}
}
@ -550,68 +571,162 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
}
}
private fun gotDirectories(newDirs: ArrayList<Directory>, isFromCache: Boolean) {
/*if (!isFromCache) {
Thread {
checkFolderContentChange(newDirs)
}.start()
}*/
private fun gotDirectories(newDirs: ArrayList<Directory>) {
val dirs = getSortedDirectories(newDirs)
directories_refresh_layout.isRefreshing = false
mIsGettingDirs = false
var isPlaceholderVisible = dirs.isEmpty()
directories_empty_text_label.beVisibleIf(dirs.isEmpty() && !isFromCache)
directories_empty_text.beVisibleIf(dirs.isEmpty() && !isFromCache)
directories_grid.beVisibleIf(directories_empty_text_label.isGone())
runOnUiThread {
checkPlaceholderVisibility(dirs)
val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID
directories_vertical_fastscroller.beVisibleIf(directories_grid.isVisible() && !allowHorizontalScroll)
directories_horizontal_fastscroller.beVisibleIf(directories_grid.isVisible() && allowHorizontalScroll)
setupAdapter(dirs)
}
// cached folders have been loaded, recheck folders one by one starting with the first displayed
Thread {
val mediaFetcher = MediaFetcher(applicationContext)
val getImagesOnly = mIsPickImageIntent || mIsGetImageContentIntent
val getVideosOnly = mIsPickVideoIntent || mIsGetVideoContentIntent
val hiddenString = getString(R.string.hidden)
val albumCovers = config.parseAlbumCovers()
val includedFolders = config.includedFolders
val isSortingAscending = config.directorySorting and SORT_DESCENDING == 0
val mediumDao = galleryDB.MediumDao()
val directoryDao = galleryDB.DirectoryDao()
for (directory in dirs) {
val curMedia = mediaFetcher.getFilesFrom(directory.path, getImagesOnly, getVideosOnly)
val newDir = if (curMedia.isEmpty()) {
directory
} else {
createDirectoryFromMedia(directory.path, curMedia, albumCovers, hiddenString, includedFolders, isSortingAscending)
}
// we are looping through the already displayed folders looking for changes, do not do anything if nothing changed
if (directory == newDir) {
continue
}
directory.apply {
tmb = newDir.tmb
mediaCnt = newDir.mediaCnt
modified = newDir.modified
taken = newDir.taken
this@apply.size = newDir.size
types = newDir.types
}
showSortedDirs(dirs)
// update directories and media files in the local db, delete invalid items
updateDBDirectory(directory)
mediumDao.insertAll(curMedia)
getCachedMedia(directory.path, getVideosOnly, getImagesOnly) {
it.forEach {
if (!curMedia.contains(it)) {
mediumDao.deleteMediumPath(it.path)
}
}
}
}
val foldersToScan = mediaFetcher.getFoldersToScan("")
dirs.forEach {
foldersToScan.remove(it.path)
}
for (folder in foldersToScan) {
val newMedia = mediaFetcher.getFilesFrom(folder, getImagesOnly, getVideosOnly)
if (newMedia.isEmpty()) {
continue
}
if (isPlaceholderVisible) {
isPlaceholderVisible = false
runOnUiThread {
directories_empty_text_label.beGone()
directories_empty_text.beGone()
directories_grid.beVisible()
}
}
val newDir = createDirectoryFromMedia(folder, newMedia, albumCovers, hiddenString, includedFolders, isSortingAscending)
dirs.add(newDir)
showSortedDirs(dirs)
directoryDao.insert(newDir)
mediumDao.insertAll(newMedia)
}
mIsGettingDirs = false
mLoadedInitialPhotos = true
checkLastMediaChanged()
mDirs = dirs
runOnUiThread {
setupAdapter()
directories_refresh_layout.isRefreshing = false
directories_vertical_fastscroller.measureRecyclerView()
checkPlaceholderVisibility(dirs)
}
checkInvalidDirectories(dirs, directoryDao)
}.start()
}
if (!isFromCache) {
storeDirectories()
private fun checkPlaceholderVisibility(dirs: ArrayList<Directory>) {
directories_empty_text_label.beVisibleIf(dirs.isEmpty() && mLoadedInitialPhotos)
directories_empty_text.beVisibleIf(dirs.isEmpty() && mLoadedInitialPhotos)
directories_grid.beVisibleIf(directories_empty_text_label.isGone())
}
private fun showSortedDirs(dirs: ArrayList<Directory>) {
var sortedDirs = getSortedDirectories(dirs).clone() as ArrayList<Directory>
sortedDirs = sortedDirs.distinctBy { it.path.toLowerCase() } as ArrayList<Directory>
runOnUiThread {
(directories_grid.adapter as DirectoryAdapter).updateDirs(sortedDirs)
}
}
private fun checkFolderContentChange(newDirs: ArrayList<Directory>) {
newDirs.forEach {
val storedShortDirValue = config.loadFolderMediaShort(it.path)
if (storedShortDirValue != it.toString()) {
config.saveFolderMediaShort(it.path, it.toString())
if (storedShortDirValue.isNotEmpty()) {
updateStoredFolderItems(it.path)
}
private fun createDirectoryFromMedia(path: String, curMedia: ArrayList<Medium>, albumCovers: ArrayList<AlbumCover>, hiddenString: String,
includedFolders: MutableSet<String>, isSortingAscending: Boolean): Directory {
var thumbnail = curMedia.firstOrNull { getDoesFilePathExist(it.path) }?.path ?: ""
if (thumbnail.startsWith(OTG_PATH)) {
thumbnail = thumbnail.getOTGPublicPath(applicationContext)
}
albumCovers.forEach {
if (it.path == path && getDoesFilePathExist(it.tmb)) {
thumbnail = it.tmb
}
}
private fun storeDirectories() {
if (!config.temporarilyShowHidden && config.tempFolderPath.isEmpty()) {
storeDirectoryItems(mDirs)
}
val mediaTypes = curMedia.getDirMediaTypes()
val dirName = checkAppendingHidden(path, hiddenString, includedFolders)
val firstItem = curMedia.first()
val lastItem = curMedia.last()
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 = curMedia.sumByLong { it.size }
return Directory(null, path, thumbnail, dirName, curMedia.size, lastModified, dateTaken, size, getPathLocation(path), mediaTypes)
}
private fun setupAdapter() {
private fun setupAdapter(dirs: ArrayList<Directory>) {
val currAdapter = directories_grid.adapter
if (currAdapter == null) {
initZoomListener()
val fastscroller = if (config.scrollHorizontally) directories_horizontal_fastscroller else directories_vertical_fastscroller
DirectoryAdapter(this, mDirs, this, directories_grid, isPickIntent(intent) || isGetAnyContentIntent(intent), fastscroller) {
itemClicked((it as Directory).path)
DirectoryAdapter(this, dirs, this, directories_grid, isPickIntent(intent) || isGetAnyContentIntent(intent), fastscroller) {
val path = (it as Directory).path
if (path != config.tempFolderPath) {
itemClicked(path)
}
}.apply {
setupZoomListener(mZoomListener)
directories_grid.adapter = this
}
} else {
(currAdapter as DirectoryAdapter).updateDirs(mDirs)
(currAdapter as DirectoryAdapter).updateDirs(dirs)
}
setupScrollDirection()
@ -639,6 +754,31 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
}
}
private fun checkInvalidDirectories(dirs: ArrayList<Directory>, directoryDao: DirectoryDao) {
val invalidDirs = ArrayList<Directory>()
dirs.forEach {
if (!getDoesFilePathExist(it.path)) {
invalidDirs.add(it)
} else if (it.path != config.tempFolderPath) {
val children = if (it.path.startsWith(OTG_PATH)) getOTGFolderChildrenNames(it.path) else File(it.path).list()?.asList()
val hasMediaFile = children?.any { it.isImageVideoGif() } ?: false
if (!hasMediaFile) {
invalidDirs.add(it)
}
}
}
if (invalidDirs.isNotEmpty()) {
dirs.removeAll(invalidDirs)
showSortedDirs(dirs)
invalidDirs.forEach {
directoryDao.deleteDirPath(it.path)
}
}
}
private fun getCurrentlyDisplayedDirs() = getRecyclerAdapter()?.dirs ?: ArrayList()
private fun getBubbleTextItem(index: Int) = getRecyclerAdapter()?.dirs?.getOrNull(index)?.getBubbleText() ?: ""
private fun setupLatestMediaId() {
@ -678,16 +818,14 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
}
override fun recheckPinnedFolders() {
gotDirectories(movePinnedDirectoriesToFront(mDirs), true)
gotDirectories(movePinnedDirectoriesToFront(getCurrentlyDisplayedDirs()))
}
override fun updateDirectories(directories: ArrayList<Directory>, refreshList: Boolean) {
if (refreshList) {
gotDirectories(directories, true)
} else {
mDirs = directories
storeDirectories()
}
override fun updateDirectories(directories: ArrayList<Directory>) {
Thread {
storeDirectoryItems(directories)
removeInvalidDBDirectories()
}.start()
}
private fun checkWhatsNewDialog() {

View file

@ -42,6 +42,7 @@ import com.simplemobiletools.gallery.models.Medium
import kotlinx.android.synthetic.main.activity_media.*
import java.io.File
import java.io.IOException
import java.util.*
class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
private val LAST_MEDIA_CHECK_PERIOD = 3000L
@ -94,8 +95,10 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
}
storeStateVariables()
if (mShowAll)
if (mShowAll) {
supportActionBar?.setDisplayHomeAsUpEnabled(false)
}
media_empty_text.setOnClickListener {
showFilterMediaDialog()
@ -117,7 +120,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
getMediaAdapter()?.updateCropThumbnails(config.cropThumbnails)
}
if (mStoredScrollHorizontally != config.scrollHorizontally || mStoredShowInfoBubble != config.showInfoBubble) {
if (mStoredScrollHorizontally != config.scrollHorizontally) {
getMediaAdapter()?.updateScrollHorizontally(config.viewTypeFiles != VIEW_TYPE_LIST || !config.scrollHorizontally)
setupScrollDirection()
}
@ -134,6 +137,8 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
media_horizontal_fastscroller.updateBubbleColors()
media_vertical_fastscroller.updateBubbleColors()
media_horizontal_fastscroller.allowBubbleDisplay = config.showInfoBubble
media_vertical_fastscroller.allowBubbleDisplay = config.showInfoBubble
media_refresh_layout.isEnabled = config.enablePullToRefresh
tryloadGallery()
invalidateOptionsMenu()
@ -257,11 +262,13 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, object : MenuItemCompat.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
mIsSearchOpen = true
media_refresh_layout.isEnabled = false
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
mIsSearchOpen = false
media_refresh_layout.isEnabled = config.enablePullToRefresh
return true
}
})
@ -272,7 +279,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
val filtered = mMedia.filter { it.name.contains(text, true) } as ArrayList
filtered.sortBy { !it.name.startsWith(text, true) }
runOnUiThread {
(media_grid.adapter as? MediaAdapter)?.updateMedia(filtered)
getMediaAdapter()?.updateMedia(filtered)
}
}.start()
}
@ -365,12 +372,14 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
private fun showSortingDialog() {
ChangeSortingDialog(this, false, !config.showAll, mPath) {
mLoadedInitialPhotos = false
getMedia()
}
}
private fun showFilterMediaDialog() {
FilterMediaDialog(this) {
mLoadedInitialPhotos = false
media_refresh_layout.isRefreshing = true
getMedia()
}
@ -378,7 +387,6 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
private fun toggleFilenameVisibility() {
config.displayFileNames = !config.displayFileNames
if (media_grid.adapter != null)
getMediaAdapter()?.updateDisplayFilenames(config.displayFileNames)
}
@ -442,7 +450,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
private fun deleteDirectoryIfEmpty() {
val fileDirItem = FileDirItem(mPath, mPath.getFilenameFromPath())
if (config.deleteEmptyFolders && !fileDirItem.isDownloadsFolder() && fileDirItem.isDirectory && fileDirItem.getProperFileCount(applicationContext, true) == 0) {
deleteFile(fileDirItem, true)
tryDeleteFileDirItem(fileDirItem, true)
}
}
@ -452,16 +460,23 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
}
mIsGettingMedia = true
val media = getCachedMedia(mPath)
if (media.isNotEmpty() && !mLoadedInitialPhotos) {
gotMedia(media, true)
if (!mLoadedInitialPhotos) {
getCachedMedia(mPath, mIsGetVideoIntent, mIsGetImageIntent) {
if (it.isEmpty()) {
runOnUiThread {
media_refresh_layout.isRefreshing = true
}
} else {
gotMedia(it, true)
}
}
} else {
media_refresh_layout.isRefreshing = true
}
mLoadedInitialPhotos = true
mCurrAsyncTask?.stopFetching()
mCurrAsyncTask = GetMediaAsynctask(applicationContext, mPath, mIsGetVideoIntent, mIsGetImageIntent, mShowAll) {
mCurrAsyncTask = GetMediaAsynctask(applicationContext, mPath, mIsGetImageIntent, mIsGetVideoIntent, mShowAll) {
gotMedia(it)
}
mCurrAsyncTask!!.execute()
@ -470,11 +485,19 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
private fun isDirEmpty(): Boolean {
return if (mMedia.size <= 0 && config.filterMedia > 0) {
deleteDirectoryIfEmpty()
deleteDBDirectory()
finish()
true
} else
} else {
false
}
}
private fun deleteDBDirectory() {
Thread {
galleryDB.DirectoryDao().deleteDirPath(mPath)
}.start()
}
private fun tryToggleTemporarilyShowHidden() {
if (config.temporarilyShowHidden) {
@ -487,6 +510,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
}
private fun toggleTemporarilyShowHidden(show: Boolean) {
mLoadedInitialPhotos = false
config.temporarilyShowHidden = show
getMedia()
invalidateOptionsMenu()
@ -620,14 +644,21 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
}
private fun gotMedia(media: ArrayList<Medium>, isFromCache: Boolean = false) {
val mediaToInsert = media.clone() as ArrayList<Medium>
Thread {
mLatestMediaId = getLatestMediaId()
mLatestMediaDateId = getLatestMediaByDateId()
if (!isFromCache) {
galleryDB.MediumDao().insertAll(mediaToInsert)
}
}.start()
mIsGettingMedia = false
media_refresh_layout.isRefreshing = false
checkLastMediaChanged()
mMedia = media
runOnUiThread {
media_refresh_layout.isRefreshing = false
media_empty_text_label.beVisibleIf(media.isEmpty() && !isFromCache)
media_empty_text.beVisibleIf(media.isEmpty() && !isFromCache)
media_grid.beVisibleIf(media_empty_text_label.isGone())
@ -636,45 +667,37 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener {
media_vertical_fastscroller.beVisibleIf(media_grid.isVisible() && !allowHorizontalScroll)
media_horizontal_fastscroller.beVisibleIf(media_grid.isVisible() && allowHorizontalScroll)
checkLastMediaChanged()
mMedia = media
runOnUiThread {
setupAdapter()
}
if (!isFromCache) {
storeFolder()
}
}
private fun storeFolder() {
if (!config.temporarilyShowHidden) {
Thread {
storeFolderItems(mPath, mMedia)
}.start()
}
}
override fun deleteFiles(fileDirItems: ArrayList<FileDirItem>) {
override fun tryDeleteFiles(fileDirItems: ArrayList<FileDirItem>) {
val filtered = fileDirItems.filter { it.path.isImageVideoGif() } as ArrayList
deleteFiles(filtered) {
if (!it) {
toast(R.string.unknown_error_occurred)
} else if (mMedia.isEmpty()) {
return@deleteFiles
}
mMedia.removeAll { filtered.map { it.path }.contains(it.path) }
Thread {
val mediumDao = galleryDB.MediumDao()
filtered.forEach {
mediumDao.deleteMediumPath(it.path)
}
}.start()
if (mMedia.isEmpty()) {
deleteDirectoryIfEmpty()
deleteDBDirectory()
finish()
} else {
updateStoredDirectories()
}
}
}
override fun refreshItems() {
getMedia()
Handler().postDelayed({
getMedia()
}, 1000)
}
override fun selectedPaths(paths: ArrayList<String>) {

View file

@ -85,12 +85,12 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
val bundle = Bundle()
val file = File(mUri.toString())
val type = when {
file.isImageFast() -> TYPE_IMAGE
file.isVideoFast() -> TYPE_VIDEO
else -> TYPE_GIF
file.isImageFast() -> TYPE_IMAGES
file.isVideoFast() -> TYPE_VIDEOS
else -> TYPE_GIFS
}
mMedium = Medium(getFilenameFromUri(mUri!!), mUri.toString(), 0, 0, file.length(), type)
mMedium = Medium(null, getFilenameFromUri(mUri!!), mUri.toString(), mUri!!.path.getParentPath(), 0, 0, file.length(), type)
supportActionBar?.title = mMedium!!.name
bundle.putSerializable(MEDIUM, mMedium)

View file

@ -203,6 +203,11 @@ class SettingsActivity : SimpleActivity() {
settings_scroll_horizontally_holder.setOnClickListener {
settings_scroll_horizontally.toggle()
config.scrollHorizontally = settings_scroll_horizontally.isChecked
if (config.scrollHorizontally) {
config.enablePullToRefresh = false
settings_enable_pull_to_refresh.isChecked = false
}
}
}

View file

@ -45,7 +45,10 @@ import com.simplemobiletools.gallery.fragments.ViewPagerFragment
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.models.Medium
import kotlinx.android.synthetic.main.activity_medium.*
import java.io.*
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.util.*
class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, ViewPagerFragment.FragmentListener {
@ -197,7 +200,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
showSystemUI()
mDirectory = mPath.getParentPath().trimEnd('/')
mDirectory = mPath.getParentPath()
if (mDirectory.startsWith(OTG_PATH.trimEnd('/'))) {
mDirectory += "/"
}
@ -527,11 +530,10 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}
}
val newFile = File(newPath)
val tmpFile = File(filesDir, ".tmp_${newPath.getFilenameFromPath()}")
val tmpPath = "$filesDir/.tmp_${newPath.getFilenameFromPath()}"
val tmpFileDirItem = FileDirItem(tmpPath, tmpPath.getFilenameFromPath())
try {
getFileOutputStream(tmpFile.toFileDirItem(applicationContext)) {
getFileOutputStream(tmpFileDirItem) {
if (it == null) {
toast(R.string.unknown_error_occurred)
return@getFileOutputStream
@ -539,22 +541,24 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
val oldLastModified = getCurrentFile().lastModified()
if (oldPath.isJpg()) {
copyFile(getCurrentFile(), tmpFile)
saveExifRotation(ExifInterface(tmpFile.absolutePath), mRotationDegrees)
copyFile(getCurrentPath(), tmpPath)
saveExifRotation(ExifInterface(tmpPath), mRotationDegrees)
} else {
val bitmap = BitmapFactory.decodeFile(oldPath)
saveFile(tmpFile, bitmap, it as FileOutputStream)
val inputstream = getFileInputStreamSync(oldPath)
val bitmap = BitmapFactory.decodeStream(inputstream)
saveFile(tmpPath, bitmap, it as FileOutputStream)
}
if (tmpFile.length() > 0 && getDoesFilePathExist(newPath)) {
deleteFile(FileDirItem(newPath, newPath.getFilenameFromPath()))
if (getDoesFilePathExist(newPath)) {
tryDeleteFileDirItem(FileDirItem(newPath, newPath.getFilenameFromPath()))
}
copyFile(tmpFile, newFile)
copyFile(tmpPath, newPath)
scanPath(newPath)
toast(R.string.file_saved)
if (config.keepLastModified) {
newFile.setLastModified(oldLastModified)
File(newPath).setLastModified(oldLastModified)
updateLastModified(newPath, oldLastModified)
}
@ -575,13 +579,14 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} catch (e: Exception) {
showErrorToast(e)
} finally {
deleteFile(FileDirItem(tmpFile.absolutePath, tmpFile.absolutePath.getFilenameFromPath()))
tryDeleteFileDirItem(tmpFileDirItem)
}
}
@TargetApi(Build.VERSION_CODES.N)
private fun tryRotateByExif(path: String): Boolean {
return if (saveImageRotation(path, mRotationDegrees)) {
return try {
if (saveImageRotation(path, mRotationDegrees)) {
mRotationDegrees = 0
invalidateOptionsMenu()
toast(R.string.file_saved)
@ -589,27 +594,30 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} else {
false
}
} catch (e: Exception) {
showErrorToast(e)
false
}
}
private fun copyFile(source: File, destination: File) {
private fun copyFile(source: String, destination: String) {
var inputStream: InputStream? = null
var out: OutputStream? = null
try {
val fileDocument = if (isPathOnSD(destination.absolutePath)) getDocumentFile(destination.parent) else null
out = getFileOutputStreamSync(destination.absolutePath, source.getMimeType(), fileDocument)
inputStream = FileInputStream(source)
inputStream.copyTo(out!!)
out = getFileOutputStreamSync(destination, source.getMimeType())
inputStream = getFileInputStreamSync(source)
inputStream?.copyTo(out!!)
} finally {
inputStream?.close()
out?.close()
}
}
private fun saveFile(file: File, bitmap: Bitmap, out: FileOutputStream) {
private fun saveFile(path: String, bitmap: Bitmap, out: FileOutputStream) {
val matrix = Matrix()
matrix.postRotate(mRotationDegrees.toFloat())
val bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
bmp.compress(file.absolutePath.getCompressionFormat(), 90, out)
bmp.compress(path.getCompressionFormat(), 90, out)
}
private fun isShowHiddenFlagNeeded(): Boolean {
@ -736,7 +744,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private fun deleteConfirmed() {
val path = getCurrentMedia()[mPos].path
deleteFile(FileDirItem(path, path.getFilenameFromPath())) {
tryDeleteFileDirItem(FileDirItem(path, path.getFilenameFromPath())) {
refreshViewPager()
}
}
@ -752,8 +760,16 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}
private fun renameFile() {
RenameItemDialog(this, getCurrentPath()) {
getCurrentMedia()[mPos].path = it
val oldPath = getCurrentPath()
RenameItemDialog(this, oldPath) {
getCurrentMedia()[mPos].apply {
path = it
name = it.getFilenameFromPath()
}
Thread {
updateDBMediaPath(oldPath, it)
}.start()
updateActionbarTitle()
}
}
@ -814,7 +830,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private fun deleteDirectoryIfEmpty() {
val fileDirItem = FileDirItem(mDirectory, mDirectory.getFilenameFromPath(), getIsPathDirectory(mDirectory))
if (config.deleteEmptyFolders && !fileDirItem.isDownloadsFolder() && fileDirItem.isDirectory && fileDirItem.getProperFileCount(applicationContext, true) == 0) {
deleteFile(fileDirItem, true)
tryDeleteFileDirItem(fileDirItem, true)
}
scanPath(mDirectory)

View file

@ -12,6 +12,7 @@ import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.PropertiesDialog
import com.simplemobiletools.commons.dialogs.RenameItemDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView
@ -19,10 +20,7 @@ import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.dialogs.ExcludeFolderDialog
import com.simplemobiletools.gallery.dialogs.PickMediumDialog
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.TYPE_GIF
import com.simplemobiletools.gallery.helpers.TYPE_IMAGE
import com.simplemobiletools.gallery.helpers.TYPE_VIDEO
import com.simplemobiletools.gallery.helpers.VIEW_TYPE_LIST
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.models.AlbumCover
import com.simplemobiletools.gallery.models.Directory
import kotlinx.android.synthetic.main.directory_item_list.view.*
@ -155,7 +153,8 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
}
private fun renameDir() {
val sourcePath = dirs[selectedPositions.first()].path
val firstDir = dirs[selectedPositions.first()]
val sourcePath = firstDir.path
val dir = File(sourcePath)
if (activity.isAStorageRootFolder(dir.absolutePath)) {
activity.toast(R.string.rename_folder_root)
@ -164,17 +163,13 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
RenameItemDialog(activity, dir.absolutePath) {
activity.runOnUiThread {
if (selectedPositions.isEmpty()) {
return@runOnUiThread
}
dirs[selectedPositions.first()].apply {
firstDir.apply {
path = it
name = it.getFilenameFromPath()
tmb = File(it, tmb.getFilenameFromPath()).absolutePath
}
updateDirs(dirs)
listener?.updateDirectories(dirs.toList() as ArrayList, false)
listener?.updateDirectories(dirs.toList() as ArrayList)
}
}
}
@ -212,7 +207,7 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
dirs.forEach {
it.name = activity.checkAppendingHidden(it.path, hidden, includedFolders)
}
listener?.updateDirectories(dirs.toList() as ArrayList, false)
listener?.updateDirectories(dirs.toList() as ArrayList)
activity.runOnUiThread {
updateDirs(dirs)
}
@ -249,7 +244,7 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
dirs = newDirs
finishActMode()
fastScroller?.measureRecyclerView()
listener?.updateDirectories(newDirs, false)
listener?.updateDirectories(newDirs)
}
}
}
@ -284,9 +279,16 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
private fun copyMoveTo(isCopyOperation: Boolean) {
val paths = ArrayList<String>()
val showHidden = activity.config.shouldShowHidden
selectedPositions.forEach {
val dir = File(dirs[it].path)
paths.addAll(dir.listFiles().filter { !activity.getIsPathDirectory(it.absolutePath) && it.absolutePath.isImageVideoGif() }.map { it.absolutePath })
val path = dirs[it].path
if (path.startsWith(OTG_PATH)) {
paths.addAll(getOTGFilePaths(path, showHidden))
} else {
File(path).listFiles()?.filter {
!activity.getIsPathDirectory(it.absolutePath) && it.isImageVideoGif() && (showHidden || !it.name.startsWith('.'))
}?.mapTo(paths, { it.absolutePath })
}
}
val fileDirItems = paths.map { FileDirItem(it, it.getFilenameFromPath()) } as ArrayList<FileDirItem>
@ -297,6 +299,17 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
}
}
private fun getOTGFilePaths(path: String, showHidden: Boolean): ArrayList<String> {
val paths = ArrayList<String>()
activity.getOTGFolderChildren(path)?.forEach {
if (!it.isDirectory && it.name.isImageVideoGif() && (showHidden || !it.name.startsWith('.'))) {
val relativePath = it.uri.path.substringAfterLast("${activity.config.OTGPartition}:")
paths.add("$OTG_PATH$relativePath")
}
}
return paths
}
private fun askConfirmDelete() {
if (config.skipDeleteConfirmation) {
deleteFiles()
@ -416,14 +429,18 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
dir_path?.text = "${directory.path.substringBeforeLast("/")}/"
photo_cnt.text = directory.mediaCnt.toString()
val thumbnailType = when {
directory.tmb.isImageFast() -> TYPE_IMAGE
directory.tmb.isVideoFast() -> TYPE_VIDEO
else -> TYPE_GIF
directory.tmb.isImageFast() -> TYPE_IMAGES
directory.tmb.isVideoFast() -> TYPE_VIDEOS
else -> TYPE_GIFS
}
activity.loadImage(thumbnailType, directory.tmb, dir_thumbnail, scrollHorizontally, animateGifs, cropThumbnails)
dir_pin.beVisibleIf(pinnedFolders.contains(directory.path))
dir_sd_card.beVisibleIf(directory.isOnSDCard)
dir_location.beVisibleIf(directory.location != LOCAITON_INTERNAL)
if (dir_location.isVisible()) {
dir_location.setImageResource(if (directory.location == LOCATION_SD) R.drawable.ic_sd_card else R.drawable.ic_usb)
}
photo_cnt.beVisibleIf(showMediaCount)
if (isListViewType) {
@ -431,7 +448,7 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
dir_path.setTextColor(textColor)
photo_cnt.setTextColor(textColor)
dir_pin.applyColorFilter(textColor)
dir_sd_card.applyColorFilter(textColor)
dir_location.applyColorFilter(textColor)
}
}
}
@ -443,6 +460,6 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
fun recheckPinnedFolders()
fun updateDirectories(directories: ArrayList<Directory>, refreshList: Boolean)
fun updateDirectories(directories: ArrayList<Directory>)
}
}

View file

@ -148,7 +148,12 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
}
private fun renameFile() {
RenameItemDialog(activity, getCurrentPath()) {
val oldPath = getCurrentPath()
RenameItemDialog(activity, oldPath) {
Thread {
activity.updateDBMediaPath(oldPath, it)
}.start()
activity.runOnUiThread {
listener?.refreshItems()
finishActMode()
@ -158,7 +163,6 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
private fun editFile() {
activity.openEditor(getCurrentPath())
finishActMode()
}
private fun toggleFileVisibility(hide: Boolean) {
@ -188,7 +192,8 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
val fileDirItems = paths.map { FileDirItem(it, it.getFilenameFromPath()) } as ArrayList
activity.tryCopyMoveFilesTo(fileDirItems, isCopyOperation) {
config.tempFolderPath = ""
activity.applicationContext.updateStoredFolderItems(it)
activity.applicationContext.rescanFolderMedia(it)
activity.applicationContext.rescanFolderMedia(fileDirItems.first().getParentPath())
if (!isCopyOperation) {
listener?.refreshItems()
}
@ -236,9 +241,8 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
}
media.removeAll(removeMedia)
listener?.deleteFiles(fileDirItems)
listener?.tryDeleteFiles(fileDirItems)
removeSelectedItems()
updateStoredFolderItems()
}
}
@ -248,14 +252,6 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
return selectedMedia
}
private fun updateStoredFolderItems() {
Thread {
if (media.isNotEmpty()) {
activity.applicationContext.storeFolderItems(media.first().path.getParentPath().trimEnd('/'), media as ArrayList<Medium>)
}
}.start()
}
fun updateMedia(newMedia: ArrayList<Medium>) {
if (newMedia.hashCode() != currentMediaHash) {
currentMediaHash = newMedia.hashCode()
@ -331,7 +327,7 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
interface MediaOperationsListener {
fun refreshItems()
fun deleteFiles(fileDirItems: ArrayList<FileDirItem>)
fun tryDeleteFiles(fileDirItems: ArrayList<FileDirItem>)
fun selectedPaths(paths: ArrayList<String>)
}

View file

@ -1,73 +0,0 @@
package com.simplemobiletools.gallery.asynctasks
import android.content.Context
import android.os.AsyncTask
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.commons.helpers.SORT_DESCENDING
import com.simplemobiletools.commons.helpers.sumByLong
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.checkAppendingHidden
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.MediaFetcher
import com.simplemobiletools.gallery.models.Directory
import com.simplemobiletools.gallery.models.Medium
import java.io.File
class GetDirectoriesAsynctask(val context: Context, val isPickVideo: Boolean, val isPickImage: Boolean,
val callback: (dirs: ArrayList<Directory>) -> Unit) : AsyncTask<Void, Void, ArrayList<Directory>>() {
private val mediaFetcher = MediaFetcher(context)
override fun doInBackground(vararg params: Void): ArrayList<Directory> {
if (!context.hasPermission(PERMISSION_WRITE_STORAGE)) {
return ArrayList()
}
val config = context.config
val groupedMedia = mediaFetcher.getMediaByDirectories(isPickVideo, isPickImage)
val directories = ArrayList<Directory>()
val hidden = context.getString(R.string.hidden)
val albumCovers = config.parseAlbumCovers()
val hasOTG = context.hasOTGConnected() && context.config.OTGBasePath.isNotEmpty()
val includedFolders = config.includedFolders
for ((path, curMedia) in groupedMedia) {
Medium.sorting = config.getFileSorting(path)
curMedia.sort()
val firstItem = curMedia.first()
val lastItem = curMedia.last()
val parentDir = if (hasOTG && firstItem.path.startsWith(OTG_PATH)) firstItem.path.getParentPath() else File(firstItem.path).parent ?: continue
var thumbnail = curMedia.firstOrNull { context.getDoesFilePathExist(it.path) }?.path ?: ""
if (thumbnail.startsWith(OTG_PATH)) {
thumbnail = thumbnail.getOTGPublicPath(context)
}
albumCovers.forEach {
if (it.path == parentDir && context.getDoesFilePathExist(it.tmb)) {
thumbnail = it.tmb
}
}
val dirName = context.checkAppendingHidden(parentDir, hidden, includedFolders)
val lastModified = if (config.directorySorting and SORT_DESCENDING > 0) Math.max(firstItem.modified, lastItem.modified) else Math.min(firstItem.modified, lastItem.modified)
val dateTaken = if (config.directorySorting and SORT_DESCENDING > 0) Math.max(firstItem.taken, lastItem.taken) else Math.min(firstItem.taken, lastItem.taken)
val size = curMedia.sumByLong { it.size }
val directory = Directory(parentDir, thumbnail, dirName, curMedia.size, lastModified, dateTaken, size, context.isPathOnSD(parentDir))
directories.add(directory)
}
return directories
}
override fun onPostExecute(dirs: ArrayList<Directory>) {
super.onPostExecute(dirs)
callback(dirs)
}
fun stopFetching() {
mediaFetcher.shouldStop = true
cancel(true)
}
}

View file

@ -7,18 +7,18 @@ import com.simplemobiletools.gallery.helpers.MediaFetcher
import com.simplemobiletools.gallery.models.Medium
import java.util.*
class GetMediaAsynctask(val context: Context, val mPath: String, val isPickVideo: Boolean = false, val isPickImage: Boolean = false,
class GetMediaAsynctask(val context: Context, val mPath: String, val isPickImage: Boolean = false, val isPickVideo: Boolean = false,
val showAll: Boolean, val callback: (media: ArrayList<Medium>) -> Unit) :
AsyncTask<Void, Void, ArrayList<Medium>>() {
private val mediaFetcher = MediaFetcher(context)
override fun doInBackground(vararg params: Void): ArrayList<Medium> {
return if (showAll) {
val mediaMap = mediaFetcher.getMediaByDirectories(isPickVideo, isPickImage)
val foldersToScan = mediaFetcher.getFoldersToScan("")
val media = ArrayList<Medium>()
mediaMap.values.forEach {
media.addAll(it)
for (folder in foldersToScan) {
val newMedia = mediaFetcher.getFilesFrom(folder, isPickImage, isPickVideo)
media.addAll(newMedia)
}
Medium.sorting = context.config.getFileSorting("")

View file

@ -0,0 +1,37 @@
package com.simplemobiletools.gallery.databases
import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.content.Context
import com.simplemobiletools.gallery.interfaces.DirectoryDao
import com.simplemobiletools.gallery.interfaces.MediumDao
import com.simplemobiletools.gallery.models.Directory
import com.simplemobiletools.gallery.models.Medium
@Database(entities = [(Directory::class), (Medium::class)], version = 2)
abstract class GalleryDataBase : RoomDatabase() {
abstract fun DirectoryDao(): DirectoryDao
abstract fun MediumDao(): MediumDao
companion object {
private var INSTANCE: GalleryDataBase? = null
fun getInstance(context: Context): GalleryDataBase {
if (INSTANCE == null) {
synchronized(GalleryDataBase::class) {
INSTANCE = Room.databaseBuilder(context.applicationContext, GalleryDataBase::class.java, "gallery.db")
.fallbackToDestructiveMigration()
.build()
}
}
return INSTANCE!!
}
fun destroyInstance() {
INSTANCE = null
}
}
}

View file

@ -44,7 +44,6 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, val isDirectorySorti
currSorting and SORT_BY_PATH != 0 -> sortingRadio.sorting_dialog_radio_path
currSorting and SORT_BY_SIZE != 0 -> sortingRadio.sorting_dialog_radio_size
currSorting and SORT_BY_DATE_MODIFIED != 0 -> sortingRadio.sorting_dialog_radio_last_modified
currSorting and SORT_BY_DATE_TAKEN != 0 -> sortingRadio.sorting_dialog_radio_date_taken
else -> sortingRadio.sorting_dialog_radio_name
}
sortBtn.isChecked = true
@ -66,8 +65,7 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, val isDirectorySorti
R.id.sorting_dialog_radio_name -> SORT_BY_NAME
R.id.sorting_dialog_radio_path -> SORT_BY_PATH
R.id.sorting_dialog_radio_size -> SORT_BY_SIZE
R.id.sorting_dialog_radio_last_modified -> SORT_BY_DATE_MODIFIED
else -> SORT_BY_DATE_TAKEN
else -> SORT_BY_DATE_MODIFIED
}
if (view.sorting_dialog_radio_order.checkedRadioButtonId == R.id.sorting_dialog_radio_descending) {

View file

@ -5,9 +5,9 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.GIFS
import com.simplemobiletools.gallery.helpers.IMAGES
import com.simplemobiletools.gallery.helpers.VIDEOS
import com.simplemobiletools.gallery.helpers.TYPE_GIFS
import com.simplemobiletools.gallery.helpers.TYPE_IMAGES
import com.simplemobiletools.gallery.helpers.TYPE_VIDEOS
import kotlinx.android.synthetic.main.dialog_filter_media.view.*
class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) {
@ -16,9 +16,9 @@ class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result:
init {
val filterMedia = activity.config.filterMedia
view.apply {
filter_media_images.isChecked = filterMedia and IMAGES != 0
filter_media_videos.isChecked = filterMedia and VIDEOS != 0
filter_media_gifs.isChecked = filterMedia and GIFS != 0
filter_media_images.isChecked = filterMedia and TYPE_IMAGES != 0
filter_media_videos.isChecked = filterMedia and TYPE_VIDEOS != 0
filter_media_gifs.isChecked = filterMedia and TYPE_GIFS != 0
}
AlertDialog.Builder(activity)
@ -32,11 +32,11 @@ class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result:
private fun dialogConfirmed() {
var result = 0
if (view.filter_media_images.isChecked)
result += IMAGES
result += TYPE_IMAGES
if (view.filter_media_videos.isChecked)
result += VIDEOS
result += TYPE_VIDEOS
if (view.filter_media_gifs.isChecked)
result += GIFS
result += TYPE_GIFS
activity.config.filterMedia = result
callback(result)

View file

@ -11,7 +11,6 @@ import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.DirectoryAdapter
import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask
import com.simplemobiletools.gallery.extensions.addTempFolderIfNeeded
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.extensions.getCachedDirectories
@ -40,14 +39,13 @@ class PickDirectoryDialog(val activity: BaseSimpleActivity, val sourcePath: Stri
activity.setupDialogStuff(view, this, R.string.select_destination)
}
val dirs = activity.getCachedDirectories()
if (dirs.isNotEmpty()) {
gotDirectories(activity.addTempFolderIfNeeded(dirs))
}
GetDirectoriesAsynctask(activity, false, false) {
activity.getCachedDirectories {
if (it.isNotEmpty()) {
activity.runOnUiThread {
gotDirectories(activity.addTempFolderIfNeeded(it))
}.execute()
}
}
}
}
private fun showOtherFolder() {

View file

@ -36,12 +36,16 @@ class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val c
activity.setupDialogStuff(view, this, R.string.select_photo)
}
val media = activity.getCachedMedia(path).filter { !it.isVideo() } as ArrayList
activity.getCachedMedia(path) {
val media = it.filter { !it.isVideo() } as ArrayList
if (media.isNotEmpty()) {
activity.runOnUiThread {
gotMedia(media)
}
}
}
GetMediaAsynctask(activity, path, false, true, false) {
GetMediaAsynctask(activity, path, true, false, false) {
gotMedia(it)
}.execute()
}

View file

@ -7,15 +7,14 @@ import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.gallery.R
import kotlinx.android.synthetic.main.dialog_save_as.view.*
import java.io.File
class SaveAsDialog(val activity: BaseSimpleActivity, val path: String, val appendFilename: Boolean, val callback: (savePath: String) -> Unit) {
init {
var realPath = path.getParentPath().trimEnd('/')
var realPath = path.getParentPath()
val view = activity.layoutInflater.inflate(R.layout.dialog_save_as, null).apply {
save_as_path.text = activity.humanizePath(realPath)
save_as_path.text = "${activity.humanizePath(realPath).trimEnd('/')}/"
val fullName = path.getFilenameFromPath()
val dotAt = fullName.lastIndexOf(".")
@ -60,20 +59,21 @@ class SaveAsDialog(val activity: BaseSimpleActivity, val path: String, val appen
return@setOnClickListener
}
val newFile = File(realPath, "$filename.$extension")
if (!newFile.name.isAValidFilename()) {
val newFilename = "$filename.$extension"
val newPath = "${realPath.trimEnd('/')}/$newFilename"
if (!newFilename.isAValidFilename()) {
activity.toast(R.string.filename_invalid_characters)
return@setOnClickListener
}
if (newFile.exists()) {
val title = String.format(activity.getString(R.string.file_already_exists_overwrite), newFile.name)
if (activity.getDoesFilePathExist(newPath)) {
val title = String.format(activity.getString(R.string.file_already_exists_overwrite), newFilename)
ConfirmationDialog(activity, title) {
callback(newFile.absolutePath)
callback(newPath)
dismiss()
}
} else {
callback(newFile.absolutePath)
callback(newPath)
dismiss()
}
}

View file

@ -5,14 +5,6 @@ import android.content.Intent
import android.provider.MediaStore
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
@ -23,13 +15,7 @@ import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.SimpleActivity
import com.simplemobiletools.gallery.dialogs.PickDirectoryDialog
import com.simplemobiletools.gallery.helpers.NOMEDIA
import com.simplemobiletools.gallery.helpers.TYPE_GIF
import com.simplemobiletools.gallery.helpers.TYPE_IMAGE
import com.simplemobiletools.gallery.helpers.TYPE_VIDEO
import com.simplemobiletools.gallery.models.Directory
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.views.MySquareImageView
import pl.droidsonroids.gif.GifDrawable
import java.io.File
import java.util.*
@ -148,7 +134,7 @@ fun BaseSimpleActivity.removeNoMedia(path: String, callback: (() -> Unit)? = nul
return
}
deleteFile(file.toFileDirItem(applicationContext)) {
tryDeleteFileDirItem(file.toFileDirItem(applicationContext)) {
callback?.invoke()
}
}
@ -162,36 +148,12 @@ fun BaseSimpleActivity.toggleFileVisibility(oldPath: String, hide: Boolean, call
filename.substring(1, filename.length)
}
val newPath = "$path$filename"
val newPath = "$path/$filename"
renameFile(oldPath, newPath) {
callback?.invoke(newPath)
}
}
fun Activity.loadImage(type: Int, path: String, target: MySquareImageView, horizontalScroll: Boolean, animateGifs: Boolean, cropThumbnails: Boolean) {
target.isHorizontalScrolling = horizontalScroll
if (type == TYPE_IMAGE || type == TYPE_VIDEO) {
if (type == TYPE_IMAGE && path.isPng()) {
loadPng(path, target, cropThumbnails)
} else {
loadJpg(path, target, cropThumbnails)
}
} else if (type == TYPE_GIF) {
try {
val gifDrawable = GifDrawable(path)
target.setImageDrawable(gifDrawable)
if (animateGifs) {
gifDrawable.start()
} else {
gifDrawable.stop()
}
target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER
} catch (e: Exception) {
loadJpg(path, target, cropThumbnails)
} catch (e: OutOfMemoryError) {
loadJpg(path, target, cropThumbnails)
}
Thread {
updateDBMediaPath(oldPath, newPath)
}.start()
}
}
@ -207,49 +169,12 @@ fun BaseSimpleActivity.tryCopyMoveFilesTo(fileDirItems: ArrayList<FileDirItem>,
}
}
fun BaseSimpleActivity.addTempFolderIfNeeded(dirs: ArrayList<Directory>): ArrayList<Directory> {
val directories = ArrayList<Directory>()
val tempFolderPath = config.tempFolderPath
if (tempFolderPath.isNotEmpty()) {
val newFolder = Directory(tempFolderPath, "", tempFolderPath.getFilenameFromPath(), 0, 0, 0, 0L, isPathOnSD(tempFolderPath))
directories.add(newFolder)
fun BaseSimpleActivity.tryDeleteFileDirItem(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
deleteFile(fileDirItem, allowDeleteFolder) {
callback?.invoke(it)
Thread {
galleryDB.MediumDao().deleteMediumPath(fileDirItem.path)
}.start()
}
directories.addAll(dirs)
return directories
}
fun Activity.loadPng(path: String, target: MySquareImageView, cropThumbnails: Boolean) {
val options = RequestOptions()
.signature(path.getFileSignature())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.format(DecodeFormat.PREFER_ARGB_8888)
val builder = Glide.with(applicationContext)
.asBitmap()
.load(path)
if (cropThumbnails) options.centerCrop() else options.fitCenter()
builder.apply(options).into(target)
}
fun Activity.loadJpg(path: String, target: MySquareImageView, cropThumbnails: Boolean) {
val options = RequestOptions()
.signature(path.getFileSignature())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
val builder = Glide.with(applicationContext)
.load(path)
if (cropThumbnails) options.centerCrop() else options.fitCenter()
builder.apply(options).transition(DrawableTransitionOptions.withCrossFade()).into(target)
}
fun Activity.getCachedDirectories(): ArrayList<Directory> {
val token = object : TypeToken<List<Directory>>() {}.type
return Gson().fromJson<ArrayList<Directory>>(config.directories, token) ?: ArrayList(1)
}
fun Activity.getCachedMedia(path: String): ArrayList<Medium> {
val token = object : TypeToken<List<Medium>>() {}.type
return Gson().fromJson<ArrayList<Medium>>(config.loadFolderMedia(path), token) ?: ArrayList(1)
}

View file

@ -0,0 +1,23 @@
package com.simplemobiletools.gallery.extensions
import com.simplemobiletools.gallery.helpers.TYPE_GIFS
import com.simplemobiletools.gallery.helpers.TYPE_IMAGES
import com.simplemobiletools.gallery.helpers.TYPE_VIDEOS
import com.simplemobiletools.gallery.models.Medium
fun ArrayList<Medium>.getDirMediaTypes(): Int {
var types = 0
if (any { it.isImage() }) {
types += TYPE_IMAGES
}
if (any { it.isVideo() }) {
types += TYPE_VIDEOS
}
if (any { it.isGif() }) {
types += TYPE_GIFS
}
return types
}

View file

@ -9,19 +9,24 @@ import android.media.AudioManager
import android.os.Build
import android.provider.MediaStore
import android.view.WindowManager
import com.google.gson.Gson
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.SettingsActivity
import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.helpers.Config
import com.simplemobiletools.gallery.helpers.NOMEDIA
import com.simplemobiletools.gallery.helpers.SAVE_DIRS_CNT
import com.simplemobiletools.gallery.helpers.SAVE_MEDIA_CNT
import com.simplemobiletools.gallery.databases.GalleryDataBase
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.interfaces.DirectoryDao
import com.simplemobiletools.gallery.models.Directory
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.views.MySquareImageView
import pl.droidsonroids.gif.GifDrawable
import java.io.File
val Context.portrait get() = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
@ -65,17 +70,27 @@ fun Context.launchSettings() {
val Context.config: Config get() = Config.newInstance(applicationContext)
val Context.galleryDB: GalleryDataBase get() = GalleryDataBase.getInstance(applicationContext)
fun Context.movePinnedDirectoriesToFront(dirs: ArrayList<Directory>): ArrayList<Directory> {
val foundFolders = ArrayList<Directory>()
val pinnedFolders = config.pinnedFolders
dirs.forEach {
if (pinnedFolders.contains(it.path))
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)
}
}
return dirs
}
@ -98,7 +113,6 @@ fun Context.getNoMediaFolders(callback: (folders: ArrayList<String>) -> Unit) {
val sortOrder = "${MediaStore.Files.FileColumns.DATE_MODIFIED} DESC"
var cursor: Cursor? = null
try {
cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
if (cursor?.moveToFirst() == true) {
@ -118,50 +132,35 @@ fun Context.getNoMediaFolders(callback: (folders: ArrayList<String>) -> Unit) {
}.start()
}
fun Context.isPathInMediaStore(path: String): Boolean {
if (path.startsWith(OTG_PATH)) {
return false
fun Context.rescanFolderMedia(path: String) {
Thread {
rescanFolderMediaSync(path)
}.start()
}
val projection = arrayOf(MediaStore.Images.Media.DATE_MODIFIED)
val uri = MediaStore.Files.getContentUri("external")
val selection = "${MediaStore.MediaColumns.DATA} = ?"
val selectionArgs = arrayOf(path)
val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)
fun Context.rescanFolderMediaSync(path: String) {
getCachedMedia(path) {
val cached = it
GetMediaAsynctask(applicationContext, path, false, false, false) {
Thread {
val newMedia = it
val mediumDao = galleryDB.MediumDao()
mediumDao.insertAll(newMedia)
cursor?.use {
return cursor.moveToFirst()
cached.forEach {
if (!newMedia.contains(it)) {
mediumDao.deleteMediumPath(it.path)
}
return false
}
fun Context.updateStoredFolderItems(path: String) {
GetMediaAsynctask(this, path, false, false, false) {
storeFolderItems(path, it)
}.start()
}.execute()
}
fun Context.storeFolderItems(path: String, items: ArrayList<Medium>) {
try {
val subList = items.subList(0, Math.min(SAVE_MEDIA_CNT, items.size))
val json = Gson().toJson(subList)
config.saveFolderMedia(path, json)
} catch (ignored: Exception) {
} catch (ignored: OutOfMemoryError) {
}
}
fun Context.updateStoredDirectories() {
GetDirectoriesAsynctask(this, false, false) {
if (!config.temporarilyShowHidden) {
storeDirectoryItems(it)
}
}.execute()
}
fun Context.storeDirectoryItems(items: ArrayList<Directory>) {
val subList = items.subList(0, Math.min(SAVE_DIRS_CNT, items.size))
config.directories = Gson().toJson(subList)
Thread {
galleryDB.DirectoryDao().insertAll(items)
}.start()
}
fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders: MutableSet<String>): String {
@ -184,3 +183,150 @@ fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders:
dirName
}
}
fun Context.loadImage(type: Int, path: String, target: MySquareImageView, horizontalScroll: Boolean, animateGifs: Boolean, cropThumbnails: Boolean) {
target.isHorizontalScrolling = horizontalScroll
if (type == TYPE_IMAGES || type == TYPE_VIDEOS) {
if (type == TYPE_IMAGES && path.isPng()) {
loadPng(path, target, cropThumbnails)
} else {
loadJpg(path, target, cropThumbnails)
}
} else if (type == TYPE_GIFS) {
try {
val gifDrawable = GifDrawable(path)
target.setImageDrawable(gifDrawable)
if (animateGifs) {
gifDrawable.start()
} else {
gifDrawable.stop()
}
target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER
} catch (e: Exception) {
loadJpg(path, target, cropThumbnails)
} catch (e: OutOfMemoryError) {
loadJpg(path, target, cropThumbnails)
}
}
}
fun Context.addTempFolderIfNeeded(dirs: ArrayList<Directory>): ArrayList<Directory> {
val directories = ArrayList<Directory>()
val tempFolderPath = config.tempFolderPath
if (tempFolderPath.isNotEmpty()) {
val newFolder = Directory(null, tempFolderPath, "", tempFolderPath.getFilenameFromPath(), 0, 0, 0, 0L, getPathLocation(tempFolderPath), 0)
directories.add(newFolder)
}
directories.addAll(dirs)
return directories
}
fun Context.getPathLocation(path: String): Int {
return when {
isPathOnSD(path) -> LOCATION_SD
path.startsWith(OTG_PATH) -> LOCATION_OTG
else -> LOCAITON_INTERNAL
}
}
fun Context.loadPng(path: String, target: MySquareImageView, cropThumbnails: Boolean) {
val options = RequestOptions()
.signature(path.getFileSignature())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.format(DecodeFormat.PREFER_ARGB_8888)
val builder = Glide.with(applicationContext)
.asBitmap()
.load(path)
if (cropThumbnails) options.centerCrop() else options.fitCenter()
builder.apply(options).into(target)
}
fun Context.loadJpg(path: String, target: MySquareImageView, cropThumbnails: Boolean) {
val options = RequestOptions()
.signature(path.getFileSignature())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
val builder = Glide.with(applicationContext)
.load(path)
if (cropThumbnails) options.centerCrop() else options.fitCenter()
builder.apply(options).transition(DrawableTransitionOptions.withCrossFade()).into(target)
}
fun Context.getCachedDirectories(getVideosOnly: Boolean = false, getImagesOnly: Boolean = false, callback: (ArrayList<Directory>) -> Unit) {
Thread {
val directoryDao = galleryDB.DirectoryDao()
val directories = directoryDao.getAll() as ArrayList<Directory>
val shouldShowHidden = config.shouldShowHidden
val excludedPaths = config.excludedFolders
val includedPaths = config.includedFolders
var filteredDirectories = directories.filter { it.path.shouldFolderBeVisible(excludedPaths, includedPaths, shouldShowHidden) } 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)
}
}) as ArrayList<Directory>
callback(filteredDirectories.distinctBy { it.path.toLowerCase() } as ArrayList<Directory>)
removeInvalidDBDirectories(directories, directoryDao)
}.start()
}
fun Context.getCachedMedia(path: String, getVideosOnly: Boolean = false, getImagesOnly: Boolean = false, callback: (ArrayList<Medium>) -> Unit) {
Thread {
val mediumDao = galleryDB.MediumDao()
val media = (if (path == "/") mediumDao.getAll() else mediumDao.getMediaFromPath(path)) as ArrayList<Medium>
val shouldShowHidden = config.shouldShowHidden
var filteredMedia = media
if (!shouldShowHidden) {
filteredMedia = media.filter { !it.name.startsWith('.') } as ArrayList<Medium>
}
val filterMedia = config.filterMedia
filteredMedia = (when {
getVideosOnly -> filteredMedia.filter { it.type == TYPE_VIDEOS }
getImagesOnly -> filteredMedia.filter { it.type == TYPE_IMAGES }
else -> filteredMedia.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)
}
}) as ArrayList<Medium>
callback(filteredMedia)
media.filter { !getDoesFilePathExist(it.path) }.forEach {
mediumDao.deleteMediumPath(it.path)
}
}.start()
}
fun Context.removeInvalidDBDirectories(dirs: ArrayList<Directory>? = null, directoryDao: DirectoryDao = galleryDB.DirectoryDao()) {
val dirsToCheck = dirs ?: directoryDao.getAll()
dirsToCheck.filter { !getDoesFilePathExist(it.path) && it.path != config.tempFolderPath }.forEach {
directoryDao.deleteDirPath(it.path)
}
}
fun Context.updateDBMediaPath(oldPath: String, newPath: String) {
val newFilename = newPath.getFilenameFromPath()
val newParentPath = newPath.getParentPath()
galleryDB.MediumDao().updateMedium(oldPath, newParentPath, newFilename, newPath)
}
fun Context.updateDBDirectory(directory: Directory) {
galleryDB.DirectoryDao().updateDirectory(directory.path, directory.tmb, directory.mediaCnt, directory.modified, directory.taken, directory.size, directory.types)
}
fun Context.getOTGFolderChildren(path: String) = getDocumentFile(path)?.listFiles()
fun Context.getOTGFolderChildrenNames(path: String) = getOTGFolderChildren(path)?.map { it.name }?.toList()

View file

@ -5,9 +5,30 @@ import java.io.File
fun String.getFileSignature(): ObjectKey {
val file = File(this)
return ObjectKey("${file.name}${file.lastModified()}")
return ObjectKey("${file.absolutePath}${file.lastModified()}")
}
fun String.isThisOrParentIncluded(includedPaths: MutableSet<String>) = includedPaths.any { startsWith(it, true) }
fun String.isThisOrParentExcluded(excludedPaths: MutableSet<String>) = excludedPaths.any { startsWith(it, true) }
fun String.shouldFolderBeVisible(excludedPaths: MutableSet<String>, includedPaths: MutableSet<String>, showHidden: Boolean): Boolean {
val file = File(this)
return if (isEmpty()) {
false
} else if (!showHidden && file.containsNoMedia()) {
false
} else if (isThisOrParentIncluded(includedPaths)) {
true
} else if (isThisOrParentExcluded(excludedPaths)) {
false
} else if (!showHidden && file.isDirectory && file.canonicalFile == file.absoluteFile) {
var containsNoMediaOrDot = file.containsNoMedia() || contains("/.")
if (!containsNoMediaOrDot) {
containsNoMediaOrDot = file.doesThisOrParentHaveNoMedia()
}
!containsNoMediaOrDot
} else {
true
}
}

View file

@ -275,7 +275,7 @@ class PhotoFragment : ViewPagerFragment() {
} else {
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transform(GlideRotateTransformation(context!!, degrees))
.transform(GlideRotateTransformation(degrees))
Glide.with(this)
.asBitmap()

View file

@ -6,6 +6,7 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.helpers.BaseConfig
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_TAKEN
import com.simplemobiletools.commons.helpers.SORT_DESCENDING
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.models.AlbumCover
@ -17,7 +18,13 @@ class Config(context: Context) : BaseConfig(context) {
}
var directorySorting: Int
get() = prefs.getInt(DIRECTORY_SORT_ORDER, SORT_BY_DATE_MODIFIED or SORT_DESCENDING)
get(): Int {
var sort = prefs.getInt(DIRECTORY_SORT_ORDER, SORT_BY_DATE_MODIFIED or SORT_DESCENDING)
if (sort and SORT_BY_DATE_TAKEN != 0) {
sort = sort - SORT_BY_DATE_TAKEN + SORT_BY_DATE_MODIFIED
}
return sort
}
set(order) = prefs.edit().putInt(DIRECTORY_SORT_ORDER, order).apply()
fun saveFileSorting(path: String, value: Int) {
@ -28,7 +35,13 @@ class Config(context: Context) : BaseConfig(context) {
}
}
fun getFileSorting(path: String) = prefs.getInt(SORT_FOLDER_PREFIX + path.toLowerCase(), sorting)
fun getFileSorting(path: String): Int {
var sort = prefs.getInt(SORT_FOLDER_PREFIX + path.toLowerCase(), sorting)
if (sort and SORT_BY_DATE_TAKEN != 0) {
sort = sort - SORT_BY_DATE_TAKEN + SORT_BY_DATE_MODIFIED
}
return sort
}
fun removeFileSorting(path: String) {
prefs.edit().remove(SORT_FOLDER_PREFIX + path.toLowerCase()).apply()
@ -118,18 +131,6 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getStringSet(INCLUDED_FOLDERS, HashSet<String>())
set(includedFolders) = prefs.edit().remove(INCLUDED_FOLDERS).putStringSet(INCLUDED_FOLDERS, includedFolders).apply()
fun saveFolderMedia(path: String, json: String) {
prefs.edit().putString(SAVE_FOLDER_PREFIX + path, json).apply()
}
fun loadFolderMedia(path: String) = prefs.getString(SAVE_FOLDER_PREFIX + path, "")
fun saveFolderMediaShort(path: String, value: String) {
prefs.edit().putString(SAVE_FOLDER_SHORT_PREFIX + path, value).apply()
}
fun loadFolderMediaShort(path: String) = prefs.getString(SAVE_FOLDER_SHORT_PREFIX + path, "")
var autoplayVideos: Boolean
get() = prefs.getBoolean(AUTOPLAY_VIDEOS, false)
set(autoplay) = prefs.edit().putBoolean(AUTOPLAY_VIDEOS, autoplay).apply()
@ -163,7 +164,7 @@ class Config(context: Context) : BaseConfig(context) {
set(darkBackground) = prefs.edit().putBoolean(DARK_BACKGROUND, darkBackground).apply()
var filterMedia: Int
get() = prefs.getInt(FILTER_MEDIA, IMAGES or VIDEOS or GIFS)
get() = prefs.getInt(FILTER_MEDIA, TYPE_IMAGES or TYPE_VIDEOS or TYPE_GIFS)
set(filterMedia) = prefs.edit().putInt(FILTER_MEDIA, filterMedia).apply()
var dirColumnCnt: Int
@ -226,10 +227,6 @@ class Config(context: Context) : BaseConfig(context) {
private fun getDefaultMediaColumnCount() = context.resources.getInteger(if (scrollHorizontally) R.integer.media_columns_horizontal_scroll
else R.integer.media_columns_vertical_scroll)
var directories: String
get() = prefs.getString(DIRECTORIES, "")
set(directories) = prefs.edit().putString(DIRECTORIES, directories).apply()
var albumCovers: String
get() = prefs.getString(ALBUM_COVERS, "")
set(albumCovers) = prefs.edit().putString(ALBUM_COVERS, albumCovers).apply()
@ -328,6 +325,10 @@ class Config(context: Context) : BaseConfig(context) {
set(wasNewAppShown) = prefs.edit().putBoolean(WAS_NEW_APP_SHOWN, wasNewAppShown).apply()
var lastFilepickerPath: String
get() = prefs.getString(TEMP_FOLDER_PATH, "")
set(tempFolderPath) = prefs.edit().putString(TEMP_FOLDER_PATH, tempFolderPath).apply()
get() = prefs.getString(LAST_FILEPICKER_PATH, "")
set(lastFilepickerPath) = prefs.edit().putString(LAST_FILEPICKER_PATH, lastFilepickerPath).apply()
var wasOTGHandled: Boolean
get() = prefs.getBoolean(WAS_OTG_HANDLED, false)
set(wasOTGHandled) = prefs.edit().putBoolean(WAS_OTG_HANDLED, wasOTGHandled).apply()
}

View file

@ -25,8 +25,6 @@ const val MEDIA_LANDSCAPE_COLUMN_CNT = "media_landscape_column_cnt"
const val MEDIA_HORIZONTAL_COLUMN_CNT = "media_horizontal_column_cnt"
const val MEDIA_LANDSCAPE_HORIZONTAL_COLUMN_CNT = "media_landscape_horizontal_column_cnt"
const val SHOW_ALL = "show_all" // display images and videos from all folders together
const val SAVE_FOLDER_PREFIX = "folder2_"
const val SAVE_FOLDER_SHORT_PREFIX = "folder_short_"
const val HIDE_FOLDER_TOOLTIP_SHOWN = "hide_folder_tooltip_shown"
const val EXCLUDED_FOLDERS = "excluded_folders"
const val INCLUDED_FOLDERS = "included_folders"
@ -49,6 +47,7 @@ const val REPLACE_ZOOMABLE_IMAGES = "replace_zoomable_images"
const val DO_EXTRA_CHECK = "do_extra_check"
const val WAS_NEW_APP_SHOWN = "was_new_app_shown_clock"
const val LAST_FILEPICKER_PATH = "last_filepicker_path"
const val WAS_OTG_HANDLED = "was_otg_handled"
// slideshow
const val SLIDESHOW_INTERVAL = "slideshow_interval"
@ -67,8 +66,6 @@ const val MAX_COLUMN_COUNT = 20
const val SHOW_TEMP_HIDDEN_DURATION = 600000L
const val CLICK_MAX_DURATION = 150
const val DRAG_THRESHOLD = 8
const val SAVE_DIRS_CNT = 60
const val SAVE_MEDIA_CNT = 80
const val DIRECTORY = "directory"
const val MEDIUM = "medium"
@ -77,7 +74,6 @@ const val GET_IMAGE_INTENT = "get_image_intent"
const val GET_VIDEO_INTENT = "get_video_intent"
const val GET_ANY_INTENT = "get_any_intent"
const val SET_WALLPAPER_INTENT = "set_wallpaper_intent"
const val DIRECTORIES = "directories2"
const val IS_VIEW_INTENT = "is_view_intent"
const val PICKED_PATHS = "picked_paths"
@ -86,11 +82,6 @@ const val ROTATE_BY_SYSTEM_SETTING = 0
const val ROTATE_BY_DEVICE_ROTATION = 1
const val ROTATE_BY_ASPECT_RATIO = 2
// filter media
const val IMAGES = 1
const val VIDEOS = 2
const val GIFS = 4
// view types
const val VIEW_TYPE_GRID = 1
const val VIEW_TYPE_LIST = 2
@ -109,6 +100,10 @@ const val EXT_ARTIST = 512
const val EXT_ALBUM = 1024
// media types
const val TYPE_IMAGE = 1
const val TYPE_VIDEO = 2
const val TYPE_GIF = 3
const val TYPE_IMAGES = 1
const val TYPE_VIDEOS = 2
const val TYPE_GIFS = 4
const val LOCAITON_INTERNAL = 1
const val LOCATION_SD = 2
const val LOCATION_OTG = 3

View file

@ -1,70 +0,0 @@
package com.simplemobiletools.gallery.helpers
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.media.ExifInterface
import android.net.Uri
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder
import com.simplemobiletools.gallery.activities.ViewPagerActivity
import com.simplemobiletools.gallery.extensions.getFileSignature
class GlideDecoder : ImageDecoder {
override fun decode(context: Context, uri: Uri): Bitmap {
val exif = android.media.ExifInterface(uri.path)
val orientation = exif.getAttributeInt(android.media.ExifInterface.TAG_ORIENTATION, android.media.ExifInterface.ORIENTATION_NORMAL)
val targetWidth = if (ViewPagerActivity.screenWidth == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenWidth
val targetHeight = if (ViewPagerActivity.screenHeight == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenHeight
ViewPagerActivity.wasDecodedByGlide = true
val options = RequestOptions()
.signature(uri.path.getFileSignature())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.override(targetWidth, targetHeight)
val degrees = getRotationDegrees(orientation)
if (degrees != 0) {
options.transform(GlideRotateTransformation(context, getRotationDegrees(orientation)))
}
val drawable = Glide.with(context)
.load(uri)
.apply(options)
.submit()
.get()
return drawableToBitmap(drawable)
}
private fun drawableToBitmap(drawable: Drawable): Bitmap {
if (drawable is BitmapDrawable && drawable.bitmap != null) {
return drawable.bitmap
}
val bitmap = if (drawable.intrinsicWidth <= 0 || drawable.intrinsicHeight <= 0) {
Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
} else {
Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
}
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
// rotating backwards intentionally, as SubsamplingScaleImageView will rotate it properly at displaying
private fun getRotationDegrees(orientation: Int) = when (orientation) {
ExifInterface.ORIENTATION_ROTATE_270 -> 90
ExifInterface.ORIENTATION_ROTATE_180 -> 180
ExifInterface.ORIENTATION_ROTATE_90 -> 270
else -> 0
}
}

View file

@ -1,13 +1,12 @@
package com.simplemobiletools.gallery.helpers
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Matrix
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import java.security.MessageDigest
class GlideRotateTransformation(context: Context, val rotateRotationAngle: Int) : BitmapTransformation(context) {
class GlideRotateTransformation(val rotateRotationAngle: Int) : BitmapTransformation() {
override fun transform(pool: BitmapPool, bitmap: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
if (rotateRotationAngle % 360 == 0)
return bitmap

View file

@ -8,47 +8,50 @@ import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.helpers.photoExtensions
import com.simplemobiletools.commons.helpers.videoExtensions
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.extensions.getOTGFolderChildren
import com.simplemobiletools.gallery.extensions.shouldFolderBeVisible
import com.simplemobiletools.gallery.models.Medium
import java.io.File
import java.util.LinkedHashMap
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
import kotlin.collections.set
class MediaFetcher(val context: Context) {
var shouldStop = false
fun getMediaByDirectories(isPickVideo: Boolean, isPickImage: Boolean): HashMap<String, ArrayList<Medium>> {
val media = getFilesFrom("", isPickImage, isPickVideo)
return groupDirectories(media)
}
fun getFilesFrom(curPath: String, isPickImage: Boolean, isPickVideo: Boolean): ArrayList<Medium> {
val filterMedia = context.config.filterMedia
if (filterMedia == 0) {
return ArrayList()
}
if (curPath.startsWith(OTG_PATH)) {
val curMedia = ArrayList<Medium>()
getMediaOnOTG(curPath, curMedia, isPickImage, isPickVideo, filterMedia)
return curMedia
if (curPath.startsWith(OTG_PATH)) {
val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia)
curMedia.addAll(newMedia)
} else {
val newMedia = fetchFolderContent(curPath, isPickImage, isPickVideo, filterMedia)
curMedia.addAll(newMedia)
}
Medium.sorting = context.config.getFileSorting(curPath)
curMedia.sort()
return curMedia
}
fun getFoldersToScan(path: String): ArrayList<String> {
val filterMedia = context.config.filterMedia
val projection = arrayOf(MediaStore.Images.Media.DATA)
val uri = MediaStore.Files.getContentUri("external")
val selection = "${getSelectionQuery(curPath, filterMedia)} ${MediaStore.Images.ImageColumns.BUCKET_ID} IS NOT NULL) GROUP BY (${MediaStore.Images.ImageColumns.BUCKET_ID}"
val selectionArgs = getSelectionArgsQuery(curPath, filterMedia).toTypedArray()
val selection = "${getSelectionQuery(path, filterMedia)} ${MediaStore.Images.ImageColumns.BUCKET_ID} IS NOT NULL) GROUP BY (${MediaStore.Images.ImageColumns.BUCKET_ID}"
val selectionArgs = getSelectionArgsQuery(path, filterMedia).toTypedArray()
return try {
val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
parseCursor(context, cursor, isPickImage, isPickVideo, curPath, filterMedia)
parseCursor(cursor, path)
} catch (e: Exception) {
ArrayList()
}
}
}
private fun getSelectionQuery(path: String, filterMedia: Int): String {
val query = StringBuilder()
@ -57,19 +60,19 @@ class MediaFetcher(val context: Context) {
}
query.append("(")
if (filterMedia and IMAGES != 0) {
if (filterMedia and TYPE_IMAGES != 0) {
photoExtensions.forEach {
query.append("${MediaStore.Images.Media.DATA} LIKE ? OR ")
}
}
if (filterMedia and VIDEOS != 0) {
if (filterMedia and TYPE_VIDEOS != 0) {
videoExtensions.forEach {
query.append("${MediaStore.Images.Media.DATA} LIKE ? OR ")
}
}
if (filterMedia and GIFS != 0) {
if (filterMedia and TYPE_GIFS != 0) {
query.append("${MediaStore.Images.Media.DATA} LIKE ?")
}
@ -85,29 +88,29 @@ class MediaFetcher(val context: Context) {
args.add("$path/%/%")
}
if (filterMedia and IMAGES != 0) {
if (filterMedia and TYPE_IMAGES != 0) {
photoExtensions.forEach {
args.add("%$it")
}
}
if (filterMedia and VIDEOS != 0) {
if (filterMedia and TYPE_VIDEOS != 0) {
videoExtensions.forEach {
args.add("%$it")
}
}
if (filterMedia and GIFS != 0) {
if (filterMedia and TYPE_GIFS != 0) {
args.add("%.gif")
}
return args
}
private fun parseCursor(context: Context, cursor: Cursor, isPickImage: Boolean, isPickVideo: Boolean, curPath: String, filterMedia: Int): ArrayList<Medium> {
private fun parseCursor(cursor: Cursor, curPath: String): ArrayList<String> {
val config = context.config
val includedFolders = config.includedFolders
val foldersToScan = HashSet<String>()
var foldersToScan = ArrayList<String>()
cursor.use {
if (cursor.moveToFirst()) {
@ -129,25 +132,27 @@ class MediaFetcher(val context: Context) {
}
}
val curMedia = ArrayList<Medium>()
val showHidden = config.shouldShowHidden
val excludedFolders = config.excludedFolders
foldersToScan.filter { shouldFolderBeVisible(it, excludedFolders, includedFolders, showHidden) }.toList().forEach {
fetchFolderContent(it, curMedia, isPickImage, isPickVideo, filterMedia)
foldersToScan = foldersToScan.filter { it.shouldFolderBeVisible(excludedFolders, includedFolders, showHidden) } as ArrayList<String>
if (config.isThirdPartyIntent && curPath.isNotEmpty()) {
foldersToScan.add(curPath)
}
if (config.isThirdPartyIntent && curPath.isNotEmpty() && curMedia.isEmpty()) {
getMediaInFolder(curPath, curMedia, isPickImage, isPickVideo, filterMedia)
return foldersToScan.distinctBy { it.toLowerCase() } as ArrayList<String>
}
Medium.sorting = config.getFileSorting(curPath)
curMedia.sort()
return curMedia
}
private fun addFolder(curFolders: HashSet<String>, folder: String) {
private fun addFolder(curFolders: ArrayList<String>, folder: String) {
curFolders.add(folder)
if (folder.startsWith(OTG_PATH)) {
val files = context.getOTGFolderChildren(folder) ?: return
for (file in files) {
if (file.isDirectory) {
val relativePath = file.uri.path.substringAfterLast("${context.config.OTGPartition}:")
addFolder(curFolders, "$OTG_PATH$relativePath")
}
}
} else {
val files = File(folder).listFiles() ?: return
for (file in files) {
if (file.isDirectory) {
@ -155,55 +160,19 @@ class MediaFetcher(val context: Context) {
}
}
}
}
private fun fetchFolderContent(path: String, curMedia: ArrayList<Medium>, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int) {
if (path.startsWith(OTG_PATH)) {
getMediaOnOTG(path, curMedia, isPickImage, isPickVideo, filterMedia)
private fun fetchFolderContent(path: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int): ArrayList<Medium> {
return if (path.startsWith(OTG_PATH)) {
getMediaOnOTG(path, isPickImage, isPickVideo, filterMedia)
} else {
getMediaInFolder(path, curMedia, isPickImage, isPickVideo, filterMedia)
getMediaInFolder(path, isPickImage, isPickVideo, filterMedia)
}
}
private fun groupDirectories(media: ArrayList<Medium>): HashMap<String, ArrayList<Medium>> {
val directories = LinkedHashMap<String, ArrayList<Medium>>()
val hasOTG = context.hasOTGConnected() && context.config.OTGBasePath.isNotEmpty()
for (medium in media) {
if (shouldStop) {
break
}
val parentDir = (if (hasOTG && medium.path.startsWith(OTG_PATH)) medium.path.getParentPath().toLowerCase() else File(medium.path).parent?.toLowerCase())
?: continue
if (directories.containsKey(parentDir)) {
directories[parentDir]!!.add(medium)
} else {
directories[parentDir] = arrayListOf(medium)
}
}
return directories
}
private fun shouldFolderBeVisible(path: String, excludedPaths: MutableSet<String>, includedPaths: MutableSet<String>, showHidden: Boolean): Boolean {
val file = File(path)
return if (path.isEmpty()) {
false
} else if (path.isThisOrParentIncluded(includedPaths)) {
true
} else if (path.isThisOrParentExcluded(excludedPaths)) {
false
} else if (!showHidden && file.isDirectory && file.canonicalFile == file.absoluteFile) {
var containsNoMediaOrDot = file.containsNoMedia() || path.contains("/.")
if (!containsNoMediaOrDot) {
containsNoMediaOrDot = file.doesThisOrParentHaveNoMedia()
}
!containsNoMediaOrDot
} else {
true
}
}
private fun getMediaInFolder(folder: String, curMedia: ArrayList<Medium>, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int) {
val files = File(folder).listFiles() ?: return
private fun getMediaInFolder(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int): ArrayList<Medium> {
val media = ArrayList<Medium>()
val files = File(folder).listFiles() ?: return media
val doExtraCheck = context.config.doExtraCheck
val showHidden = context.config.shouldShowHidden
@ -220,13 +189,13 @@ class MediaFetcher(val context: Context) {
if (!isImage && !isVideo && !isGif)
continue
if (isVideo && (isPickImage || filterMedia and VIDEOS == 0))
if (isVideo && (isPickImage || filterMedia and TYPE_VIDEOS == 0))
continue
if (isImage && (isPickVideo || filterMedia and IMAGES == 0))
if (isImage && (isPickVideo || filterMedia and TYPE_IMAGES == 0))
continue
if (isGif && filterMedia and GIFS == 0)
if (isGif && filterMedia and TYPE_GIFS == 0)
continue
if (!showHidden && filename.startsWith('.'))
@ -240,18 +209,20 @@ class MediaFetcher(val context: Context) {
val dateModified = file.lastModified()
val type = when {
isImage -> TYPE_IMAGE
isVideo -> TYPE_VIDEO
else -> TYPE_GIF
isImage -> TYPE_IMAGES
isVideo -> TYPE_VIDEOS
else -> TYPE_GIFS
}
val medium = Medium(filename, file.absolutePath, dateModified, dateTaken, size, type)
curMedia.add(medium)
val medium = Medium(null, filename, file.absolutePath, folder, dateModified, dateTaken, size, type)
media.add(medium)
}
return media
}
private fun getMediaOnOTG(folder: String, curMedia: ArrayList<Medium>, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int) {
val files = context.getDocumentFile(folder)?.listFiles() ?: return
private fun getMediaOnOTG(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int): ArrayList<Medium> {
val media = ArrayList<Medium>()
val files = context.getDocumentFile(folder)?.listFiles() ?: return media
val doExtraCheck = context.config.doExtraCheck
val showHidden = context.config.shouldShowHidden
@ -260,7 +231,7 @@ class MediaFetcher(val context: Context) {
break
}
val filename = file.name
val filename = file.name ?: continue
val isImage = filename.isImageFast()
val isVideo = if (isImage) false else filename.isVideoFast()
val isGif = if (isImage || isVideo) false else filename.isGif()
@ -268,13 +239,13 @@ class MediaFetcher(val context: Context) {
if (!isImage && !isVideo && !isGif)
continue
if (isVideo && (isPickImage || filterMedia and VIDEOS == 0))
if (isVideo && (isPickImage || filterMedia and TYPE_VIDEOS == 0))
continue
if (isImage && (isPickVideo || filterMedia and IMAGES == 0))
if (isImage && (isPickVideo || filterMedia and TYPE_IMAGES == 0))
continue
if (isGif && filterMedia and GIFS == 0)
if (isGif && filterMedia and TYPE_GIFS == 0)
continue
if (!showHidden && filename.startsWith('.'))
@ -288,14 +259,16 @@ class MediaFetcher(val context: Context) {
val dateModified = file.lastModified()
val type = when {
isImage -> TYPE_IMAGE
isVideo -> TYPE_VIDEO
else -> TYPE_GIF
isImage -> TYPE_IMAGES
isVideo -> TYPE_VIDEOS
else -> TYPE_GIFS
}
val path = Uri.decode(file.uri.toString().replaceFirst("${context.config.OTGBasePath}%3A", OTG_PATH))
val medium = Medium(filename, path, dateModified, dateTaken, size, type)
curMedia.add(medium)
}
val path = Uri.decode(file.uri.toString().replaceFirst("${context.config.OTGTreeUri}/document/${context.config.OTGPartition}%3A", OTG_PATH))
val medium = Medium(null, filename, path, folder, dateModified, dateTaken, size, type)
media.add(medium)
}
return media
}
}

View file

@ -0,0 +1,25 @@
package com.simplemobiletools.gallery.interfaces
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query
import com.simplemobiletools.gallery.models.Directory
@Dao
interface DirectoryDao {
@Query("SELECT path, thumbnail, filename, media_count, last_modified, date_taken, size, location, media_types FROM directories")
fun getAll(): List<Directory>
@Insert(onConflict = REPLACE)
fun insert(directory: Directory)
@Insert(onConflict = REPLACE)
fun insertAll(directories: List<Directory>)
@Query("DELETE FROM directories WHERE path = :path")
fun deleteDirPath(path: String)
@Query("UPDATE OR REPLACE directories SET thumbnail = :thumbnail, media_count = :mediaCnt, last_modified = :lastModified, date_taken = :dateTaken, size = :size, media_types = :mediaTypes WHERE path = :path")
fun updateDirectory(path: String, thumbnail: String, mediaCnt: Int, lastModified: Long, dateTaken: Long, size: Long, mediaTypes: Int)
}

View file

@ -0,0 +1,28 @@
package com.simplemobiletools.gallery.interfaces
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query
import com.simplemobiletools.gallery.models.Medium
@Dao
interface MediumDao {
@Query("SELECT * FROM media")
fun getAll(): List<Medium>
@Query("SELECT filename, full_path, parent_path, last_modified, date_taken, size, type FROM media WHERE parent_path = :path")
fun getMediaFromPath(path: String): List<Medium>
@Insert(onConflict = REPLACE)
fun insert(medium: Medium)
@Insert(onConflict = REPLACE)
fun insertAll(media: List<Medium>)
@Query("DELETE FROM media WHERE full_path = :path")
fun deleteMediumPath(path: String)
@Query("UPDATE OR REPLACE media SET filename = :newFilename, full_path = :newFullPath, parent_path = :newParentPath WHERE full_path = :oldPath")
fun updateMedium(oldPath: String, newParentPath: String, newFilename: String, newFullPath: String)
}

View file

@ -1,14 +1,29 @@
package com.simplemobiletools.gallery.models
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
import com.simplemobiletools.commons.extensions.formatDate
import com.simplemobiletools.commons.extensions.formatSize
import com.simplemobiletools.commons.helpers.*
import java.io.Serializable
data class Directory(var path: String, var tmb: String, var name: String, var mediaCnt: Int, val modified: Long, val taken: Long,
val size: Long, val isOnSDCard: Boolean) : Serializable, Comparable<Directory> {
@Entity(tableName = "directories", indices = [Index(value = "path", unique = true)])
data class Directory(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "path") var path: String,
@ColumnInfo(name = "thumbnail") var tmb: String,
@ColumnInfo(name = "filename") var name: String,
@ColumnInfo(name = "media_count") var mediaCnt: Int,
@ColumnInfo(name = "last_modified") var modified: Long,
@ColumnInfo(name = "date_taken") var taken: Long,
@ColumnInfo(name = "size") var size: Long,
@ColumnInfo(name = "location") val location: Int,
@ColumnInfo(name = "media_types") var types: Int) : Serializable, Comparable<Directory> {
companion object {
private val serialVersionUID = -6553345863555455L
private const val serialVersionUID = -6553345863555455L
var sorting: Int = 0
}

View file

@ -1,31 +1,42 @@
package com.simplemobiletools.gallery.models
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
import com.simplemobiletools.commons.extensions.formatDate
import com.simplemobiletools.commons.extensions.formatSize
import com.simplemobiletools.commons.extensions.getMimeType
import com.simplemobiletools.commons.extensions.isDng
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.gallery.helpers.TYPE_GIF
import com.simplemobiletools.gallery.helpers.TYPE_IMAGE
import com.simplemobiletools.gallery.helpers.TYPE_VIDEO
import com.simplemobiletools.gallery.helpers.TYPE_GIFS
import com.simplemobiletools.gallery.helpers.TYPE_IMAGES
import com.simplemobiletools.gallery.helpers.TYPE_VIDEOS
import java.io.Serializable
data class Medium(var name: String, var path: String, val modified: Long, val taken: Long, val size: Long, val type: Int) : Serializable, Comparable<Medium> {
@Entity(tableName = "media", indices = [(Index(value = "full_path", unique = true))])
data class Medium(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "filename") var name: String,
@ColumnInfo(name = "full_path") var path: String,
@ColumnInfo(name = "parent_path") var parentPath: String,
@ColumnInfo(name = "last_modified") val modified: Long,
@ColumnInfo(name = "date_taken") val taken: Long,
@ColumnInfo(name = "size") val size: Long,
@ColumnInfo(name = "type") val type: Int) : Serializable, Comparable<Medium> {
companion object {
private val serialVersionUID = -6553149366975455L
private const val serialVersionUID = -6553149366975455L
var sorting: Int = 0
}
fun isGif() = type == TYPE_GIF
fun isGif() = type == TYPE_GIFS
fun isImage() = type == TYPE_IMAGE
fun isImage() = type == TYPE_IMAGES
fun isVideo() = type == TYPE_VIDEO
fun isVideo() = type == TYPE_VIDEOS
fun isDng() = path.isDng()
fun getMimeType() = path.getMimeType()
override fun compareTo(other: Medium): Int {
var result: Int
when {

View file

@ -1,16 +0,0 @@
package com.simplemobiletools.gallery.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.google.gson.Gson
import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask
import com.simplemobiletools.gallery.extensions.config
class InstallReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
GetDirectoriesAsynctask(context, false, false) {
context.config.directories = Gson().toJson(it)
}.execute()
}
}

View file

@ -3,14 +3,32 @@ package com.simplemobiletools.gallery.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.google.gson.Gson
import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.commons.extensions.getFilenameFromPath
import com.simplemobiletools.commons.extensions.getParentPath
import com.simplemobiletools.commons.extensions.isImageFast
import com.simplemobiletools.commons.extensions.isVideoFast
import com.simplemobiletools.commons.helpers.REFRESH_PATH
import com.simplemobiletools.gallery.extensions.galleryDB
import com.simplemobiletools.gallery.helpers.TYPE_GIFS
import com.simplemobiletools.gallery.helpers.TYPE_IMAGES
import com.simplemobiletools.gallery.helpers.TYPE_VIDEOS
import com.simplemobiletools.gallery.models.Medium
import java.io.File
class RefreshMediaReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
GetDirectoriesAsynctask(context, false, false) {
context.config.directories = Gson().toJson(it)
}.execute()
val path = intent.getStringExtra(REFRESH_PATH) ?: return
Thread {
val medium = Medium(null, path.getFilenameFromPath(), path, path.getParentPath(), System.currentTimeMillis(), System.currentTimeMillis(),
File(path).length(), getFileType(path))
context.galleryDB.MediumDao().insert(medium)
}.start()
}
private fun getFileType(path: String) = when {
path.isImageFast() -> TYPE_IMAGES
path.isVideoFast() -> TYPE_VIDEOS
else -> TYPE_GIFS
}
}

View file

@ -52,14 +52,6 @@
android:paddingTop="@dimen/medium_margin"
android:text="@string/last_modified"/>
<com.simplemobiletools.commons.views.MyCompatRadioButton
android:id="@+id/sorting_dialog_radio_date_taken"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/medium_margin"
android:paddingTop="@dimen/medium_margin"
android:text="@string/date_taken"/>
</RadioGroup>
<include

View file

@ -82,7 +82,7 @@
</LinearLayout>
<ImageView
android:id="@+id/dir_sd_card"
android:id="@+id/dir_location"
android:layout_width="@dimen/sd_card_icon_size"
android:layout_height="@dimen/sd_card_icon_size"
android:layout_alignParentBottom="true"

View file

@ -76,7 +76,7 @@
android:paddingBottom="@dimen/tiny_margin">
<ImageView
android:id="@+id/dir_sd_card"
android:id="@+id/dir_location"
android:layout_width="@dimen/sd_card_icon_size"
android:layout_height="@dimen/sd_card_icon_size"
android:paddingBottom="@dimen/small_margin"

View file

@ -0,0 +1,189 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Simple Gallery</string>
<string name="app_launcher_name">Galleri</string>
<string name="edit">Rediger</string>
<string name="open_camera">Åbn kamera</string>
<string name="hidden">(skjult)</string>
<string name="excluded">(ekskluderet)</string>
<string name="pin_folder">Pin folder</string>
<string name="unpin_folder">Unpin folder</string>
<string name="pin_to_the_top">Pin to the top</string>
<string name="show_all">Vis indholdet af alle mappert</string>
<string name="all_folders">Alle mapper</string>
<string name="folder_view">Skift til mappevisning</string>
<string name="other_folder">Anden mappe</string>
<string name="show_on_map">Vis på kort</string>
<string name="unknown_location">Ukendt placering</string>
<string name="increase_column_count">Flere kolonner</string>
<string name="reduce_column_count">Færre kolonner</string>
<string name="change_cover_image">Change cover image</string>
<string name="select_photo">Select photo</string>
<string name="use_default">Use default</string>
<string name="volume">Volume</string>
<string name="brightness">Brightness</string>
<string name="lock_orientation">Lås orientering</string>
<string name="unlock_orientation">Lås orientering op</string>
<!-- Filter -->
<string name="filter_media">Filtrer medier</string>
<string name="images">Billeder</string>
<string name="videos">Videoer</string>
<string name="gifs">GIF\'er</string>
<string name="no_media_with_filters">Der blev ikke fundet nogen filer med det valgte filter.</string>
<string name="change_filters_underlined"><u>Skift filter</u></string>
<!-- Hide / Exclude -->
<string name="hide_folder_description">Denne funktion skjuler mappen og dens eventueller undermapper ved at oprette en \'.nomedia\'-fil i den. Du kan se dem ved at klikke på \'Vis skjulte\' i indstillingerne. Fortsæt?</string>
<string name="exclude">Ekskluder</string>
<string name="excluded_folders">Ekskluderede mapper</string>
<string name="manage_excluded_folders">Administrer ekskluderede mapper</string>
<string name="exclude_folder_description">Dette vil kun ekskludere de valgte mapper (og deres undermapper) fra Simple Gallery. Du kan administrere ekskluderede mapper i indstillingerne.</string>
<string name="exclude_folder_parent">Ekskluder en overliggende mappe i stedet?</string>
<string name="excluded_activity_placeholder">Ekskludering af mapper vil skjule dem og eventuelle undermapper for Simple Gallery, de vil stadig være synlige for andre apps.\n\nHvis du også vil skjule dem for andre apps, skal du bruge funktionen Skjul.</string>
<string name="remove_all">Fjern alle</string>
<string name="remove_all_description">Fjern alle fra listen med ekskluderede mapper? Det vil ikke slette mapperne.</string>
<string name="hidden_folders">Skjulte mapper</string>
<string name="manage_hidden_folders">Administrer skjulte mapper</string>
<string name="hidden_folders_placeholder">Det ser ikke ud til at du har nogen skjulte mapper med en \".nomedia\"-fil.</string>
<!-- Include folders -->
<string name="include_folders">Inkluderede mapper</string>
<string name="manage_included_folders">Administrer inkluderede mapper</string>
<string name="add_folder">Tilføj mappe</string>
<string name="included_activity_placeholder">Hvis du har mapper med mediefiler som appen ikke har fundet, kan du manuelt tilføje dem her.\n\nDet vil ikke ekskludere andre mapper.</string>
<!-- Resizing -->
<string name="resize">Skaler</string>
<string name="resize_and_save">Resize selection and save</string>
<string name="width">Width</string>
<string name="height">Height</string>
<string name="keep_aspect_ratio">Keep aspect ratio</string>
<string name="invalid_values">Please enter a valid resolution</string>
<!-- Editor -->
<string name="editor">Editor</string>
<string name="save">Gem</string>
<string name="rotate">Roter</string>
<string name="path">Sti</string>
<string name="invalid_image_path">Ugyldig sti til billede</string>
<string name="image_editing_failed">Redigering af billede mislykkedes</string>
<string name="edit_image_with">Rediger billede med:</string>
<string name="no_editor_found">Der blev ikke fundet en editor til billedbehandling</string>
<string name="unknown_file_location">Ukendt filplacering</string>
<string name="error_saving_file">Kunne ikke overskrive kildefilen</string>
<string name="rotate_left">Roter mod venstre</string>
<string name="rotate_right">Roter mod højre</string>
<string name="rotate_one_eighty">Roter 180º</string>
<string name="flip">Spejlvend</string>
<string name="flip_horizontally">Spejlvend vandret</string>
<string name="flip_vertically">Spejlvend lodret</string>
<string name="edit_with">Rediger med</string>
<!-- Set wallpaper -->
<string name="simple_wallpaper">Simple Wallpaper</string>
<string name="set_as_wallpaper">Sæt som baggrundsbillede</string>
<string name="set_as_wallpaper_failed">Det mislykkedes at sætte billedet som baggrund</string>
<string name="set_as_wallpaper_with">Sæt som baggrundsbillede med:</string>
<string name="setting_wallpaper">Sætter baggrundsbillede&#8230;</string>
<string name="wallpaper_set_successfully">Sat som baggrundsbillede</string>
<string name="portrait_aspect_ratio">Stående billedformat</string>
<string name="landscape_aspect_ratio">Liggende billedformat</string>
<string name="home_screen">Hjemmeskærm</string>
<string name="lock_screen">Låseskærm</string>
<string name="home_and_lock_screen">Hjemme- og låseskærm</string>
<!-- Slideshow -->
<string name="slideshow">Slideshow</string>
<string name="interval">Frekvens (sekunder):</string>
<string name="include_photos">Inkluder billeder</string>
<string name="include_videos">Inkluder videoer</string>
<string name="include_gifs">Inkluder GIF\'er</string>
<string name="random_order">Tilfældig rækkefølge</string>
<string name="use_fade">Use fade animations</string>
<string name="move_backwards">Kør baglæns</string>
<string name="loop_slideshow">Loop slideshow</string>
<string name="slideshow_ended">Slideshowet endte</string>
<string name="no_media_for_slideshow">Der blev ikke funket nogen mediefiler til slideshowet</string>
<!-- View types -->
<string name="change_view_type">Skift visning</string>
<string name="grid">Gitter</string>
<string name="list">Liste</string>
<!-- Settings -->
<string name="autoplay_videos">Afspil automatisk videoer</string>
<string name="toggle_filename">Toggle filename visibility</string>
<string name="loop_videos">Kør videoer i sløjfe</string>
<string name="animate_gifs">Animér GIF\'er i miniaturer</string>
<string name="max_brightness">Maksimal lysstyrke ved fuldskærmsvisning af medier</string>
<string name="crop_thumbnails">Beskær miniaturer til kvadrater</string>
<string name="screen_rotation_by">Roter fuldskærmsmedier efter</string>
<string name="screen_rotation_system_setting">Systemindstilling</string>
<string name="screen_rotation_device_rotation">Enhedens orientering</string>
<string name="screen_rotation_aspect_ratio">Billedformat</string>
<string name="black_background_at_fullscreen">Sort baggrund og statuslinje ved medievisning i fuldskærm</string>
<string name="scroll_thumbnails_horizontally">Scroll miniaturer vandret</string>
<string name="hide_system_ui_at_fullscreen">Skjul automatisk systemets brugerflade ved fuldskærmsvisning af medier</string>
<string name="delete_empty_folders">Slet tomme mapper efter sletning af deres indhold</string>
<string name="allow_photo_gestures">Tillad kontrol af lysstyrke på billeder med lodrette bevægelser</string>
<string name="allow_video_gestures">Tillad kontrol af videolyd og lysstyrke med lodrette bevægelser</string>
<string name="show_media_count">Vis antal filer i hver mappe i oversigten</string>
<string name="replace_share_with_rotate">Erstat Del med Roter i fuldskærmsmenuen</string>
<string name="show_extended_details">Vis udvidede oplysninger over medier i fuldskærm</string>
<string name="manage_extended_details">Manage extended details</string>
<string name="one_finger_zoom">Tillad zoom med en finger når medier er i fuldskærm</string>
<string name="allow_instant_change">Tillad skift af medie ved klik på skærmens sider</string>
<string name="replace_zoomable_images">RErstat stærkt zoombare billeder med nogle i bedre kvalitet</string>
<string name="hide_extended_details">Skjul udvidede oplysninger når statuslinjen er skjult</string>
<string name="do_extra_check">Tjek en ekstra gang for at undgå visning af ugyldige filer</string>
<!-- Setting sections -->
<string name="thumbnails">Thumbnails</string>
<string name="fullscreen_media">Fullscreen media</string>
<string name="extended_details">Extended details</string>
<!-- FAQ -->
<string name="faq_1_title">How can I make Simple Gallery the default device gallery?</string>
<string name="faq_1_text">First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\".
The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app.</string>
<string name="faq_2_title">I locked the app with a password, but I forgot it. What can I do?</string>
<string name="faq_2_text">You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files.</string>
<string name="faq_3_title">How can I make an album always appear at the top?</string>
<string name="faq_3_text">You can long press the desired album and select the Pin icon at the actionmenu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method.</string>
<string name="faq_4_title">How can I fast-forward videos?</string>
<string name="faq_4_text">You can click on the current or max duration texts near the seekbar, that will move the video either backward, or forward.</string>
<string name="faq_5_title">What is the difference between hiding and excluding a folder?</string>
<string name="faq_5_text">Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too.</string>
<string name="faq_6_title">Why do folders with music cover art or stickers show up?</string>
<string name="faq_6_text">It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too.</string>
<string name="faq_7_title">A folder with images isn\'t showing up, what can I do?</string>
<string name="faq_7_text">That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder.</string>
<string name="faq_8_title">What if I want just a few particular folders visible?</string>
<string name="faq_8_text">Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders.
That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up.</string>
<string name="faq_9_title">Fullscreen images have weird artifacts, can I somehow improve the quality?</string>
<string name="faq_9_text">Yea, there is a toggle in Settings saying \"Replace deep zoomable images with better quality ones\", you can use that. It will improve the quality of the images, but they will get blurred once you try zooming in too much.</string>
<string name="faq_10_title">Can I crop images with this app?</string>
<string name="faq_10_text">Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view.</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->
<string name="app_short_description">A gallery for viewing photos and videos without ads.</string>
<string name="app_long_description">
A simple tool usable for viewing photos and videos. Items can be sorted by date, size, name both ascending or descending, photos can be zoomed in. Media files are shown in multiple columns depending on the size of the display, you can change the column count by pinch gestures. They can be renamed, shared, deleted, copied, moved. Images can also be cropped, rotated, flipped or set as Wallpaper directly from the app.
The Gallery is also offered for third party usage for previewing images / videos, adding attachments at email clients etc. It\'s perfect for everyday usage.
The fingerprint permission is needed for locking either hidden item visibility, or the whole app.
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com
</string>
<!--
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
</resources>

View file

@ -5,7 +5,7 @@
<string name="edit">Modifica</string>
<string name="open_camera">Apri fotocamera</string>
<string name="hidden">(nascosta)</string>
<string name="excluded">(excluded)</string>
<string name="excluded">(esclusa)</string>
<string name="pin_folder">Blocca cartella</string>
<string name="unpin_folder">Sblocca cartella</string>
<string name="pin_to_the_top">Fissa in alto</string>
@ -165,7 +165,7 @@
<string name="faq_9_title">Le immagini a schermo intero hanno strani artefatti, posso migliorarne la qualità in qualche modo?</string>
<string name="faq_9_text">Sì, c\'è un\'opzione nelle impostazioni che dice \"Sostituisci le immagini ingrandibili a fondo con altre di migliore qualità\", puoi usare quella. Ciò migliorerà la qualità delle immagini, ma saranno sfuocate quando proverai a ingrandirle troppo.</string>
<string name="faq_10_title">Posso ritagliare le immagini con questa app?</string>
<string name="faq_10_text">Sì, puoi ritagliare le immagini nell\'editor, trascinando gli angoli dell\'immagine. Puoi usare l\'editor sia premendo a lungo la miniatura di un'immagine e selezionando Modifica, o selezionando Modifica mentre la vedi a schermo intero.</string>
<string name="faq_10_text">Sì, puoi ritagliare le immagini nell\'editor, trascinando gli angoli dell\'immagine. Puoi usare l\'editor sia premendo a lungo la miniatura di un\'immagine e selezionando Modifica, o selezionando Modifica mentre la vedi a schermo intero.</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->

View file

@ -5,7 +5,7 @@
<string name="edit">Rediger</string>
<string name="open_camera">Åpne kamera</string>
<string name="hidden">(skjult)</string>
<string name="excluded">(excluded)</string>
<string name="excluded">(ekskludert)</string>
<string name="pin_folder">Fest mappe</string>
<string name="unpin_folder">Løsne mappe</string>
<string name="pin_to_the_top">Fest til toppen</string>

View file

@ -5,7 +5,7 @@
<string name="edit">Editar</string>
<string name="open_camera">Abrir câmara</string>
<string name="hidden">(oculta)</string>
<string name="excluded">(excluded)</string>
<string name="excluded">(excluída)</string>
<string name="pin_folder">Fixar pasta</string>
<string name="unpin_folder">Desafixar pasta</string>
<string name="pin_to_the_top">Fixar no topo</string>

View file

@ -3,9 +3,9 @@
<string name="app_name">Simple Gallery</string>
<string name="app_launcher_name">Galleri</string>
<string name="edit">Redigera</string>
<string name="open_camera">Starta kameran</string>
<string name="open_camera">Öppna kameran</string>
<string name="hidden">(dold)</string>
<string name="excluded">(excluded)</string>
<string name="excluded">(utesluten)</string>
<string name="pin_folder">Fäst mapp</string>
<string name="unpin_folder">Lossa mapp</string>
<string name="pin_to_the_top">Fäst högst upp</string>

View file

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.2.31'
ext.kotlin_version = '1.2.40'
repositories {
jcenter()
@ -9,7 +9,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.0'
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong