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 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)* Version 3.7.1 *(2018-04-12)*
---------------------------- ----------------------------

View file

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

View file

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

View file

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

View file

@ -12,14 +12,12 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.Toast
import com.simplemobiletools.commons.dialogs.CreateNewFolderDialog import com.simplemobiletools.commons.dialogs.CreateNewFolderDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PERMISSION_READ_STORAGE import com.simplemobiletools.commons.helpers.*
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.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.models.Release 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.BuildConfig
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.DirectoryAdapter 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.ChangeSortingDialog
import com.simplemobiletools.gallery.dialogs.FilterMediaDialog import com.simplemobiletools.gallery.dialogs.FilterMediaDialog
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.* 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.Directory
import com.simplemobiletools.gallery.models.Medium
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import java.io.* import java.io.*
@ -43,8 +44,6 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
private val LAST_MEDIA_CHECK_PERIOD = 3000L private val LAST_MEDIA_CHECK_PERIOD = 3000L
private val NEW_APP_PACKAGE = "com.simplemobiletools.clock" private val NEW_APP_PACKAGE = "com.simplemobiletools.clock"
lateinit var mDirs: ArrayList<Directory>
private var mIsPickImageIntent = false private var mIsPickImageIntent = false
private var mIsPickVideoIntent = false private var mIsPickVideoIntent = false
private var mIsGetImageContentIntent = false private var mIsGetImageContentIntent = false
@ -60,7 +59,6 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
private var mLatestMediaDateId = 0L private var mLatestMediaDateId = 0L
private var mLastMediaHandler = Handler() private var mLastMediaHandler = Handler()
private var mTempShowHiddenHandler = Handler() private var mTempShowHiddenHandler = Handler()
private var mCurrAsyncTask: GetDirectoriesAsynctask? = null
private var mZoomListener: MyRecyclerView.MyZoomListener? = null private var mZoomListener: MyRecyclerView.MyZoomListener? = null
private var mStoredAnimateGifs = true private var mStoredAnimateGifs = true
@ -88,7 +86,6 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
removeTempFolder() removeTempFolder()
directories_refresh_layout.setOnRefreshListener { getDirectories() } directories_refresh_layout.setOnRefreshListener { getDirectories() }
mDirs = ArrayList()
storeStateVariables() storeStateVariables()
checkWhatsNewDialog() checkWhatsNewDialog()
@ -104,6 +101,10 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
config.wasNewAppShown = true config.wasNewAppShown = true
NewAppDialog(this, NEW_APP_PACKAGE, "Simple Clock") NewAppDialog(this, NEW_APP_PACKAGE, "Simple Clock")
}*/ }*/
if (!config.wasOTGHandled && hasPermission(PERMISSION_WRITE_STORAGE)) {
checkOTGInclusion()
}
} }
override fun onStart() { override fun onStart() {
@ -127,7 +128,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
getRecyclerAdapter()?.updateShowMediaCount(config.showMediaCount) getRecyclerAdapter()?.updateShowMediaCount(config.showMediaCount)
} }
if (mStoredScrollHorizontally != config.scrollHorizontally || mStoredShowInfoBubble != config.showInfoBubble) { if (mStoredScrollHorizontally != config.scrollHorizontally) {
getRecyclerAdapter()?.updateScrollHorizontally(config.viewTypeFolders != VIEW_TYPE_LIST && config.scrollHorizontally) getRecyclerAdapter()?.updateScrollHorizontally(config.viewTypeFolders != VIEW_TYPE_LIST && config.scrollHorizontally)
setupScrollDirection() setupScrollDirection()
} }
@ -144,6 +145,8 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
directories_horizontal_fastscroller.updateBubbleColors() directories_horizontal_fastscroller.updateBubbleColors()
directories_vertical_fastscroller.updateBubbleColors() directories_vertical_fastscroller.updateBubbleColors()
directories_horizontal_fastscroller.allowBubbleDisplay = config.showInfoBubble
directories_vertical_fastscroller.allowBubbleDisplay = config.showInfoBubble
directories_refresh_layout.isEnabled = config.enablePullToRefresh directories_refresh_layout.isEnabled = config.enablePullToRefresh
invalidateOptionsMenu() invalidateOptionsMenu()
directories_empty_text_label.setTextColor(config.textColor) directories_empty_text_label.setTextColor(config.textColor)
@ -187,9 +190,8 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
config.temporarilyShowHidden = false config.temporarilyShowHidden = false
mTempShowHiddenHandler.removeCallbacksAndMessages(null) mTempShowHiddenHandler.removeCallbacksAndMessages(null)
removeTempFolder() removeTempFolder()
if (!isChangingConfigurations) {
if (!mDirs.isEmpty()) { GalleryDataBase.destroyInstance()
mCurrAsyncTask?.stopFetching()
} }
} }
@ -244,13 +246,27 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
val newFolder = File(config.tempFolderPath) val newFolder = File(config.tempFolderPath)
if (newFolder.exists() && newFolder.isDirectory) { if (newFolder.exists() && newFolder.isDirectory) {
if (newFolder.list()?.isEmpty() == true) { 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 = "" config.tempFolderPath = ""
} }
} }
private fun checkOTGInclusion() {
Thread {
if (hasOTGConnected()) {
runOnUiThread {
handleOTGPermission {
config.addIncludedFolder(OTG_PATH)
}
}
config.wasOTGHandled = true
}
}.start()
}
private fun tryLoadGallery() { private fun tryLoadGallery() {
handlePermission(PERMISSION_WRITE_STORAGE) { handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) { if (it) {
@ -274,22 +290,17 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
} }
mIsGettingDirs = true mIsGettingDirs = true
val dirs = getCachedDirectories() val getImagesOnly = mIsPickImageIntent || mIsGetImageContentIntent
if (dirs.isNotEmpty() && !mLoadedInitialPhotos) { val getVideosOnly = mIsPickVideoIntent || mIsGetVideoContentIntent
gotDirectories(dirs, true)
}
getCachedDirectories(getVideosOnly, getImagesOnly) {
if (!mLoadedInitialPhotos) { if (!mLoadedInitialPhotos) {
runOnUiThread {
directories_refresh_layout.isRefreshing = true 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() { 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) { if (config.directorySorting and SORT_BY_DATE_MODIFIED > 0 || config.directorySorting and SORT_BY_DATE_TAKEN > 0) {
getDirectories() getDirectories()
} else { } else {
gotDirectories(mDirs, true) gotDirectories(getCurrentlyDisplayedDirs())
} }
} }
} }
private fun showFilterMediaDialog() { private fun showFilterMediaDialog() {
FilterMediaDialog(this) { FilterMediaDialog(this) {
mLoadedInitialPhotos = false
directories_refresh_layout.isRefreshing = true directories_refresh_layout.isRefreshing = true
getDirectories() getDirectories()
} }
@ -332,8 +344,9 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
config.viewTypeFolders = it as Int config.viewTypeFolders = it as Int
invalidateOptionsMenu() invalidateOptionsMenu()
setupLayoutManager() setupLayoutManager()
val dirs = getCurrentlyDisplayedDirs()
directories_grid.adapter = null directories_grid.adapter = null
setupAdapter() setupAdapter(dirs)
} }
} }
@ -348,6 +361,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
} }
private fun toggleTemporarilyShowHidden(show: Boolean) { private fun toggleTemporarilyShowHidden(show: Boolean) {
mLoadedInitialPhotos = false
config.temporarilyShowHidden = show config.temporarilyShowHidden = show
getDirectories() getDirectories()
invalidateOptionsMenu() invalidateOptionsMenu()
@ -359,6 +373,13 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
runOnUiThread { runOnUiThread {
refreshItems() 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) { FilePickerDialog(this, internalStoragePath, false, config.shouldShowHidden) {
CreateNewFolderDialog(this, it) { CreateNewFolderDialog(this, it) {
config.tempFolderPath = 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) { private fun gotDirectories(newDirs: ArrayList<Directory>) {
/*if (!isFromCache) {
Thread {
checkFolderContentChange(newDirs)
}.start()
}*/
val dirs = getSortedDirectories(newDirs) val dirs = getSortedDirectories(newDirs)
directories_refresh_layout.isRefreshing = false var isPlaceholderVisible = dirs.isEmpty()
mIsGettingDirs = false
directories_empty_text_label.beVisibleIf(dirs.isEmpty() && !isFromCache) runOnUiThread {
directories_empty_text.beVisibleIf(dirs.isEmpty() && !isFromCache) checkPlaceholderVisibility(dirs)
directories_grid.beVisibleIf(directories_empty_text_label.isGone())
val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID
directories_vertical_fastscroller.beVisibleIf(directories_grid.isVisible() && !allowHorizontalScroll) directories_vertical_fastscroller.beVisibleIf(directories_grid.isVisible() && !allowHorizontalScroll)
directories_horizontal_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() checkLastMediaChanged()
mDirs = dirs
runOnUiThread { runOnUiThread {
setupAdapter() directories_refresh_layout.isRefreshing = false
directories_vertical_fastscroller.measureRecyclerView()
checkPlaceholderVisibility(dirs)
}
checkInvalidDirectories(dirs, directoryDao)
}.start()
} }
if (!isFromCache) { private fun checkPlaceholderVisibility(dirs: ArrayList<Directory>) {
storeDirectories() 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>) { private fun createDirectoryFromMedia(path: String, curMedia: ArrayList<Medium>, albumCovers: ArrayList<AlbumCover>, hiddenString: String,
newDirs.forEach { includedFolders: MutableSet<String>, isSortingAscending: Boolean): Directory {
val storedShortDirValue = config.loadFolderMediaShort(it.path) var thumbnail = curMedia.firstOrNull { getDoesFilePathExist(it.path) }?.path ?: ""
if (storedShortDirValue != it.toString()) { if (thumbnail.startsWith(OTG_PATH)) {
config.saveFolderMediaShort(it.path, it.toString()) thumbnail = thumbnail.getOTGPublicPath(applicationContext)
if (storedShortDirValue.isNotEmpty()) {
updateStoredFolderItems(it.path)
}
} }
albumCovers.forEach {
if (it.path == path && getDoesFilePathExist(it.tmb)) {
thumbnail = it.tmb
} }
} }
private fun storeDirectories() { val mediaTypes = curMedia.getDirMediaTypes()
if (!config.temporarilyShowHidden && config.tempFolderPath.isEmpty()) { val dirName = checkAppendingHidden(path, hiddenString, includedFolders)
storeDirectoryItems(mDirs)
} 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 val currAdapter = directories_grid.adapter
if (currAdapter == null) { if (currAdapter == null) {
initZoomListener() initZoomListener()
val fastscroller = if (config.scrollHorizontally) directories_horizontal_fastscroller else directories_vertical_fastscroller val fastscroller = if (config.scrollHorizontally) directories_horizontal_fastscroller else directories_vertical_fastscroller
DirectoryAdapter(this, mDirs, this, directories_grid, isPickIntent(intent) || isGetAnyContentIntent(intent), fastscroller) { DirectoryAdapter(this, dirs, this, directories_grid, isPickIntent(intent) || isGetAnyContentIntent(intent), fastscroller) {
itemClicked((it as Directory).path) val path = (it as Directory).path
if (path != config.tempFolderPath) {
itemClicked(path)
}
}.apply { }.apply {
setupZoomListener(mZoomListener) setupZoomListener(mZoomListener)
directories_grid.adapter = this directories_grid.adapter = this
} }
} else { } else {
(currAdapter as DirectoryAdapter).updateDirs(mDirs) (currAdapter as DirectoryAdapter).updateDirs(dirs)
} }
setupScrollDirection() 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 getBubbleTextItem(index: Int) = getRecyclerAdapter()?.dirs?.getOrNull(index)?.getBubbleText() ?: ""
private fun setupLatestMediaId() { private fun setupLatestMediaId() {
@ -678,16 +818,14 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
} }
override fun recheckPinnedFolders() { override fun recheckPinnedFolders() {
gotDirectories(movePinnedDirectoriesToFront(mDirs), true) gotDirectories(movePinnedDirectoriesToFront(getCurrentlyDisplayedDirs()))
} }
override fun updateDirectories(directories: ArrayList<Directory>, refreshList: Boolean) { override fun updateDirectories(directories: ArrayList<Directory>) {
if (refreshList) { Thread {
gotDirectories(directories, true) storeDirectoryItems(directories)
} else { removeInvalidDBDirectories()
mDirs = directories }.start()
storeDirectories()
}
} }
private fun checkWhatsNewDialog() { private fun checkWhatsNewDialog() {

View file

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

View file

@ -85,12 +85,12 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
val bundle = Bundle() val bundle = Bundle()
val file = File(mUri.toString()) val file = File(mUri.toString())
val type = when { val type = when {
file.isImageFast() -> TYPE_IMAGE file.isImageFast() -> TYPE_IMAGES
file.isVideoFast() -> TYPE_VIDEO file.isVideoFast() -> TYPE_VIDEOS
else -> TYPE_GIF 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 supportActionBar?.title = mMedium!!.name
bundle.putSerializable(MEDIUM, mMedium) bundle.putSerializable(MEDIUM, mMedium)

View file

@ -203,6 +203,11 @@ class SettingsActivity : SimpleActivity() {
settings_scroll_horizontally_holder.setOnClickListener { settings_scroll_horizontally_holder.setOnClickListener {
settings_scroll_horizontally.toggle() settings_scroll_horizontally.toggle()
config.scrollHorizontally = settings_scroll_horizontally.isChecked 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.helpers.*
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.models.Medium
import kotlinx.android.synthetic.main.activity_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.* import java.util.*
class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, ViewPagerFragment.FragmentListener { class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, ViewPagerFragment.FragmentListener {
@ -197,7 +200,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
showSystemUI() showSystemUI()
mDirectory = mPath.getParentPath().trimEnd('/') mDirectory = mPath.getParentPath()
if (mDirectory.startsWith(OTG_PATH.trimEnd('/'))) { if (mDirectory.startsWith(OTG_PATH.trimEnd('/'))) {
mDirectory += "/" mDirectory += "/"
} }
@ -527,11 +530,10 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
} }
val newFile = File(newPath) val tmpPath = "$filesDir/.tmp_${newPath.getFilenameFromPath()}"
val tmpFile = File(filesDir, ".tmp_${newPath.getFilenameFromPath()}") val tmpFileDirItem = FileDirItem(tmpPath, tmpPath.getFilenameFromPath())
try { try {
getFileOutputStream(tmpFile.toFileDirItem(applicationContext)) { getFileOutputStream(tmpFileDirItem) {
if (it == null) { if (it == null) {
toast(R.string.unknown_error_occurred) toast(R.string.unknown_error_occurred)
return@getFileOutputStream return@getFileOutputStream
@ -539,22 +541,24 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
val oldLastModified = getCurrentFile().lastModified() val oldLastModified = getCurrentFile().lastModified()
if (oldPath.isJpg()) { if (oldPath.isJpg()) {
copyFile(getCurrentFile(), tmpFile) copyFile(getCurrentPath(), tmpPath)
saveExifRotation(ExifInterface(tmpFile.absolutePath), mRotationDegrees) saveExifRotation(ExifInterface(tmpPath), mRotationDegrees)
} else { } else {
val bitmap = BitmapFactory.decodeFile(oldPath) val inputstream = getFileInputStreamSync(oldPath)
saveFile(tmpFile, bitmap, it as FileOutputStream) val bitmap = BitmapFactory.decodeStream(inputstream)
saveFile(tmpPath, bitmap, it as FileOutputStream)
} }
if (tmpFile.length() > 0 && getDoesFilePathExist(newPath)) { if (getDoesFilePathExist(newPath)) {
deleteFile(FileDirItem(newPath, newPath.getFilenameFromPath())) tryDeleteFileDirItem(FileDirItem(newPath, newPath.getFilenameFromPath()))
} }
copyFile(tmpFile, newFile)
copyFile(tmpPath, newPath)
scanPath(newPath) scanPath(newPath)
toast(R.string.file_saved) toast(R.string.file_saved)
if (config.keepLastModified) { if (config.keepLastModified) {
newFile.setLastModified(oldLastModified) File(newPath).setLastModified(oldLastModified)
updateLastModified(newPath, oldLastModified) updateLastModified(newPath, oldLastModified)
} }
@ -575,13 +579,14 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} catch (e: Exception) { } catch (e: Exception) {
showErrorToast(e) showErrorToast(e)
} finally { } finally {
deleteFile(FileDirItem(tmpFile.absolutePath, tmpFile.absolutePath.getFilenameFromPath())) tryDeleteFileDirItem(tmpFileDirItem)
} }
} }
@TargetApi(Build.VERSION_CODES.N) @TargetApi(Build.VERSION_CODES.N)
private fun tryRotateByExif(path: String): Boolean { private fun tryRotateByExif(path: String): Boolean {
return if (saveImageRotation(path, mRotationDegrees)) { return try {
if (saveImageRotation(path, mRotationDegrees)) {
mRotationDegrees = 0 mRotationDegrees = 0
invalidateOptionsMenu() invalidateOptionsMenu()
toast(R.string.file_saved) toast(R.string.file_saved)
@ -589,27 +594,30 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} else { } else {
false 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 inputStream: InputStream? = null
var out: OutputStream? = null var out: OutputStream? = null
try { try {
val fileDocument = if (isPathOnSD(destination.absolutePath)) getDocumentFile(destination.parent) else null out = getFileOutputStreamSync(destination, source.getMimeType())
out = getFileOutputStreamSync(destination.absolutePath, source.getMimeType(), fileDocument) inputStream = getFileInputStreamSync(source)
inputStream = FileInputStream(source) inputStream?.copyTo(out!!)
inputStream.copyTo(out!!)
} finally { } finally {
inputStream?.close() inputStream?.close()
out?.close() out?.close()
} }
} }
private fun saveFile(file: File, bitmap: Bitmap, out: FileOutputStream) { private fun saveFile(path: String, bitmap: Bitmap, out: FileOutputStream) {
val matrix = Matrix() val matrix = Matrix()
matrix.postRotate(mRotationDegrees.toFloat()) matrix.postRotate(mRotationDegrees.toFloat())
val bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) 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 { private fun isShowHiddenFlagNeeded(): Boolean {
@ -736,7 +744,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private fun deleteConfirmed() { private fun deleteConfirmed() {
val path = getCurrentMedia()[mPos].path val path = getCurrentMedia()[mPos].path
deleteFile(FileDirItem(path, path.getFilenameFromPath())) { tryDeleteFileDirItem(FileDirItem(path, path.getFilenameFromPath())) {
refreshViewPager() refreshViewPager()
} }
} }
@ -752,8 +760,16 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
private fun renameFile() { private fun renameFile() {
RenameItemDialog(this, getCurrentPath()) { val oldPath = getCurrentPath()
getCurrentMedia()[mPos].path = it RenameItemDialog(this, oldPath) {
getCurrentMedia()[mPos].apply {
path = it
name = it.getFilenameFromPath()
}
Thread {
updateDBMediaPath(oldPath, it)
}.start()
updateActionbarTitle() updateActionbarTitle()
} }
} }
@ -814,7 +830,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private fun deleteDirectoryIfEmpty() { private fun deleteDirectoryIfEmpty() {
val fileDirItem = FileDirItem(mDirectory, mDirectory.getFilenameFromPath(), getIsPathDirectory(mDirectory)) val fileDirItem = FileDirItem(mDirectory, mDirectory.getFilenameFromPath(), getIsPathDirectory(mDirectory))
if (config.deleteEmptyFolders && !fileDirItem.isDownloadsFolder() && fileDirItem.isDirectory && fileDirItem.getProperFileCount(applicationContext, true) == 0) { if (config.deleteEmptyFolders && !fileDirItem.isDownloadsFolder() && fileDirItem.isDirectory && fileDirItem.getProperFileCount(applicationContext, true) == 0) {
deleteFile(fileDirItem, true) tryDeleteFileDirItem(fileDirItem, true)
} }
scanPath(mDirectory) 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.PropertiesDialog
import com.simplemobiletools.commons.dialogs.RenameItemDialog import com.simplemobiletools.commons.dialogs.RenameItemDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView 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.ExcludeFolderDialog
import com.simplemobiletools.gallery.dialogs.PickMediumDialog import com.simplemobiletools.gallery.dialogs.PickMediumDialog
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.TYPE_GIF import com.simplemobiletools.gallery.helpers.*
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.models.AlbumCover import com.simplemobiletools.gallery.models.AlbumCover
import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.models.Directory
import kotlinx.android.synthetic.main.directory_item_list.view.* import kotlinx.android.synthetic.main.directory_item_list.view.*
@ -155,7 +153,8 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
} }
private fun renameDir() { private fun renameDir() {
val sourcePath = dirs[selectedPositions.first()].path val firstDir = dirs[selectedPositions.first()]
val sourcePath = firstDir.path
val dir = File(sourcePath) val dir = File(sourcePath)
if (activity.isAStorageRootFolder(dir.absolutePath)) { if (activity.isAStorageRootFolder(dir.absolutePath)) {
activity.toast(R.string.rename_folder_root) activity.toast(R.string.rename_folder_root)
@ -164,17 +163,13 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
RenameItemDialog(activity, dir.absolutePath) { RenameItemDialog(activity, dir.absolutePath) {
activity.runOnUiThread { activity.runOnUiThread {
if (selectedPositions.isEmpty()) { firstDir.apply {
return@runOnUiThread
}
dirs[selectedPositions.first()].apply {
path = it path = it
name = it.getFilenameFromPath() name = it.getFilenameFromPath()
tmb = File(it, tmb.getFilenameFromPath()).absolutePath tmb = File(it, tmb.getFilenameFromPath()).absolutePath
} }
updateDirs(dirs) 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 { dirs.forEach {
it.name = activity.checkAppendingHidden(it.path, hidden, includedFolders) it.name = activity.checkAppendingHidden(it.path, hidden, includedFolders)
} }
listener?.updateDirectories(dirs.toList() as ArrayList, false) listener?.updateDirectories(dirs.toList() as ArrayList)
activity.runOnUiThread { activity.runOnUiThread {
updateDirs(dirs) updateDirs(dirs)
} }
@ -249,7 +244,7 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
dirs = newDirs dirs = newDirs
finishActMode() finishActMode()
fastScroller?.measureRecyclerView() 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) { private fun copyMoveTo(isCopyOperation: Boolean) {
val paths = ArrayList<String>() val paths = ArrayList<String>()
val showHidden = activity.config.shouldShowHidden
selectedPositions.forEach { selectedPositions.forEach {
val dir = File(dirs[it].path) val path = dirs[it].path
paths.addAll(dir.listFiles().filter { !activity.getIsPathDirectory(it.absolutePath) && it.absolutePath.isImageVideoGif() }.map { it.absolutePath }) 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> 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() { private fun askConfirmDelete() {
if (config.skipDeleteConfirmation) { if (config.skipDeleteConfirmation) {
deleteFiles() deleteFiles()
@ -416,14 +429,18 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
dir_path?.text = "${directory.path.substringBeforeLast("/")}/" dir_path?.text = "${directory.path.substringBeforeLast("/")}/"
photo_cnt.text = directory.mediaCnt.toString() photo_cnt.text = directory.mediaCnt.toString()
val thumbnailType = when { val thumbnailType = when {
directory.tmb.isImageFast() -> TYPE_IMAGE directory.tmb.isImageFast() -> TYPE_IMAGES
directory.tmb.isVideoFast() -> TYPE_VIDEO directory.tmb.isVideoFast() -> TYPE_VIDEOS
else -> TYPE_GIF else -> TYPE_GIFS
} }
activity.loadImage(thumbnailType, directory.tmb, dir_thumbnail, scrollHorizontally, animateGifs, cropThumbnails) activity.loadImage(thumbnailType, directory.tmb, dir_thumbnail, scrollHorizontally, animateGifs, cropThumbnails)
dir_pin.beVisibleIf(pinnedFolders.contains(directory.path)) 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) photo_cnt.beVisibleIf(showMediaCount)
if (isListViewType) { if (isListViewType) {
@ -431,7 +448,7 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
dir_path.setTextColor(textColor) dir_path.setTextColor(textColor)
photo_cnt.setTextColor(textColor) photo_cnt.setTextColor(textColor)
dir_pin.applyColorFilter(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 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() { private fun renameFile() {
RenameItemDialog(activity, getCurrentPath()) { val oldPath = getCurrentPath()
RenameItemDialog(activity, oldPath) {
Thread {
activity.updateDBMediaPath(oldPath, it)
}.start()
activity.runOnUiThread { activity.runOnUiThread {
listener?.refreshItems() listener?.refreshItems()
finishActMode() finishActMode()
@ -158,7 +163,6 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
private fun editFile() { private fun editFile() {
activity.openEditor(getCurrentPath()) activity.openEditor(getCurrentPath())
finishActMode()
} }
private fun toggleFileVisibility(hide: Boolean) { 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 val fileDirItems = paths.map { FileDirItem(it, it.getFilenameFromPath()) } as ArrayList
activity.tryCopyMoveFilesTo(fileDirItems, isCopyOperation) { activity.tryCopyMoveFilesTo(fileDirItems, isCopyOperation) {
config.tempFolderPath = "" config.tempFolderPath = ""
activity.applicationContext.updateStoredFolderItems(it) activity.applicationContext.rescanFolderMedia(it)
activity.applicationContext.rescanFolderMedia(fileDirItems.first().getParentPath())
if (!isCopyOperation) { if (!isCopyOperation) {
listener?.refreshItems() listener?.refreshItems()
} }
@ -236,9 +241,8 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
} }
media.removeAll(removeMedia) media.removeAll(removeMedia)
listener?.deleteFiles(fileDirItems) listener?.tryDeleteFiles(fileDirItems)
removeSelectedItems() removeSelectedItems()
updateStoredFolderItems()
} }
} }
@ -248,14 +252,6 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
return selectedMedia 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>) { fun updateMedia(newMedia: ArrayList<Medium>) {
if (newMedia.hashCode() != currentMediaHash) { if (newMedia.hashCode() != currentMediaHash) {
currentMediaHash = newMedia.hashCode() currentMediaHash = newMedia.hashCode()
@ -331,7 +327,7 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Medium>,
interface MediaOperationsListener { interface MediaOperationsListener {
fun refreshItems() fun refreshItems()
fun deleteFiles(fileDirItems: ArrayList<FileDirItem>) fun tryDeleteFiles(fileDirItems: ArrayList<FileDirItem>)
fun selectedPaths(paths: ArrayList<String>) 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 com.simplemobiletools.gallery.models.Medium
import java.util.* 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) : val showAll: Boolean, val callback: (media: ArrayList<Medium>) -> Unit) :
AsyncTask<Void, Void, ArrayList<Medium>>() { AsyncTask<Void, Void, ArrayList<Medium>>() {
private val mediaFetcher = MediaFetcher(context) private val mediaFetcher = MediaFetcher(context)
override fun doInBackground(vararg params: Void): ArrayList<Medium> { override fun doInBackground(vararg params: Void): ArrayList<Medium> {
return if (showAll) { return if (showAll) {
val mediaMap = mediaFetcher.getMediaByDirectories(isPickVideo, isPickImage) val foldersToScan = mediaFetcher.getFoldersToScan("")
val media = ArrayList<Medium>() val media = ArrayList<Medium>()
for (folder in foldersToScan) {
mediaMap.values.forEach { val newMedia = mediaFetcher.getFilesFrom(folder, isPickImage, isPickVideo)
media.addAll(it) media.addAll(newMedia)
} }
Medium.sorting = context.config.getFileSorting("") 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_PATH != 0 -> sortingRadio.sorting_dialog_radio_path
currSorting and SORT_BY_SIZE != 0 -> sortingRadio.sorting_dialog_radio_size 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_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 else -> sortingRadio.sorting_dialog_radio_name
} }
sortBtn.isChecked = true 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_name -> SORT_BY_NAME
R.id.sorting_dialog_radio_path -> SORT_BY_PATH R.id.sorting_dialog_radio_path -> SORT_BY_PATH
R.id.sorting_dialog_radio_size -> SORT_BY_SIZE R.id.sorting_dialog_radio_size -> SORT_BY_SIZE
R.id.sorting_dialog_radio_last_modified -> SORT_BY_DATE_MODIFIED else -> SORT_BY_DATE_MODIFIED
else -> SORT_BY_DATE_TAKEN
} }
if (view.sorting_dialog_radio_order.checkedRadioButtonId == R.id.sorting_dialog_radio_descending) { 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.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.GIFS import com.simplemobiletools.gallery.helpers.TYPE_GIFS
import com.simplemobiletools.gallery.helpers.IMAGES import com.simplemobiletools.gallery.helpers.TYPE_IMAGES
import com.simplemobiletools.gallery.helpers.VIDEOS import com.simplemobiletools.gallery.helpers.TYPE_VIDEOS
import kotlinx.android.synthetic.main.dialog_filter_media.view.* import kotlinx.android.synthetic.main.dialog_filter_media.view.*
class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) { class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) {
@ -16,9 +16,9 @@ class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result:
init { init {
val filterMedia = activity.config.filterMedia val filterMedia = activity.config.filterMedia
view.apply { view.apply {
filter_media_images.isChecked = filterMedia and IMAGES != 0 filter_media_images.isChecked = filterMedia and TYPE_IMAGES != 0
filter_media_videos.isChecked = filterMedia and VIDEOS != 0 filter_media_videos.isChecked = filterMedia and TYPE_VIDEOS != 0
filter_media_gifs.isChecked = filterMedia and GIFS != 0 filter_media_gifs.isChecked = filterMedia and TYPE_GIFS != 0
} }
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
@ -32,11 +32,11 @@ class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result:
private fun dialogConfirmed() { private fun dialogConfirmed() {
var result = 0 var result = 0
if (view.filter_media_images.isChecked) if (view.filter_media_images.isChecked)
result += IMAGES result += TYPE_IMAGES
if (view.filter_media_videos.isChecked) if (view.filter_media_videos.isChecked)
result += VIDEOS result += TYPE_VIDEOS
if (view.filter_media_gifs.isChecked) if (view.filter_media_gifs.isChecked)
result += GIFS result += TYPE_GIFS
activity.config.filterMedia = result activity.config.filterMedia = result
callback(result) callback(result)

View file

@ -11,7 +11,6 @@ import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.views.MyGridLayoutManager import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.DirectoryAdapter import com.simplemobiletools.gallery.adapters.DirectoryAdapter
import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask
import com.simplemobiletools.gallery.extensions.addTempFolderIfNeeded import com.simplemobiletools.gallery.extensions.addTempFolderIfNeeded
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.extensions.getCachedDirectories 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) activity.setupDialogStuff(view, this, R.string.select_destination)
} }
val dirs = activity.getCachedDirectories() activity.getCachedDirectories {
if (dirs.isNotEmpty()) { if (it.isNotEmpty()) {
gotDirectories(activity.addTempFolderIfNeeded(dirs)) activity.runOnUiThread {
}
GetDirectoriesAsynctask(activity, false, false) {
gotDirectories(activity.addTempFolderIfNeeded(it)) gotDirectories(activity.addTempFolderIfNeeded(it))
}.execute() }
}
}
} }
private fun showOtherFolder() { 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) 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()) { if (media.isNotEmpty()) {
activity.runOnUiThread {
gotMedia(media) gotMedia(media)
} }
}
}
GetMediaAsynctask(activity, path, false, true, false) { GetMediaAsynctask(activity, path, true, false, false) {
gotMedia(it) gotMedia(it)
}.execute() }.execute()
} }

View file

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

View file

@ -5,14 +5,6 @@ import android.content.Intent
import android.provider.MediaStore import android.provider.MediaStore
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.view.View 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.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
@ -23,13 +15,7 @@ import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.SimpleActivity import com.simplemobiletools.gallery.activities.SimpleActivity
import com.simplemobiletools.gallery.dialogs.PickDirectoryDialog import com.simplemobiletools.gallery.dialogs.PickDirectoryDialog
import com.simplemobiletools.gallery.helpers.NOMEDIA 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.models.Medium
import com.simplemobiletools.gallery.views.MySquareImageView
import pl.droidsonroids.gif.GifDrawable
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -148,7 +134,7 @@ fun BaseSimpleActivity.removeNoMedia(path: String, callback: (() -> Unit)? = nul
return return
} }
deleteFile(file.toFileDirItem(applicationContext)) { tryDeleteFileDirItem(file.toFileDirItem(applicationContext)) {
callback?.invoke() callback?.invoke()
} }
} }
@ -162,36 +148,12 @@ fun BaseSimpleActivity.toggleFileVisibility(oldPath: String, hide: Boolean, call
filename.substring(1, filename.length) filename.substring(1, filename.length)
} }
val newPath = "$path$filename" val newPath = "$path/$filename"
renameFile(oldPath, newPath) { renameFile(oldPath, newPath) {
callback?.invoke(newPath) callback?.invoke(newPath)
} Thread {
} updateDBMediaPath(oldPath, newPath)
}.start()
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)
}
} }
} }
@ -207,49 +169,12 @@ fun BaseSimpleActivity.tryCopyMoveFilesTo(fileDirItems: ArrayList<FileDirItem>,
} }
} }
fun BaseSimpleActivity.addTempFolderIfNeeded(dirs: ArrayList<Directory>): ArrayList<Directory> { fun BaseSimpleActivity.tryDeleteFileDirItem(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
val directories = ArrayList<Directory>() deleteFile(fileDirItem, allowDeleteFolder) {
val tempFolderPath = config.tempFolderPath callback?.invoke(it)
if (tempFolderPath.isNotEmpty()) {
val newFolder = Directory(tempFolderPath, "", tempFolderPath.getFilenameFromPath(), 0, 0, 0, 0L, isPathOnSD(tempFolderPath)) Thread {
directories.add(newFolder) 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.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import android.view.WindowManager 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.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.SettingsActivity import com.simplemobiletools.gallery.activities.SettingsActivity
import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.helpers.Config import com.simplemobiletools.gallery.databases.GalleryDataBase
import com.simplemobiletools.gallery.helpers.NOMEDIA import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.helpers.SAVE_DIRS_CNT import com.simplemobiletools.gallery.interfaces.DirectoryDao
import com.simplemobiletools.gallery.helpers.SAVE_MEDIA_CNT
import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.models.Directory
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.views.MySquareImageView
import pl.droidsonroids.gif.GifDrawable
import java.io.File import java.io.File
val Context.portrait get() = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT 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.config: Config get() = Config.newInstance(applicationContext)
val Context.galleryDB: GalleryDataBase get() = GalleryDataBase.getInstance(applicationContext)
fun Context.movePinnedDirectoriesToFront(dirs: ArrayList<Directory>): ArrayList<Directory> { fun Context.movePinnedDirectoriesToFront(dirs: ArrayList<Directory>): ArrayList<Directory> {
val foundFolders = ArrayList<Directory>() val foundFolders = ArrayList<Directory>()
val pinnedFolders = config.pinnedFolders val pinnedFolders = config.pinnedFolders
dirs.forEach { dirs.forEach {
if (pinnedFolders.contains(it.path)) if (pinnedFolders.contains(it.path)) {
foundFolders.add(it) foundFolders.add(it)
} }
}
dirs.removeAll(foundFolders) dirs.removeAll(foundFolders)
dirs.addAll(0, 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 return dirs
} }
@ -98,7 +113,6 @@ fun Context.getNoMediaFolders(callback: (folders: ArrayList<String>) -> Unit) {
val sortOrder = "${MediaStore.Files.FileColumns.DATE_MODIFIED} DESC" val sortOrder = "${MediaStore.Files.FileColumns.DATE_MODIFIED} DESC"
var cursor: Cursor? = null var cursor: Cursor? = null
try { try {
cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder) cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
if (cursor?.moveToFirst() == true) { if (cursor?.moveToFirst() == true) {
@ -118,50 +132,35 @@ fun Context.getNoMediaFolders(callback: (folders: ArrayList<String>) -> Unit) {
}.start() }.start()
} }
fun Context.isPathInMediaStore(path: String): Boolean { fun Context.rescanFolderMedia(path: String) {
if (path.startsWith(OTG_PATH)) { Thread {
return false rescanFolderMediaSync(path)
}.start()
} }
val projection = arrayOf(MediaStore.Images.Media.DATE_MODIFIED) fun Context.rescanFolderMediaSync(path: String) {
val uri = MediaStore.Files.getContentUri("external") getCachedMedia(path) {
val selection = "${MediaStore.MediaColumns.DATA} = ?" val cached = it
val selectionArgs = arrayOf(path) GetMediaAsynctask(applicationContext, path, false, false, false) {
val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) Thread {
val newMedia = it
val mediumDao = galleryDB.MediumDao()
mediumDao.insertAll(newMedia)
cursor?.use { cached.forEach {
return cursor.moveToFirst() if (!newMedia.contains(it)) {
mediumDao.deleteMediumPath(it.path)
} }
return false
} }
}.start()
fun Context.updateStoredFolderItems(path: String) {
GetMediaAsynctask(this, path, false, false, false) {
storeFolderItems(path, it)
}.execute() }.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>) { fun Context.storeDirectoryItems(items: ArrayList<Directory>) {
val subList = items.subList(0, Math.min(SAVE_DIRS_CNT, items.size)) Thread {
config.directories = Gson().toJson(subList) galleryDB.DirectoryDao().insertAll(items)
}.start()
} }
fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders: MutableSet<String>): String { fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders: MutableSet<String>): String {
@ -184,3 +183,150 @@ fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders:
dirName 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 { fun String.getFileSignature(): ObjectKey {
val file = File(this) 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.isThisOrParentIncluded(includedPaths: MutableSet<String>) = includedPaths.any { startsWith(it, true) }
fun String.isThisOrParentExcluded(excludedPaths: MutableSet<String>) = excludedPaths.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 { } else {
val options = RequestOptions() val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.transform(GlideRotateTransformation(context!!, degrees)) .transform(GlideRotateTransformation(degrees))
Glide.with(this) Glide.with(this)
.asBitmap() .asBitmap()

View file

@ -6,6 +6,7 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.helpers.BaseConfig import com.simplemobiletools.commons.helpers.BaseConfig
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED 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.commons.helpers.SORT_DESCENDING
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.models.AlbumCover import com.simplemobiletools.gallery.models.AlbumCover
@ -17,7 +18,13 @@ class Config(context: Context) : BaseConfig(context) {
} }
var directorySorting: Int 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() set(order) = prefs.edit().putInt(DIRECTORY_SORT_ORDER, order).apply()
fun saveFileSorting(path: String, value: Int) { 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) { fun removeFileSorting(path: String) {
prefs.edit().remove(SORT_FOLDER_PREFIX + path.toLowerCase()).apply() 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>()) get() = prefs.getStringSet(INCLUDED_FOLDERS, HashSet<String>())
set(includedFolders) = prefs.edit().remove(INCLUDED_FOLDERS).putStringSet(INCLUDED_FOLDERS, includedFolders).apply() 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 var autoplayVideos: Boolean
get() = prefs.getBoolean(AUTOPLAY_VIDEOS, false) get() = prefs.getBoolean(AUTOPLAY_VIDEOS, false)
set(autoplay) = prefs.edit().putBoolean(AUTOPLAY_VIDEOS, autoplay).apply() 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() set(darkBackground) = prefs.edit().putBoolean(DARK_BACKGROUND, darkBackground).apply()
var filterMedia: Int 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() set(filterMedia) = prefs.edit().putInt(FILTER_MEDIA, filterMedia).apply()
var dirColumnCnt: Int 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 private fun getDefaultMediaColumnCount() = context.resources.getInteger(if (scrollHorizontally) R.integer.media_columns_horizontal_scroll
else R.integer.media_columns_vertical_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 var albumCovers: String
get() = prefs.getString(ALBUM_COVERS, "") get() = prefs.getString(ALBUM_COVERS, "")
set(albumCovers) = prefs.edit().putString(ALBUM_COVERS, albumCovers).apply() 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() set(wasNewAppShown) = prefs.edit().putBoolean(WAS_NEW_APP_SHOWN, wasNewAppShown).apply()
var lastFilepickerPath: String var lastFilepickerPath: String
get() = prefs.getString(TEMP_FOLDER_PATH, "") get() = prefs.getString(LAST_FILEPICKER_PATH, "")
set(tempFolderPath) = prefs.edit().putString(TEMP_FOLDER_PATH, tempFolderPath).apply() 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_HORIZONTAL_COLUMN_CNT = "media_horizontal_column_cnt"
const val MEDIA_LANDSCAPE_HORIZONTAL_COLUMN_CNT = "media_landscape_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 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 HIDE_FOLDER_TOOLTIP_SHOWN = "hide_folder_tooltip_shown"
const val EXCLUDED_FOLDERS = "excluded_folders" const val EXCLUDED_FOLDERS = "excluded_folders"
const val INCLUDED_FOLDERS = "included_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 DO_EXTRA_CHECK = "do_extra_check"
const val WAS_NEW_APP_SHOWN = "was_new_app_shown_clock" const val WAS_NEW_APP_SHOWN = "was_new_app_shown_clock"
const val LAST_FILEPICKER_PATH = "last_filepicker_path" const val LAST_FILEPICKER_PATH = "last_filepicker_path"
const val WAS_OTG_HANDLED = "was_otg_handled"
// slideshow // slideshow
const val SLIDESHOW_INTERVAL = "slideshow_interval" 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 SHOW_TEMP_HIDDEN_DURATION = 600000L
const val CLICK_MAX_DURATION = 150 const val CLICK_MAX_DURATION = 150
const val DRAG_THRESHOLD = 8 const val DRAG_THRESHOLD = 8
const val SAVE_DIRS_CNT = 60
const val SAVE_MEDIA_CNT = 80
const val DIRECTORY = "directory" const val DIRECTORY = "directory"
const val MEDIUM = "medium" 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_VIDEO_INTENT = "get_video_intent"
const val GET_ANY_INTENT = "get_any_intent" const val GET_ANY_INTENT = "get_any_intent"
const val SET_WALLPAPER_INTENT = "set_wallpaper_intent" const val SET_WALLPAPER_INTENT = "set_wallpaper_intent"
const val DIRECTORIES = "directories2"
const val IS_VIEW_INTENT = "is_view_intent" const val IS_VIEW_INTENT = "is_view_intent"
const val PICKED_PATHS = "picked_paths" 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_DEVICE_ROTATION = 1
const val ROTATE_BY_ASPECT_RATIO = 2 const val ROTATE_BY_ASPECT_RATIO = 2
// filter media
const val IMAGES = 1
const val VIDEOS = 2
const val GIFS = 4
// view types // view types
const val VIEW_TYPE_GRID = 1 const val VIEW_TYPE_GRID = 1
const val VIEW_TYPE_LIST = 2 const val VIEW_TYPE_LIST = 2
@ -109,6 +100,10 @@ const val EXT_ARTIST = 512
const val EXT_ALBUM = 1024 const val EXT_ALBUM = 1024
// media types // media types
const val TYPE_IMAGE = 1 const val TYPE_IMAGES = 1
const val TYPE_VIDEO = 2 const val TYPE_VIDEOS = 2
const val TYPE_GIF = 3 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 package com.simplemobiletools.gallery.helpers
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Matrix import android.graphics.Matrix
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import java.security.MessageDigest 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 { override fun transform(pool: BitmapPool, bitmap: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
if (rotateRotationAngle % 360 == 0) if (rotateRotationAngle % 360 == 0)
return bitmap 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.OTG_PATH
import com.simplemobiletools.commons.helpers.photoExtensions import com.simplemobiletools.commons.helpers.photoExtensions
import com.simplemobiletools.commons.helpers.videoExtensions 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 com.simplemobiletools.gallery.models.Medium
import java.io.File 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) { class MediaFetcher(val context: Context) {
var shouldStop = false 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> { fun getFilesFrom(curPath: String, isPickImage: Boolean, isPickVideo: Boolean): ArrayList<Medium> {
val filterMedia = context.config.filterMedia val filterMedia = context.config.filterMedia
if (filterMedia == 0) { if (filterMedia == 0) {
return ArrayList() return ArrayList()
} }
if (curPath.startsWith(OTG_PATH)) {
val curMedia = ArrayList<Medium>() val curMedia = ArrayList<Medium>()
getMediaOnOTG(curPath, curMedia, isPickImage, isPickVideo, filterMedia) if (curPath.startsWith(OTG_PATH)) {
return curMedia val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia)
curMedia.addAll(newMedia)
} else { } 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 projection = arrayOf(MediaStore.Images.Media.DATA)
val uri = MediaStore.Files.getContentUri("external") 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 selection = "${getSelectionQuery(path, filterMedia)} ${MediaStore.Images.ImageColumns.BUCKET_ID} IS NOT NULL) GROUP BY (${MediaStore.Images.ImageColumns.BUCKET_ID}"
val selectionArgs = getSelectionArgsQuery(curPath, filterMedia).toTypedArray() val selectionArgs = getSelectionArgsQuery(path, filterMedia).toTypedArray()
return try { return try {
val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
parseCursor(context, cursor, isPickImage, isPickVideo, curPath, filterMedia) parseCursor(cursor, path)
} catch (e: Exception) { } catch (e: Exception) {
ArrayList() ArrayList()
} }
} }
}
private fun getSelectionQuery(path: String, filterMedia: Int): String { private fun getSelectionQuery(path: String, filterMedia: Int): String {
val query = StringBuilder() val query = StringBuilder()
@ -57,19 +60,19 @@ class MediaFetcher(val context: Context) {
} }
query.append("(") query.append("(")
if (filterMedia and IMAGES != 0) { if (filterMedia and TYPE_IMAGES != 0) {
photoExtensions.forEach { photoExtensions.forEach {
query.append("${MediaStore.Images.Media.DATA} LIKE ? OR ") query.append("${MediaStore.Images.Media.DATA} LIKE ? OR ")
} }
} }
if (filterMedia and VIDEOS != 0) { if (filterMedia and TYPE_VIDEOS != 0) {
videoExtensions.forEach { videoExtensions.forEach {
query.append("${MediaStore.Images.Media.DATA} LIKE ? OR ") 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 ?") query.append("${MediaStore.Images.Media.DATA} LIKE ?")
} }
@ -85,29 +88,29 @@ class MediaFetcher(val context: Context) {
args.add("$path/%/%") args.add("$path/%/%")
} }
if (filterMedia and IMAGES != 0) { if (filterMedia and TYPE_IMAGES != 0) {
photoExtensions.forEach { photoExtensions.forEach {
args.add("%$it") args.add("%$it")
} }
} }
if (filterMedia and VIDEOS != 0) { if (filterMedia and TYPE_VIDEOS != 0) {
videoExtensions.forEach { videoExtensions.forEach {
args.add("%$it") args.add("%$it")
} }
} }
if (filterMedia and GIFS != 0) { if (filterMedia and TYPE_GIFS != 0) {
args.add("%.gif") args.add("%.gif")
} }
return args 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 config = context.config
val includedFolders = config.includedFolders val includedFolders = config.includedFolders
val foldersToScan = HashSet<String>() var foldersToScan = ArrayList<String>()
cursor.use { cursor.use {
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
@ -129,25 +132,27 @@ class MediaFetcher(val context: Context) {
} }
} }
val curMedia = ArrayList<Medium>()
val showHidden = config.shouldShowHidden val showHidden = config.shouldShowHidden
val excludedFolders = config.excludedFolders val excludedFolders = config.excludedFolders
foldersToScan.filter { shouldFolderBeVisible(it, excludedFolders, includedFolders, showHidden) }.toList().forEach { foldersToScan = foldersToScan.filter { it.shouldFolderBeVisible(excludedFolders, includedFolders, showHidden) } as ArrayList<String>
fetchFolderContent(it, curMedia, isPickImage, isPickVideo, filterMedia) if (config.isThirdPartyIntent && curPath.isNotEmpty()) {
foldersToScan.add(curPath)
} }
if (config.isThirdPartyIntent && curPath.isNotEmpty() && curMedia.isEmpty()) { return foldersToScan.distinctBy { it.toLowerCase() } as ArrayList<String>
getMediaInFolder(curPath, curMedia, isPickImage, isPickVideo, filterMedia)
} }
Medium.sorting = config.getFileSorting(curPath) private fun addFolder(curFolders: ArrayList<String>, folder: String) {
curMedia.sort()
return curMedia
}
private fun addFolder(curFolders: HashSet<String>, folder: String) {
curFolders.add(folder) 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 val files = File(folder).listFiles() ?: return
for (file in files) { for (file in files) {
if (file.isDirectory) { 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) { private fun fetchFolderContent(path: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int): ArrayList<Medium> {
if (path.startsWith(OTG_PATH)) { return if (path.startsWith(OTG_PATH)) {
getMediaOnOTG(path, curMedia, isPickImage, isPickVideo, filterMedia) getMediaOnOTG(path, isPickImage, isPickVideo, filterMedia)
} else { } else {
getMediaInFolder(path, curMedia, isPickImage, isPickVideo, filterMedia) getMediaInFolder(path, isPickImage, isPickVideo, filterMedia)
} }
} }
private fun groupDirectories(media: ArrayList<Medium>): HashMap<String, ArrayList<Medium>> { private fun getMediaInFolder(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int): ArrayList<Medium> {
val directories = LinkedHashMap<String, ArrayList<Medium>>() val media = ArrayList<Medium>()
val hasOTG = context.hasOTGConnected() && context.config.OTGBasePath.isNotEmpty() val files = File(folder).listFiles() ?: return media
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
val doExtraCheck = context.config.doExtraCheck val doExtraCheck = context.config.doExtraCheck
val showHidden = context.config.shouldShowHidden val showHidden = context.config.shouldShowHidden
@ -220,13 +189,13 @@ class MediaFetcher(val context: Context) {
if (!isImage && !isVideo && !isGif) if (!isImage && !isVideo && !isGif)
continue continue
if (isVideo && (isPickImage || filterMedia and VIDEOS == 0)) if (isVideo && (isPickImage || filterMedia and TYPE_VIDEOS == 0))
continue continue
if (isImage && (isPickVideo || filterMedia and IMAGES == 0)) if (isImage && (isPickVideo || filterMedia and TYPE_IMAGES == 0))
continue continue
if (isGif && filterMedia and GIFS == 0) if (isGif && filterMedia and TYPE_GIFS == 0)
continue continue
if (!showHidden && filename.startsWith('.')) if (!showHidden && filename.startsWith('.'))
@ -240,18 +209,20 @@ class MediaFetcher(val context: Context) {
val dateModified = file.lastModified() val dateModified = file.lastModified()
val type = when { val type = when {
isImage -> TYPE_IMAGE isImage -> TYPE_IMAGES
isVideo -> TYPE_VIDEO isVideo -> TYPE_VIDEOS
else -> TYPE_GIF else -> TYPE_GIFS
} }
val medium = Medium(filename, file.absolutePath, dateModified, dateTaken, size, type) val medium = Medium(null, filename, file.absolutePath, folder, dateModified, dateTaken, size, type)
curMedia.add(medium) media.add(medium)
} }
return media
} }
private fun getMediaOnOTG(folder: String, curMedia: ArrayList<Medium>, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int) { private fun getMediaOnOTG(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int): ArrayList<Medium> {
val files = context.getDocumentFile(folder)?.listFiles() ?: return val media = ArrayList<Medium>()
val files = context.getDocumentFile(folder)?.listFiles() ?: return media
val doExtraCheck = context.config.doExtraCheck val doExtraCheck = context.config.doExtraCheck
val showHidden = context.config.shouldShowHidden val showHidden = context.config.shouldShowHidden
@ -260,7 +231,7 @@ class MediaFetcher(val context: Context) {
break break
} }
val filename = file.name val filename = file.name ?: continue
val isImage = filename.isImageFast() val isImage = filename.isImageFast()
val isVideo = if (isImage) false else filename.isVideoFast() val isVideo = if (isImage) false else filename.isVideoFast()
val isGif = if (isImage || isVideo) false else filename.isGif() val isGif = if (isImage || isVideo) false else filename.isGif()
@ -268,13 +239,13 @@ class MediaFetcher(val context: Context) {
if (!isImage && !isVideo && !isGif) if (!isImage && !isVideo && !isGif)
continue continue
if (isVideo && (isPickImage || filterMedia and VIDEOS == 0)) if (isVideo && (isPickImage || filterMedia and TYPE_VIDEOS == 0))
continue continue
if (isImage && (isPickVideo || filterMedia and IMAGES == 0)) if (isImage && (isPickVideo || filterMedia and TYPE_IMAGES == 0))
continue continue
if (isGif && filterMedia and GIFS == 0) if (isGif && filterMedia and TYPE_GIFS == 0)
continue continue
if (!showHidden && filename.startsWith('.')) if (!showHidden && filename.startsWith('.'))
@ -288,14 +259,16 @@ class MediaFetcher(val context: Context) {
val dateModified = file.lastModified() val dateModified = file.lastModified()
val type = when { val type = when {
isImage -> TYPE_IMAGE isImage -> TYPE_IMAGES
isVideo -> TYPE_VIDEO isVideo -> TYPE_VIDEOS
else -> TYPE_GIF else -> TYPE_GIFS
} }
val path = Uri.decode(file.uri.toString().replaceFirst("${context.config.OTGBasePath}%3A", OTG_PATH)) val path = Uri.decode(file.uri.toString().replaceFirst("${context.config.OTGTreeUri}/document/${context.config.OTGPartition}%3A", OTG_PATH))
val medium = Medium(filename, path, dateModified, dateTaken, size, type) val medium = Medium(null, filename, path, folder, dateModified, dateTaken, size, type)
curMedia.add(medium) 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 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.formatDate
import com.simplemobiletools.commons.extensions.formatSize import com.simplemobiletools.commons.extensions.formatSize
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
import java.io.Serializable 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, @Entity(tableName = "directories", indices = [Index(value = "path", unique = true)])
val size: Long, val isOnSDCard: Boolean) : Serializable, Comparable<Directory> { 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 { companion object {
private val serialVersionUID = -6553345863555455L private const val serialVersionUID = -6553345863555455L
var sorting: Int = 0 var sorting: Int = 0
} }

View file

@ -1,31 +1,42 @@
package com.simplemobiletools.gallery.models 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.formatDate
import com.simplemobiletools.commons.extensions.formatSize import com.simplemobiletools.commons.extensions.formatSize
import com.simplemobiletools.commons.extensions.getMimeType
import com.simplemobiletools.commons.extensions.isDng import com.simplemobiletools.commons.extensions.isDng
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.gallery.helpers.TYPE_GIF import com.simplemobiletools.gallery.helpers.TYPE_GIFS
import com.simplemobiletools.gallery.helpers.TYPE_IMAGE import com.simplemobiletools.gallery.helpers.TYPE_IMAGES
import com.simplemobiletools.gallery.helpers.TYPE_VIDEO import com.simplemobiletools.gallery.helpers.TYPE_VIDEOS
import java.io.Serializable 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 { companion object {
private val serialVersionUID = -6553149366975455L private const val serialVersionUID = -6553149366975455L
var sorting: Int = 0 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 isDng() = path.isDng()
fun getMimeType() = path.getMimeType()
override fun compareTo(other: Medium): Int { override fun compareTo(other: Medium): Int {
var result: Int var result: Int
when { 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.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.google.gson.Gson import com.simplemobiletools.commons.extensions.getFilenameFromPath
import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask import com.simplemobiletools.commons.extensions.getParentPath
import com.simplemobiletools.gallery.extensions.config 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() { class RefreshMediaReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
GetDirectoriesAsynctask(context, false, false) { val path = intent.getStringExtra(REFRESH_PATH) ?: return
context.config.directories = Gson().toJson(it)
}.execute() 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:paddingTop="@dimen/medium_margin"
android:text="@string/last_modified"/> 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> </RadioGroup>
<include <include

View file

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

View file

@ -76,7 +76,7 @@
android:paddingBottom="@dimen/tiny_margin"> android:paddingBottom="@dimen/tiny_margin">
<ImageView <ImageView
android:id="@+id/dir_sd_card" android:id="@+id/dir_location"
android:layout_width="@dimen/sd_card_icon_size" android:layout_width="@dimen/sd_card_icon_size"
android:layout_height="@dimen/sd_card_icon_size" android:layout_height="@dimen/sd_card_icon_size"
android:paddingBottom="@dimen/small_margin" 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="edit">Modifica</string>
<string name="open_camera">Apri fotocamera</string> <string name="open_camera">Apri fotocamera</string>
<string name="hidden">(nascosta)</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="pin_folder">Blocca cartella</string>
<string name="unpin_folder">Sblocca cartella</string> <string name="unpin_folder">Sblocca cartella</string>
<string name="pin_to_the_top">Fissa in alto</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_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_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_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 --> <!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars --> <!-- Short description has to have less than 80 chars -->

View file

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

View file

@ -5,7 +5,7 @@
<string name="edit">Editar</string> <string name="edit">Editar</string>
<string name="open_camera">Abrir câmara</string> <string name="open_camera">Abrir câmara</string>
<string name="hidden">(oculta)</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="pin_folder">Fixar pasta</string>
<string name="unpin_folder">Desafixar pasta</string> <string name="unpin_folder">Desafixar pasta</string>
<string name="pin_to_the_top">Fixar no topo</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_name">Simple Gallery</string>
<string name="app_launcher_name">Galleri</string> <string name="app_launcher_name">Galleri</string>
<string name="edit">Redigera</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="hidden">(dold)</string>
<string name="excluded">(excluded)</string> <string name="excluded">(utesluten)</string>
<string name="pin_folder">Fäst mapp</string> <string name="pin_folder">Fäst mapp</string>
<string name="unpin_folder">Lossa mapp</string> <string name="unpin_folder">Lossa mapp</string>
<string name="pin_to_the_top">Fäst högst upp</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. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.2.31' ext.kotlin_version = '1.2.40'
repositories { repositories {
jcenter() jcenter()
@ -9,7 +9,7 @@ buildscript {
} }
dependencies { 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" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong