diff --git a/CHANGELOG.md b/CHANGELOG.md index 11c107a5e..914c2a716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,37 @@ Changelog ========== +Version 3.8.2 *(2018-04-26)* +---------------------------- + + * Rewrote media caching and new file discovery for better performance + * Many OTG file handling improvements + +Version 3.8.1 *(2018-04-24)* +---------------------------- + + * Rewrote media caching and new file discovery for better performance + * Some OTG file handling improvements + +Version 3.8.0 *(2018-04-22)* +---------------------------- + + * Rewrote media caching for better performance + * Cache all media items, not just 80 per folder + * Some additional performance and stability improvements + +Version 3.7.3 *(2018-04-15)* +---------------------------- + + * Show hidden folders when appropriate + +Version 3.7.2 *(2018-04-14)* +---------------------------- + + * Fix Edit intent handled by other apps + * Hide folders containing ".nomedia" file, even if explicitly included + * Remove sorting by Date Taken until proper implementation + Version 3.7.1 *(2018-04-12)* ---------------------------- diff --git a/app/build.gradle b/app/build.gradle index b9b4cbddb..5584b3101 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' android { compileSdkVersion 27 @@ -10,8 +11,8 @@ android { applicationId "com.simplemobiletools.gallery" minSdkVersion 16 targetSdkVersion 27 - versionCode 171 - versionName "3.7.1" + versionCode 176 + versionName "3.8.2" multiDexEnabled true setProperty("archivesBaseName", "gallery") } @@ -46,13 +47,17 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:3.18.22' - implementation 'com.theartofdev.edmodo:android-image-cropper:2.6.0' + implementation 'com.simplemobiletools:commons:3.19.21' + implementation 'com.theartofdev.edmodo:android-image-cropper:2.7.0' implementation 'com.android.support:multidex:1.0.3' implementation 'it.sephiroth.android.exif:library:1.0.1' implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.12' implementation 'com.github.chrisbanes:PhotoView:2.1.3' + kapt "android.arch.persistence.room:compiler:1.0.0" + implementation "android.arch.persistence.room:runtime:1.0.0" + annotationProcessor "android.arch.persistence.room:compiler:1.0.0" + //implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.9.0' implementation 'com.github.tibbi:subsampling-scale-image-view:v3.10.0-fork' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b4915161f..09a160080 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -205,14 +205,6 @@ android:resource="@xml/provider_paths"/> - - - - - - diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/EditActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/EditActivity.kt index 6f6338795..e54ba3251 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/EditActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/EditActivity.kt @@ -66,7 +66,7 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener if (intent.extras?.containsKey(REAL_FILE_PATH) == true) { val realPath = intent.extras.getString(REAL_FILE_PATH) uri = when { - realPath.startsWith(OTG_PATH) -> Uri.parse(realPath) + realPath.startsWith(OTG_PATH) -> uri realPath.startsWith("file:/") -> Uri.parse(realPath) else -> Uri.fromFile(File(realPath)) } @@ -188,6 +188,15 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener } else if (saveUri.scheme == "content") { var newPath = applicationContext.getRealPathFromURI(saveUri) ?: "" var shouldAppendFilename = true + if (newPath.isEmpty()) { + val filename = applicationContext.getFilenameFromContentUri(saveUri) ?: "" + if (filename.isNotEmpty()) { + val path = if (intent.extras?.containsKey(REAL_FILE_PATH) == true) intent.getStringExtra(REAL_FILE_PATH).getParentPath() else internalStoragePath + newPath = "$path/$filename" + shouldAppendFilename = false + } + } + if (newPath.isEmpty()) { newPath = "$internalStoragePath/${getCurrentFormattedDateTime()}.${saveUri.toString().getFilenameExtension()}" shouldAppendFilename = false diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MainActivity.kt index 677b1caab..33c1ab8f8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MainActivity.kt @@ -12,14 +12,12 @@ import android.view.Menu import android.view.MenuItem import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.Toast import com.simplemobiletools.commons.dialogs.CreateNewFolderDialog import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.helpers.PERMISSION_READ_STORAGE -import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE -import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED -import com.simplemobiletools.commons.helpers.SORT_BY_DATE_TAKEN +import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.models.Release @@ -28,12 +26,15 @@ import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.gallery.BuildConfig import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.adapters.DirectoryAdapter -import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask +import com.simplemobiletools.gallery.databases.GalleryDataBase import com.simplemobiletools.gallery.dialogs.ChangeSortingDialog import com.simplemobiletools.gallery.dialogs.FilterMediaDialog import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.helpers.* +import com.simplemobiletools.gallery.interfaces.DirectoryDao +import com.simplemobiletools.gallery.models.AlbumCover import com.simplemobiletools.gallery.models.Directory +import com.simplemobiletools.gallery.models.Medium import kotlinx.android.synthetic.main.activity_main.* import java.io.* @@ -43,8 +44,6 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { private val LAST_MEDIA_CHECK_PERIOD = 3000L private val NEW_APP_PACKAGE = "com.simplemobiletools.clock" - lateinit var mDirs: ArrayList - private var mIsPickImageIntent = false private var mIsPickVideoIntent = false private var mIsGetImageContentIntent = false @@ -60,7 +59,6 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { private var mLatestMediaDateId = 0L private var mLastMediaHandler = Handler() private var mTempShowHiddenHandler = Handler() - private var mCurrAsyncTask: GetDirectoriesAsynctask? = null private var mZoomListener: MyRecyclerView.MyZoomListener? = null private var mStoredAnimateGifs = true @@ -88,7 +86,6 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { removeTempFolder() directories_refresh_layout.setOnRefreshListener { getDirectories() } - mDirs = ArrayList() storeStateVariables() checkWhatsNewDialog() @@ -104,6 +101,10 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { config.wasNewAppShown = true NewAppDialog(this, NEW_APP_PACKAGE, "Simple Clock") }*/ + + if (!config.wasOTGHandled && hasPermission(PERMISSION_WRITE_STORAGE)) { + checkOTGInclusion() + } } override fun onStart() { @@ -127,7 +128,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { getRecyclerAdapter()?.updateShowMediaCount(config.showMediaCount) } - if (mStoredScrollHorizontally != config.scrollHorizontally || mStoredShowInfoBubble != config.showInfoBubble) { + if (mStoredScrollHorizontally != config.scrollHorizontally) { getRecyclerAdapter()?.updateScrollHorizontally(config.viewTypeFolders != VIEW_TYPE_LIST && config.scrollHorizontally) setupScrollDirection() } @@ -144,6 +145,8 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { directories_horizontal_fastscroller.updateBubbleColors() directories_vertical_fastscroller.updateBubbleColors() + directories_horizontal_fastscroller.allowBubbleDisplay = config.showInfoBubble + directories_vertical_fastscroller.allowBubbleDisplay = config.showInfoBubble directories_refresh_layout.isEnabled = config.enablePullToRefresh invalidateOptionsMenu() directories_empty_text_label.setTextColor(config.textColor) @@ -187,9 +190,8 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { config.temporarilyShowHidden = false mTempShowHiddenHandler.removeCallbacksAndMessages(null) removeTempFolder() - - if (!mDirs.isEmpty()) { - mCurrAsyncTask?.stopFetching() + if (!isChangingConfigurations) { + GalleryDataBase.destroyInstance() } } @@ -244,13 +246,27 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { val newFolder = File(config.tempFolderPath) if (newFolder.exists() && newFolder.isDirectory) { if (newFolder.list()?.isEmpty() == true) { - deleteFile(newFolder.toFileDirItem(applicationContext), true) + toast(String.format(getString(R.string.deleting_folder), config.tempFolderPath), Toast.LENGTH_LONG) + tryDeleteFileDirItem(newFolder.toFileDirItem(applicationContext), true) } } config.tempFolderPath = "" } } + private fun checkOTGInclusion() { + Thread { + if (hasOTGConnected()) { + runOnUiThread { + handleOTGPermission { + config.addIncludedFolder(OTG_PATH) + } + } + config.wasOTGHandled = true + } + }.start() + } + private fun tryLoadGallery() { handlePermission(PERMISSION_WRITE_STORAGE) { if (it) { @@ -274,22 +290,17 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { } mIsGettingDirs = true - val dirs = getCachedDirectories() - if (dirs.isNotEmpty() && !mLoadedInitialPhotos) { - gotDirectories(dirs, true) - } + val getImagesOnly = mIsPickImageIntent || mIsGetImageContentIntent + val getVideosOnly = mIsPickVideoIntent || mIsGetVideoContentIntent - if (!mLoadedInitialPhotos) { - directories_refresh_layout.isRefreshing = true + getCachedDirectories(getVideosOnly, getImagesOnly) { + if (!mLoadedInitialPhotos) { + runOnUiThread { + directories_refresh_layout.isRefreshing = true + } + } + gotDirectories(addTempFolderIfNeeded(it)) } - - mLoadedInitialPhotos = true - mCurrAsyncTask?.stopFetching() - mCurrAsyncTask = GetDirectoriesAsynctask(applicationContext, mIsPickVideoIntent || mIsGetVideoContentIntent, mIsPickImageIntent || mIsGetImageContentIntent) { - mCurrAsyncTask = null - gotDirectories(addTempFolderIfNeeded(it), false) - } - mCurrAsyncTask!!.execute() } private fun showSortingDialog() { @@ -297,13 +308,14 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { if (config.directorySorting and SORT_BY_DATE_MODIFIED > 0 || config.directorySorting and SORT_BY_DATE_TAKEN > 0) { getDirectories() } else { - gotDirectories(mDirs, true) + gotDirectories(getCurrentlyDisplayedDirs()) } } } private fun showFilterMediaDialog() { FilterMediaDialog(this) { + mLoadedInitialPhotos = false directories_refresh_layout.isRefreshing = true getDirectories() } @@ -332,8 +344,9 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { config.viewTypeFolders = it as Int invalidateOptionsMenu() setupLayoutManager() + val dirs = getCurrentlyDisplayedDirs() directories_grid.adapter = null - setupAdapter() + setupAdapter(dirs) } } @@ -348,6 +361,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { } private fun toggleTemporarilyShowHidden(show: Boolean) { + mLoadedInitialPhotos = false config.temporarilyShowHidden = show getDirectories() invalidateOptionsMenu() @@ -359,6 +373,13 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { runOnUiThread { refreshItems() } + + Thread { + val directoryDao = galleryDB.DirectoryDao() + folders.filter { !it.exists() }.forEach { + directoryDao.deleteDirPath(it.absolutePath) + } + }.start() } } @@ -418,7 +439,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { FilePickerDialog(this, internalStoragePath, false, config.shouldShowHidden) { CreateNewFolderDialog(this, it) { config.tempFolderPath = it - gotDirectories(addTempFolderIfNeeded(mDirs), true) + gotDirectories(addTempFolderIfNeeded(getCurrentlyDisplayedDirs())) } } } @@ -550,68 +571,162 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { } } - private fun gotDirectories(newDirs: ArrayList, isFromCache: Boolean) { - /*if (!isFromCache) { - Thread { - checkFolderContentChange(newDirs) - }.start() - }*/ - + private fun gotDirectories(newDirs: ArrayList) { val dirs = getSortedDirectories(newDirs) - directories_refresh_layout.isRefreshing = false - mIsGettingDirs = false - - directories_empty_text_label.beVisibleIf(dirs.isEmpty() && !isFromCache) - directories_empty_text.beVisibleIf(dirs.isEmpty() && !isFromCache) - directories_grid.beVisibleIf(directories_empty_text_label.isGone()) - - val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID - directories_vertical_fastscroller.beVisibleIf(directories_grid.isVisible() && !allowHorizontalScroll) - directories_horizontal_fastscroller.beVisibleIf(directories_grid.isVisible() && allowHorizontalScroll) - - checkLastMediaChanged() - mDirs = dirs + var isPlaceholderVisible = dirs.isEmpty() runOnUiThread { - setupAdapter() + checkPlaceholderVisibility(dirs) + + val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID + directories_vertical_fastscroller.beVisibleIf(directories_grid.isVisible() && !allowHorizontalScroll) + directories_horizontal_fastscroller.beVisibleIf(directories_grid.isVisible() && allowHorizontalScroll) + setupAdapter(dirs) } - if (!isFromCache) { - storeDirectories() - } - } + // 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() - private fun checkFolderContentChange(newDirs: ArrayList) { - newDirs.forEach { - val storedShortDirValue = config.loadFolderMediaShort(it.path) - if (storedShortDirValue != it.toString()) { - config.saveFolderMediaShort(it.path, it.toString()) - if (storedShortDirValue.isNotEmpty()) { - updateStoredFolderItems(it.path) + 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() + + runOnUiThread { + directories_refresh_layout.isRefreshing = false + directories_vertical_fastscroller.measureRecyclerView() + checkPlaceholderVisibility(dirs) + } + checkInvalidDirectories(dirs, directoryDao) + }.start() + } + + private fun checkPlaceholderVisibility(dirs: ArrayList) { + 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) { + var sortedDirs = getSortedDirectories(dirs).clone() as ArrayList + sortedDirs = sortedDirs.distinctBy { it.path.toLowerCase() } as ArrayList + + runOnUiThread { + (directories_grid.adapter as DirectoryAdapter).updateDirs(sortedDirs) } } - private fun storeDirectories() { - if (!config.temporarilyShowHidden && config.tempFolderPath.isEmpty()) { - storeDirectoryItems(mDirs) + private fun createDirectoryFromMedia(path: String, curMedia: ArrayList, albumCovers: ArrayList, hiddenString: String, + includedFolders: MutableSet, isSortingAscending: Boolean): Directory { + var thumbnail = curMedia.firstOrNull { getDoesFilePathExist(it.path) }?.path ?: "" + if (thumbnail.startsWith(OTG_PATH)) { + thumbnail = thumbnail.getOTGPublicPath(applicationContext) } + + albumCovers.forEach { + if (it.path == path && getDoesFilePathExist(it.tmb)) { + thumbnail = it.tmb + } + } + + val mediaTypes = curMedia.getDirMediaTypes() + val dirName = checkAppendingHidden(path, hiddenString, includedFolders) + + val firstItem = curMedia.first() + val lastItem = curMedia.last() + val lastModified = if (isSortingAscending) Math.min(firstItem.modified, lastItem.modified) else Math.max(firstItem.modified, lastItem.modified) + val dateTaken = if (isSortingAscending) Math.min(firstItem.taken, lastItem.taken) else Math.max(firstItem.taken, lastItem.taken) + val size = curMedia.sumByLong { it.size } + return Directory(null, path, thumbnail, dirName, curMedia.size, lastModified, dateTaken, size, getPathLocation(path), mediaTypes) } - private fun setupAdapter() { + private fun setupAdapter(dirs: ArrayList) { val currAdapter = directories_grid.adapter if (currAdapter == null) { initZoomListener() val fastscroller = if (config.scrollHorizontally) directories_horizontal_fastscroller else directories_vertical_fastscroller - DirectoryAdapter(this, mDirs, this, directories_grid, isPickIntent(intent) || isGetAnyContentIntent(intent), fastscroller) { - itemClicked((it as Directory).path) + DirectoryAdapter(this, dirs, this, directories_grid, isPickIntent(intent) || isGetAnyContentIntent(intent), fastscroller) { + val path = (it as Directory).path + if (path != config.tempFolderPath) { + itemClicked(path) + } }.apply { setupZoomListener(mZoomListener) directories_grid.adapter = this } } else { - (currAdapter as DirectoryAdapter).updateDirs(mDirs) + (currAdapter as DirectoryAdapter).updateDirs(dirs) } setupScrollDirection() @@ -639,6 +754,31 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { } } + private fun checkInvalidDirectories(dirs: ArrayList, directoryDao: DirectoryDao) { + val invalidDirs = ArrayList() + dirs.forEach { + if (!getDoesFilePathExist(it.path)) { + invalidDirs.add(it) + } else if (it.path != config.tempFolderPath) { + val children = if (it.path.startsWith(OTG_PATH)) getOTGFolderChildrenNames(it.path) else File(it.path).list()?.asList() + val hasMediaFile = children?.any { it.isImageVideoGif() } ?: false + if (!hasMediaFile) { + invalidDirs.add(it) + } + } + } + + if (invalidDirs.isNotEmpty()) { + dirs.removeAll(invalidDirs) + showSortedDirs(dirs) + invalidDirs.forEach { + directoryDao.deleteDirPath(it.path) + } + } + } + + private fun getCurrentlyDisplayedDirs() = getRecyclerAdapter()?.dirs ?: ArrayList() + private fun getBubbleTextItem(index: Int) = getRecyclerAdapter()?.dirs?.getOrNull(index)?.getBubbleText() ?: "" private fun setupLatestMediaId() { @@ -678,16 +818,14 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { } override fun recheckPinnedFolders() { - gotDirectories(movePinnedDirectoriesToFront(mDirs), true) + gotDirectories(movePinnedDirectoriesToFront(getCurrentlyDisplayedDirs())) } - override fun updateDirectories(directories: ArrayList, refreshList: Boolean) { - if (refreshList) { - gotDirectories(directories, true) - } else { - mDirs = directories - storeDirectories() - } + override fun updateDirectories(directories: ArrayList) { + Thread { + storeDirectoryItems(directories) + removeInvalidDBDirectories() + }.start() } private fun checkWhatsNewDialog() { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MediaActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MediaActivity.kt index cb26c7c38..7ef0ac69b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MediaActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MediaActivity.kt @@ -42,6 +42,7 @@ import com.simplemobiletools.gallery.models.Medium import kotlinx.android.synthetic.main.activity_media.* import java.io.File import java.io.IOException +import java.util.* class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { private val LAST_MEDIA_CHECK_PERIOD = 3000L @@ -94,8 +95,10 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { } storeStateVariables() - if (mShowAll) + + if (mShowAll) { supportActionBar?.setDisplayHomeAsUpEnabled(false) + } media_empty_text.setOnClickListener { showFilterMediaDialog() @@ -117,7 +120,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { getMediaAdapter()?.updateCropThumbnails(config.cropThumbnails) } - if (mStoredScrollHorizontally != config.scrollHorizontally || mStoredShowInfoBubble != config.showInfoBubble) { + if (mStoredScrollHorizontally != config.scrollHorizontally) { getMediaAdapter()?.updateScrollHorizontally(config.viewTypeFiles != VIEW_TYPE_LIST || !config.scrollHorizontally) setupScrollDirection() } @@ -134,6 +137,8 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { media_horizontal_fastscroller.updateBubbleColors() media_vertical_fastscroller.updateBubbleColors() + media_horizontal_fastscroller.allowBubbleDisplay = config.showInfoBubble + media_vertical_fastscroller.allowBubbleDisplay = config.showInfoBubble media_refresh_layout.isEnabled = config.enablePullToRefresh tryloadGallery() invalidateOptionsMenu() @@ -257,11 +262,13 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, object : MenuItemCompat.OnActionExpandListener { override fun onMenuItemActionExpand(item: MenuItem?): Boolean { mIsSearchOpen = true + media_refresh_layout.isEnabled = false return true } override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { mIsSearchOpen = false + media_refresh_layout.isEnabled = config.enablePullToRefresh return true } }) @@ -272,7 +279,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { val filtered = mMedia.filter { it.name.contains(text, true) } as ArrayList filtered.sortBy { !it.name.startsWith(text, true) } runOnUiThread { - (media_grid.adapter as? MediaAdapter)?.updateMedia(filtered) + getMediaAdapter()?.updateMedia(filtered) } }.start() } @@ -365,12 +372,14 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { private fun showSortingDialog() { ChangeSortingDialog(this, false, !config.showAll, mPath) { + mLoadedInitialPhotos = false getMedia() } } private fun showFilterMediaDialog() { FilterMediaDialog(this) { + mLoadedInitialPhotos = false media_refresh_layout.isRefreshing = true getMedia() } @@ -378,8 +387,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { private fun toggleFilenameVisibility() { config.displayFileNames = !config.displayFileNames - if (media_grid.adapter != null) - getMediaAdapter()?.updateDisplayFilenames(config.displayFileNames) + getMediaAdapter()?.updateDisplayFilenames(config.displayFileNames) } private fun switchToFolderView() { @@ -442,7 +450,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { private fun deleteDirectoryIfEmpty() { val fileDirItem = FileDirItem(mPath, mPath.getFilenameFromPath()) if (config.deleteEmptyFolders && !fileDirItem.isDownloadsFolder() && fileDirItem.isDirectory && fileDirItem.getProperFileCount(applicationContext, true) == 0) { - deleteFile(fileDirItem, true) + tryDeleteFileDirItem(fileDirItem, true) } } @@ -452,16 +460,23 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { } mIsGettingMedia = true - val media = getCachedMedia(mPath) - if (media.isNotEmpty() && !mLoadedInitialPhotos) { - gotMedia(media, true) + if (!mLoadedInitialPhotos) { + getCachedMedia(mPath, mIsGetVideoIntent, mIsGetImageIntent) { + if (it.isEmpty()) { + runOnUiThread { + media_refresh_layout.isRefreshing = true + } + } else { + gotMedia(it, true) + } + } } else { media_refresh_layout.isRefreshing = true } mLoadedInitialPhotos = true mCurrAsyncTask?.stopFetching() - mCurrAsyncTask = GetMediaAsynctask(applicationContext, mPath, mIsGetVideoIntent, mIsGetImageIntent, mShowAll) { + mCurrAsyncTask = GetMediaAsynctask(applicationContext, mPath, mIsGetImageIntent, mIsGetVideoIntent, mShowAll) { gotMedia(it) } mCurrAsyncTask!!.execute() @@ -470,10 +485,18 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { private fun isDirEmpty(): Boolean { return if (mMedia.size <= 0 && config.filterMedia > 0) { deleteDirectoryIfEmpty() + deleteDBDirectory() finish() true - } else + } else { false + } + } + + private fun deleteDBDirectory() { + Thread { + galleryDB.DirectoryDao().deleteDirPath(mPath) + }.start() } private fun tryToggleTemporarilyShowHidden() { @@ -487,6 +510,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { } private fun toggleTemporarilyShowHidden(show: Boolean) { + mLoadedInitialPhotos = false config.temporarilyShowHidden = show getMedia() invalidateOptionsMenu() @@ -620,61 +644,60 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { } private fun gotMedia(media: ArrayList, isFromCache: Boolean = false) { + val mediaToInsert = media.clone() as ArrayList Thread { mLatestMediaId = getLatestMediaId() mLatestMediaDateId = getLatestMediaByDateId() + if (!isFromCache) { + galleryDB.MediumDao().insertAll(mediaToInsert) + } }.start() mIsGettingMedia = false - media_refresh_layout.isRefreshing = false - - media_empty_text_label.beVisibleIf(media.isEmpty() && !isFromCache) - media_empty_text.beVisibleIf(media.isEmpty() && !isFromCache) - media_grid.beVisibleIf(media_empty_text_label.isGone()) - - val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID - media_vertical_fastscroller.beVisibleIf(media_grid.isVisible() && !allowHorizontalScroll) - media_horizontal_fastscroller.beVisibleIf(media_grid.isVisible() && allowHorizontalScroll) - checkLastMediaChanged() - mMedia = media + runOnUiThread { + media_refresh_layout.isRefreshing = false + media_empty_text_label.beVisibleIf(media.isEmpty() && !isFromCache) + media_empty_text.beVisibleIf(media.isEmpty() && !isFromCache) + media_grid.beVisibleIf(media_empty_text_label.isGone()) + + val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID + media_vertical_fastscroller.beVisibleIf(media_grid.isVisible() && !allowHorizontalScroll) + media_horizontal_fastscroller.beVisibleIf(media_grid.isVisible() && allowHorizontalScroll) + setupAdapter() } - - if (!isFromCache) { - storeFolder() - } } - private fun storeFolder() { - if (!config.temporarilyShowHidden) { - Thread { - storeFolderItems(mPath, mMedia) - }.start() - } - } - - override fun deleteFiles(fileDirItems: ArrayList) { + override fun tryDeleteFiles(fileDirItems: ArrayList) { val filtered = fileDirItems.filter { it.path.isImageVideoGif() } as ArrayList deleteFiles(filtered) { if (!it) { toast(R.string.unknown_error_occurred) - } else if (mMedia.isEmpty()) { + return@deleteFiles + } + + mMedia.removeAll { filtered.map { it.path }.contains(it.path) } + + Thread { + val mediumDao = galleryDB.MediumDao() + filtered.forEach { + mediumDao.deleteMediumPath(it.path) + } + }.start() + + if (mMedia.isEmpty()) { deleteDirectoryIfEmpty() + deleteDBDirectory() finish() - } else { - updateStoredDirectories() } } } override fun refreshItems() { getMedia() - Handler().postDelayed({ - getMedia() - }, 1000) } override fun selectedPaths(paths: ArrayList) { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoVideoActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoVideoActivity.kt index f3c4084bd..3cd1fcecc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoVideoActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoVideoActivity.kt @@ -85,12 +85,12 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList val bundle = Bundle() val file = File(mUri.toString()) val type = when { - file.isImageFast() -> TYPE_IMAGE - file.isVideoFast() -> TYPE_VIDEO - else -> TYPE_GIF + file.isImageFast() -> TYPE_IMAGES + file.isVideoFast() -> TYPE_VIDEOS + else -> TYPE_GIFS } - mMedium = Medium(getFilenameFromUri(mUri!!), mUri.toString(), 0, 0, file.length(), type) + mMedium = Medium(null, getFilenameFromUri(mUri!!), mUri.toString(), mUri!!.path.getParentPath(), 0, 0, file.length(), type) supportActionBar?.title = mMedium!!.name bundle.putSerializable(MEDIUM, mMedium) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SettingsActivity.kt index 939e68f1d..e50c30caf 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SettingsActivity.kt @@ -203,6 +203,11 @@ class SettingsActivity : SimpleActivity() { settings_scroll_horizontally_holder.setOnClickListener { settings_scroll_horizontally.toggle() config.scrollHorizontally = settings_scroll_horizontally.isChecked + + if (config.scrollHorizontally) { + config.enablePullToRefresh = false + settings_enable_pull_to_refresh.isChecked = false + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt index ab5e68d4a..1c6a30d40 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt @@ -45,7 +45,10 @@ import com.simplemobiletools.gallery.fragments.ViewPagerFragment import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.models.Medium import kotlinx.android.synthetic.main.activity_medium.* -import java.io.* +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream +import java.io.OutputStream import java.util.* class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, ViewPagerFragment.FragmentListener { @@ -197,7 +200,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View showSystemUI() - mDirectory = mPath.getParentPath().trimEnd('/') + mDirectory = mPath.getParentPath() if (mDirectory.startsWith(OTG_PATH.trimEnd('/'))) { mDirectory += "/" } @@ -527,11 +530,10 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View } } - val newFile = File(newPath) - val tmpFile = File(filesDir, ".tmp_${newPath.getFilenameFromPath()}") - + val tmpPath = "$filesDir/.tmp_${newPath.getFilenameFromPath()}" + val tmpFileDirItem = FileDirItem(tmpPath, tmpPath.getFilenameFromPath()) try { - getFileOutputStream(tmpFile.toFileDirItem(applicationContext)) { + getFileOutputStream(tmpFileDirItem) { if (it == null) { toast(R.string.unknown_error_occurred) return@getFileOutputStream @@ -539,22 +541,24 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View val oldLastModified = getCurrentFile().lastModified() if (oldPath.isJpg()) { - copyFile(getCurrentFile(), tmpFile) - saveExifRotation(ExifInterface(tmpFile.absolutePath), mRotationDegrees) + copyFile(getCurrentPath(), tmpPath) + saveExifRotation(ExifInterface(tmpPath), mRotationDegrees) } else { - val bitmap = BitmapFactory.decodeFile(oldPath) - saveFile(tmpFile, bitmap, it as FileOutputStream) + val inputstream = getFileInputStreamSync(oldPath) + val bitmap = BitmapFactory.decodeStream(inputstream) + saveFile(tmpPath, bitmap, it as FileOutputStream) } - if (tmpFile.length() > 0 && getDoesFilePathExist(newPath)) { - deleteFile(FileDirItem(newPath, newPath.getFilenameFromPath())) + if (getDoesFilePathExist(newPath)) { + tryDeleteFileDirItem(FileDirItem(newPath, newPath.getFilenameFromPath())) } - copyFile(tmpFile, newFile) + + copyFile(tmpPath, newPath) scanPath(newPath) toast(R.string.file_saved) if (config.keepLastModified) { - newFile.setLastModified(oldLastModified) + File(newPath).setLastModified(oldLastModified) updateLastModified(newPath, oldLastModified) } @@ -575,41 +579,45 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View } catch (e: Exception) { showErrorToast(e) } finally { - deleteFile(FileDirItem(tmpFile.absolutePath, tmpFile.absolutePath.getFilenameFromPath())) + tryDeleteFileDirItem(tmpFileDirItem) } } @TargetApi(Build.VERSION_CODES.N) private fun tryRotateByExif(path: String): Boolean { - return if (saveImageRotation(path, mRotationDegrees)) { - mRotationDegrees = 0 - invalidateOptionsMenu() - toast(R.string.file_saved) - true - } else { + return try { + if (saveImageRotation(path, mRotationDegrees)) { + mRotationDegrees = 0 + invalidateOptionsMenu() + toast(R.string.file_saved) + true + } else { + false + } + } catch (e: Exception) { + showErrorToast(e) false } } - private fun copyFile(source: File, destination: File) { + private fun copyFile(source: String, destination: String) { var inputStream: InputStream? = null var out: OutputStream? = null try { - val fileDocument = if (isPathOnSD(destination.absolutePath)) getDocumentFile(destination.parent) else null - out = getFileOutputStreamSync(destination.absolutePath, source.getMimeType(), fileDocument) - inputStream = FileInputStream(source) - inputStream.copyTo(out!!) + out = getFileOutputStreamSync(destination, source.getMimeType()) + inputStream = getFileInputStreamSync(source) + inputStream?.copyTo(out!!) } finally { inputStream?.close() out?.close() } } - private fun saveFile(file: File, bitmap: Bitmap, out: FileOutputStream) { + private fun saveFile(path: String, bitmap: Bitmap, out: FileOutputStream) { val matrix = Matrix() matrix.postRotate(mRotationDegrees.toFloat()) val bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) - bmp.compress(file.absolutePath.getCompressionFormat(), 90, out) + bmp.compress(path.getCompressionFormat(), 90, out) } private fun isShowHiddenFlagNeeded(): Boolean { @@ -736,7 +744,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View private fun deleteConfirmed() { val path = getCurrentMedia()[mPos].path - deleteFile(FileDirItem(path, path.getFilenameFromPath())) { + tryDeleteFileDirItem(FileDirItem(path, path.getFilenameFromPath())) { refreshViewPager() } } @@ -752,8 +760,16 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View } private fun renameFile() { - RenameItemDialog(this, getCurrentPath()) { - getCurrentMedia()[mPos].path = it + val oldPath = getCurrentPath() + RenameItemDialog(this, oldPath) { + getCurrentMedia()[mPos].apply { + path = it + name = it.getFilenameFromPath() + } + + Thread { + updateDBMediaPath(oldPath, it) + }.start() updateActionbarTitle() } } @@ -814,7 +830,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View private fun deleteDirectoryIfEmpty() { val fileDirItem = FileDirItem(mDirectory, mDirectory.getFilenameFromPath(), getIsPathDirectory(mDirectory)) if (config.deleteEmptyFolders && !fileDirItem.isDownloadsFolder() && fileDirItem.isDirectory && fileDirItem.getProperFileCount(applicationContext, true) == 0) { - deleteFile(fileDirItem, true) + tryDeleteFileDirItem(fileDirItem, true) } scanPath(mDirectory) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/DirectoryAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/DirectoryAdapter.kt index f290cdc87..06d14443c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/DirectoryAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/DirectoryAdapter.kt @@ -12,6 +12,7 @@ import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.PropertiesDialog import com.simplemobiletools.commons.dialogs.RenameItemDialog import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.MyRecyclerView @@ -19,10 +20,7 @@ import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.dialogs.ExcludeFolderDialog import com.simplemobiletools.gallery.dialogs.PickMediumDialog import com.simplemobiletools.gallery.extensions.* -import com.simplemobiletools.gallery.helpers.TYPE_GIF -import com.simplemobiletools.gallery.helpers.TYPE_IMAGE -import com.simplemobiletools.gallery.helpers.TYPE_VIDEO -import com.simplemobiletools.gallery.helpers.VIEW_TYPE_LIST +import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.models.AlbumCover import com.simplemobiletools.gallery.models.Directory import kotlinx.android.synthetic.main.directory_item_list.view.* @@ -155,7 +153,8 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList() + val showHidden = activity.config.shouldShowHidden selectedPositions.forEach { - val dir = File(dirs[it].path) - paths.addAll(dir.listFiles().filter { !activity.getIsPathDirectory(it.absolutePath) && it.absolutePath.isImageVideoGif() }.map { it.absolutePath }) + val path = dirs[it].path + if (path.startsWith(OTG_PATH)) { + paths.addAll(getOTGFilePaths(path, showHidden)) + } else { + File(path).listFiles()?.filter { + !activity.getIsPathDirectory(it.absolutePath) && it.isImageVideoGif() && (showHidden || !it.name.startsWith('.')) + }?.mapTo(paths, { it.absolutePath }) + } } val fileDirItems = paths.map { FileDirItem(it, it.getFilenameFromPath()) } as ArrayList @@ -297,6 +299,17 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList { + val paths = ArrayList() + activity.getOTGFolderChildren(path)?.forEach { + if (!it.isDirectory && it.name.isImageVideoGif() && (showHidden || !it.name.startsWith('.'))) { + val relativePath = it.uri.path.substringAfterLast("${activity.config.OTGPartition}:") + paths.add("$OTG_PATH$relativePath") + } + } + return paths + } + private fun askConfirmDelete() { if (config.skipDeleteConfirmation) { deleteFiles() @@ -416,14 +429,18 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList TYPE_IMAGE - directory.tmb.isVideoFast() -> TYPE_VIDEO - else -> TYPE_GIF + directory.tmb.isImageFast() -> TYPE_IMAGES + directory.tmb.isVideoFast() -> TYPE_VIDEOS + else -> TYPE_GIFS } activity.loadImage(thumbnailType, directory.tmb, dir_thumbnail, scrollHorizontally, animateGifs, cropThumbnails) dir_pin.beVisibleIf(pinnedFolders.contains(directory.path)) - dir_sd_card.beVisibleIf(directory.isOnSDCard) + dir_location.beVisibleIf(directory.location != LOCAITON_INTERNAL) + if (dir_location.isVisible()) { + dir_location.setImageResource(if (directory.location == LOCATION_SD) R.drawable.ic_sd_card else R.drawable.ic_usb) + } + photo_cnt.beVisibleIf(showMediaCount) if (isListViewType) { @@ -431,7 +448,7 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList, refreshList: Boolean) + fun updateDirectories(directories: ArrayList) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MediaAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MediaAdapter.kt index 5b8fbd420..a20c58a82 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MediaAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MediaAdapter.kt @@ -148,7 +148,12 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList, } private fun renameFile() { - RenameItemDialog(activity, getCurrentPath()) { + val oldPath = getCurrentPath() + RenameItemDialog(activity, oldPath) { + Thread { + activity.updateDBMediaPath(oldPath, it) + }.start() + activity.runOnUiThread { listener?.refreshItems() finishActMode() @@ -158,7 +163,6 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList, private fun editFile() { activity.openEditor(getCurrentPath()) - finishActMode() } private fun toggleFileVisibility(hide: Boolean) { @@ -188,7 +192,8 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList, val fileDirItems = paths.map { FileDirItem(it, it.getFilenameFromPath()) } as ArrayList activity.tryCopyMoveFilesTo(fileDirItems, isCopyOperation) { config.tempFolderPath = "" - activity.applicationContext.updateStoredFolderItems(it) + activity.applicationContext.rescanFolderMedia(it) + activity.applicationContext.rescanFolderMedia(fileDirItems.first().getParentPath()) if (!isCopyOperation) { listener?.refreshItems() } @@ -236,9 +241,8 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList, } media.removeAll(removeMedia) - listener?.deleteFiles(fileDirItems) + listener?.tryDeleteFiles(fileDirItems) removeSelectedItems() - updateStoredFolderItems() } } @@ -248,14 +252,6 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList, return selectedMedia } - private fun updateStoredFolderItems() { - Thread { - if (media.isNotEmpty()) { - activity.applicationContext.storeFolderItems(media.first().path.getParentPath().trimEnd('/'), media as ArrayList) - } - }.start() - } - fun updateMedia(newMedia: ArrayList) { if (newMedia.hashCode() != currentMediaHash) { currentMediaHash = newMedia.hashCode() @@ -331,7 +327,7 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList, interface MediaOperationsListener { fun refreshItems() - fun deleteFiles(fileDirItems: ArrayList) + fun tryDeleteFiles(fileDirItems: ArrayList) fun selectedPaths(paths: ArrayList) } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetDirectoriesAsynctask.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetDirectoriesAsynctask.kt deleted file mode 100644 index 19f73ddde..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetDirectoriesAsynctask.kt +++ /dev/null @@ -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) -> Unit) : AsyncTask>() { - private val mediaFetcher = MediaFetcher(context) - - override fun doInBackground(vararg params: Void): ArrayList { - if (!context.hasPermission(PERMISSION_WRITE_STORAGE)) { - return ArrayList() - } - - val config = context.config - val groupedMedia = mediaFetcher.getMediaByDirectories(isPickVideo, isPickImage) - val directories = ArrayList() - 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) { - super.onPostExecute(dirs) - callback(dirs) - } - - fun stopFetching() { - mediaFetcher.shouldStop = true - cancel(true) - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetMediaAsynctask.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetMediaAsynctask.kt index a3b8a2b5d..8f82b4c00 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetMediaAsynctask.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetMediaAsynctask.kt @@ -7,18 +7,18 @@ import com.simplemobiletools.gallery.helpers.MediaFetcher import com.simplemobiletools.gallery.models.Medium import java.util.* -class GetMediaAsynctask(val context: Context, val mPath: String, val isPickVideo: Boolean = false, val isPickImage: Boolean = false, +class GetMediaAsynctask(val context: Context, val mPath: String, val isPickImage: Boolean = false, val isPickVideo: Boolean = false, val showAll: Boolean, val callback: (media: ArrayList) -> Unit) : AsyncTask>() { private val mediaFetcher = MediaFetcher(context) override fun doInBackground(vararg params: Void): ArrayList { return if (showAll) { - val mediaMap = mediaFetcher.getMediaByDirectories(isPickVideo, isPickImage) + val foldersToScan = mediaFetcher.getFoldersToScan("") val media = ArrayList() - - mediaMap.values.forEach { - media.addAll(it) + for (folder in foldersToScan) { + val newMedia = mediaFetcher.getFilesFrom(folder, isPickImage, isPickVideo) + media.addAll(newMedia) } Medium.sorting = context.config.getFileSorting("") diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/databases/GalleryDataBase.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/databases/GalleryDataBase.kt new file mode 100644 index 000000000..cd3d82570 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/databases/GalleryDataBase.kt @@ -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 + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ChangeSortingDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ChangeSortingDialog.kt index efbf4fe0e..5d4395988 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ChangeSortingDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ChangeSortingDialog.kt @@ -29,8 +29,8 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, val isDirectorySorti .setPositiveButton(R.string.ok, this) .setNegativeButton(R.string.cancel, null) .create().apply { - activity.setupDialogStuff(view, this, R.string.sort_by) - } + activity.setupDialogStuff(view, this, R.string.sort_by) + } currSorting = if (isDirectorySorting) config.directorySorting else config.getFileSorting(path) setupSortRadio() @@ -44,7 +44,6 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, val isDirectorySorti currSorting and SORT_BY_PATH != 0 -> sortingRadio.sorting_dialog_radio_path currSorting and SORT_BY_SIZE != 0 -> sortingRadio.sorting_dialog_radio_size currSorting and SORT_BY_DATE_MODIFIED != 0 -> sortingRadio.sorting_dialog_radio_last_modified - currSorting and SORT_BY_DATE_TAKEN != 0 -> sortingRadio.sorting_dialog_radio_date_taken else -> sortingRadio.sorting_dialog_radio_name } sortBtn.isChecked = true @@ -66,8 +65,7 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, val isDirectorySorti R.id.sorting_dialog_radio_name -> SORT_BY_NAME R.id.sorting_dialog_radio_path -> SORT_BY_PATH R.id.sorting_dialog_radio_size -> SORT_BY_SIZE - R.id.sorting_dialog_radio_last_modified -> SORT_BY_DATE_MODIFIED - else -> SORT_BY_DATE_TAKEN + else -> SORT_BY_DATE_MODIFIED } if (view.sorting_dialog_radio_order.checkedRadioButtonId == R.id.sorting_dialog_radio_descending) { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/FilterMediaDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/FilterMediaDialog.kt index b1f10323f..ec3cd7faa 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/FilterMediaDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/FilterMediaDialog.kt @@ -5,9 +5,9 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.extensions.config -import com.simplemobiletools.gallery.helpers.GIFS -import com.simplemobiletools.gallery.helpers.IMAGES -import com.simplemobiletools.gallery.helpers.VIDEOS +import com.simplemobiletools.gallery.helpers.TYPE_GIFS +import com.simplemobiletools.gallery.helpers.TYPE_IMAGES +import com.simplemobiletools.gallery.helpers.TYPE_VIDEOS import kotlinx.android.synthetic.main.dialog_filter_media.view.* class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) { @@ -16,27 +16,27 @@ class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result: init { val filterMedia = activity.config.filterMedia view.apply { - filter_media_images.isChecked = filterMedia and IMAGES != 0 - filter_media_videos.isChecked = filterMedia and VIDEOS != 0 - filter_media_gifs.isChecked = filterMedia and GIFS != 0 + filter_media_images.isChecked = filterMedia and TYPE_IMAGES != 0 + filter_media_videos.isChecked = filterMedia and TYPE_VIDEOS != 0 + filter_media_gifs.isChecked = filterMedia and TYPE_GIFS != 0 } AlertDialog.Builder(activity) .setPositiveButton(R.string.ok, { dialog, which -> dialogConfirmed() }) .setNegativeButton(R.string.cancel, null) .create().apply { - activity.setupDialogStuff(view, this, R.string.filter_media) - } + activity.setupDialogStuff(view, this, R.string.filter_media) + } } private fun dialogConfirmed() { var result = 0 if (view.filter_media_images.isChecked) - result += IMAGES + result += TYPE_IMAGES if (view.filter_media_videos.isChecked) - result += VIDEOS + result += TYPE_VIDEOS if (view.filter_media_gifs.isChecked) - result += GIFS + result += TYPE_GIFS activity.config.filterMedia = result callback(result) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickDirectoryDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickDirectoryDialog.kt index 471237111..372a0483c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickDirectoryDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickDirectoryDialog.kt @@ -11,7 +11,6 @@ import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.views.MyGridLayoutManager import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.adapters.DirectoryAdapter -import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask import com.simplemobiletools.gallery.extensions.addTempFolderIfNeeded import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.extensions.getCachedDirectories @@ -37,17 +36,16 @@ class PickDirectoryDialog(val activity: BaseSimpleActivity, val sourcePath: Stri .setNegativeButton(R.string.cancel, null) .setNeutralButton(R.string.other_folder, { dialogInterface, i -> showOtherFolder() }) .create().apply { - activity.setupDialogStuff(view, this, R.string.select_destination) - } + activity.setupDialogStuff(view, this, R.string.select_destination) + } - val dirs = activity.getCachedDirectories() - if (dirs.isNotEmpty()) { - gotDirectories(activity.addTempFolderIfNeeded(dirs)) + activity.getCachedDirectories { + if (it.isNotEmpty()) { + activity.runOnUiThread { + gotDirectories(activity.addTempFolderIfNeeded(it)) + } + } } - - GetDirectoriesAsynctask(activity, false, false) { - gotDirectories(activity.addTempFolderIfNeeded(it)) - }.execute() } private fun showOtherFolder() { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickMediumDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickMediumDialog.kt index fd5abf97b..a717c3cfa 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickMediumDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickMediumDialog.kt @@ -33,15 +33,19 @@ class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val c .setNegativeButton(R.string.cancel, null) .setNeutralButton(R.string.other_folder, { dialogInterface, i -> showOtherFolder() }) .create().apply { - activity.setupDialogStuff(view, this, R.string.select_photo) + activity.setupDialogStuff(view, this, R.string.select_photo) + } + + activity.getCachedMedia(path) { + val media = it.filter { !it.isVideo() } as ArrayList + if (media.isNotEmpty()) { + activity.runOnUiThread { + gotMedia(media) + } + } } - val media = activity.getCachedMedia(path).filter { !it.isVideo() } as ArrayList - if (media.isNotEmpty()) { - gotMedia(media) - } - - GetMediaAsynctask(activity, path, false, true, false) { + GetMediaAsynctask(activity, path, true, false, false) { gotMedia(it) }.execute() } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/SaveAsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/SaveAsDialog.kt index 821706d9b..3199ff9d7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/SaveAsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/SaveAsDialog.kt @@ -7,15 +7,14 @@ import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.gallery.R import kotlinx.android.synthetic.main.dialog_save_as.view.* -import java.io.File class SaveAsDialog(val activity: BaseSimpleActivity, val path: String, val appendFilename: Boolean, val callback: (savePath: String) -> Unit) { init { - var realPath = path.getParentPath().trimEnd('/') + var realPath = path.getParentPath() val view = activity.layoutInflater.inflate(R.layout.dialog_save_as, null).apply { - save_as_path.text = activity.humanizePath(realPath) + save_as_path.text = "${activity.humanizePath(realPath).trimEnd('/')}/" val fullName = path.getFilenameFromPath() val dotAt = fullName.lastIndexOf(".") @@ -60,20 +59,21 @@ class SaveAsDialog(val activity: BaseSimpleActivity, val path: String, val appen return@setOnClickListener } - val newFile = File(realPath, "$filename.$extension") - if (!newFile.name.isAValidFilename()) { + val newFilename = "$filename.$extension" + val newPath = "${realPath.trimEnd('/')}/$newFilename" + if (!newFilename.isAValidFilename()) { activity.toast(R.string.filename_invalid_characters) return@setOnClickListener } - if (newFile.exists()) { - val title = String.format(activity.getString(R.string.file_already_exists_overwrite), newFile.name) + if (activity.getDoesFilePathExist(newPath)) { + val title = String.format(activity.getString(R.string.file_already_exists_overwrite), newFilename) ConfirmationDialog(activity, title) { - callback(newFile.absolutePath) + callback(newPath) dismiss() } } else { - callback(newFile.absolutePath) + callback(newPath) dismiss() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Activity.kt index f7073185f..a311c9a22 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Activity.kt @@ -5,14 +5,6 @@ import android.content.Intent import android.provider.MediaStore import android.support.v7.app.AppCompatActivity import android.view.View -import android.widget.ImageView -import com.bumptech.glide.Glide -import com.bumptech.glide.load.DecodeFormat -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions -import com.bumptech.glide.request.RequestOptions -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* @@ -23,13 +15,7 @@ import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.activities.SimpleActivity import com.simplemobiletools.gallery.dialogs.PickDirectoryDialog import com.simplemobiletools.gallery.helpers.NOMEDIA -import com.simplemobiletools.gallery.helpers.TYPE_GIF -import com.simplemobiletools.gallery.helpers.TYPE_IMAGE -import com.simplemobiletools.gallery.helpers.TYPE_VIDEO -import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.models.Medium -import com.simplemobiletools.gallery.views.MySquareImageView -import pl.droidsonroids.gif.GifDrawable import java.io.File import java.util.* @@ -148,7 +134,7 @@ fun BaseSimpleActivity.removeNoMedia(path: String, callback: (() -> Unit)? = nul return } - deleteFile(file.toFileDirItem(applicationContext)) { + tryDeleteFileDirItem(file.toFileDirItem(applicationContext)) { callback?.invoke() } } @@ -162,36 +148,12 @@ fun BaseSimpleActivity.toggleFileVisibility(oldPath: String, hide: Boolean, call filename.substring(1, filename.length) } - val newPath = "$path$filename" + val newPath = "$path/$filename" renameFile(oldPath, newPath) { callback?.invoke(newPath) - } -} - -fun Activity.loadImage(type: Int, path: String, target: MySquareImageView, horizontalScroll: Boolean, animateGifs: Boolean, cropThumbnails: Boolean) { - target.isHorizontalScrolling = horizontalScroll - if (type == TYPE_IMAGE || type == TYPE_VIDEO) { - if (type == TYPE_IMAGE && path.isPng()) { - loadPng(path, target, cropThumbnails) - } else { - loadJpg(path, target, cropThumbnails) - } - } else if (type == TYPE_GIF) { - try { - val gifDrawable = GifDrawable(path) - target.setImageDrawable(gifDrawable) - if (animateGifs) { - gifDrawable.start() - } else { - gifDrawable.stop() - } - - target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER - } catch (e: Exception) { - loadJpg(path, target, cropThumbnails) - } catch (e: OutOfMemoryError) { - loadJpg(path, target, cropThumbnails) - } + Thread { + updateDBMediaPath(oldPath, newPath) + }.start() } } @@ -207,49 +169,12 @@ fun BaseSimpleActivity.tryCopyMoveFilesTo(fileDirItems: ArrayList, } } -fun BaseSimpleActivity.addTempFolderIfNeeded(dirs: ArrayList): ArrayList { - val directories = ArrayList() - val tempFolderPath = config.tempFolderPath - if (tempFolderPath.isNotEmpty()) { - val newFolder = Directory(tempFolderPath, "", tempFolderPath.getFilenameFromPath(), 0, 0, 0, 0L, isPathOnSD(tempFolderPath)) - directories.add(newFolder) +fun BaseSimpleActivity.tryDeleteFileDirItem(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { + deleteFile(fileDirItem, allowDeleteFolder) { + callback?.invoke(it) + + Thread { + galleryDB.MediumDao().deleteMediumPath(fileDirItem.path) + }.start() } - directories.addAll(dirs) - return directories -} - -fun Activity.loadPng(path: String, target: MySquareImageView, cropThumbnails: Boolean) { - val options = RequestOptions() - .signature(path.getFileSignature()) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .format(DecodeFormat.PREFER_ARGB_8888) - - val builder = Glide.with(applicationContext) - .asBitmap() - .load(path) - - if (cropThumbnails) options.centerCrop() else options.fitCenter() - builder.apply(options).into(target) -} - -fun Activity.loadJpg(path: String, target: MySquareImageView, cropThumbnails: Boolean) { - val options = RequestOptions() - .signature(path.getFileSignature()) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - - val builder = Glide.with(applicationContext) - .load(path) - - if (cropThumbnails) options.centerCrop() else options.fitCenter() - builder.apply(options).transition(DrawableTransitionOptions.withCrossFade()).into(target) -} - -fun Activity.getCachedDirectories(): ArrayList { - val token = object : TypeToken>() {}.type - return Gson().fromJson>(config.directories, token) ?: ArrayList(1) -} - -fun Activity.getCachedMedia(path: String): ArrayList { - val token = object : TypeToken>() {}.type - return Gson().fromJson>(config.loadFolderMedia(path), token) ?: ArrayList(1) } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/ArrayList.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/ArrayList.kt new file mode 100644 index 000000000..afd1c36e5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/ArrayList.kt @@ -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.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 +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Context.kt index 66a7fbde5..8ef8e068c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Context.kt @@ -9,19 +9,24 @@ import android.media.AudioManager import android.os.Build import android.provider.MediaStore import android.view.WindowManager -import com.google.gson.Gson +import android.widget.ImageView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DecodeFormat +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.RequestOptions import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.activities.SettingsActivity -import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask -import com.simplemobiletools.gallery.helpers.Config -import com.simplemobiletools.gallery.helpers.NOMEDIA -import com.simplemobiletools.gallery.helpers.SAVE_DIRS_CNT -import com.simplemobiletools.gallery.helpers.SAVE_MEDIA_CNT +import com.simplemobiletools.gallery.databases.GalleryDataBase +import com.simplemobiletools.gallery.helpers.* +import com.simplemobiletools.gallery.interfaces.DirectoryDao import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.models.Medium +import com.simplemobiletools.gallery.views.MySquareImageView +import pl.droidsonroids.gif.GifDrawable import java.io.File val Context.portrait get() = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT @@ -65,17 +70,27 @@ fun Context.launchSettings() { val Context.config: Config get() = Config.newInstance(applicationContext) +val Context.galleryDB: GalleryDataBase get() = GalleryDataBase.getInstance(applicationContext) + fun Context.movePinnedDirectoriesToFront(dirs: ArrayList): ArrayList { val foundFolders = ArrayList() val pinnedFolders = config.pinnedFolders dirs.forEach { - if (pinnedFolders.contains(it.path)) + if (pinnedFolders.contains(it.path)) { foundFolders.add(it) + } } dirs.removeAll(foundFolders) dirs.addAll(0, foundFolders) + if (config.tempFolderPath.isNotEmpty()) { + val newFolder = dirs.firstOrNull { it.path == config.tempFolderPath } + if (newFolder != null) { + dirs.remove(newFolder) + dirs.add(0, newFolder) + } + } return dirs } @@ -98,7 +113,6 @@ fun Context.getNoMediaFolders(callback: (folders: ArrayList) -> Unit) { val sortOrder = "${MediaStore.Files.FileColumns.DATE_MODIFIED} DESC" var cursor: Cursor? = null - try { cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder) if (cursor?.moveToFirst() == true) { @@ -118,50 +132,35 @@ fun Context.getNoMediaFolders(callback: (folders: ArrayList) -> Unit) { }.start() } -fun Context.isPathInMediaStore(path: String): Boolean { - if (path.startsWith(OTG_PATH)) { - return false - } - - val projection = arrayOf(MediaStore.Images.Media.DATE_MODIFIED) - val uri = MediaStore.Files.getContentUri("external") - val selection = "${MediaStore.MediaColumns.DATA} = ?" - val selectionArgs = arrayOf(path) - val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) - - cursor?.use { - return cursor.moveToFirst() - } - return false +fun Context.rescanFolderMedia(path: String) { + Thread { + rescanFolderMediaSync(path) + }.start() } -fun Context.updateStoredFolderItems(path: String) { - GetMediaAsynctask(this, path, false, false, false) { - storeFolderItems(path, it) - }.execute() -} +fun Context.rescanFolderMediaSync(path: String) { + getCachedMedia(path) { + val cached = it + GetMediaAsynctask(applicationContext, path, false, false, false) { + Thread { + val newMedia = it + val mediumDao = galleryDB.MediumDao() + mediumDao.insertAll(newMedia) -fun Context.storeFolderItems(path: String, items: ArrayList) { - 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) { + cached.forEach { + if (!newMedia.contains(it)) { + mediumDao.deleteMediumPath(it.path) + } + } + }.start() + }.execute() } } -fun Context.updateStoredDirectories() { - GetDirectoriesAsynctask(this, false, false) { - if (!config.temporarilyShowHidden) { - storeDirectoryItems(it) - } - }.execute() -} - fun Context.storeDirectoryItems(items: ArrayList) { - val subList = items.subList(0, Math.min(SAVE_DIRS_CNT, items.size)) - config.directories = Gson().toJson(subList) + Thread { + galleryDB.DirectoryDao().insertAll(items) + }.start() } fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders: MutableSet): String { @@ -184,3 +183,150 @@ fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders: dirName } } + +fun Context.loadImage(type: Int, path: String, target: MySquareImageView, horizontalScroll: Boolean, animateGifs: Boolean, cropThumbnails: Boolean) { + target.isHorizontalScrolling = horizontalScroll + if (type == TYPE_IMAGES || type == TYPE_VIDEOS) { + if (type == TYPE_IMAGES && path.isPng()) { + loadPng(path, target, cropThumbnails) + } else { + loadJpg(path, target, cropThumbnails) + } + } else if (type == TYPE_GIFS) { + try { + val gifDrawable = GifDrawable(path) + target.setImageDrawable(gifDrawable) + if (animateGifs) { + gifDrawable.start() + } else { + gifDrawable.stop() + } + + target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER + } catch (e: Exception) { + loadJpg(path, target, cropThumbnails) + } catch (e: OutOfMemoryError) { + loadJpg(path, target, cropThumbnails) + } + } +} + +fun Context.addTempFolderIfNeeded(dirs: ArrayList): ArrayList { + val directories = ArrayList() + 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) -> Unit) { + Thread { + val directoryDao = galleryDB.DirectoryDao() + val directories = directoryDao.getAll() as ArrayList + val shouldShowHidden = config.shouldShowHidden + val excludedPaths = config.excludedFolders + val includedPaths = config.includedFolders + var filteredDirectories = directories.filter { it.path.shouldFolderBeVisible(excludedPaths, includedPaths, shouldShowHidden) } as ArrayList + 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 + + callback(filteredDirectories.distinctBy { it.path.toLowerCase() } as ArrayList) + + removeInvalidDBDirectories(directories, directoryDao) + }.start() +} + +fun Context.getCachedMedia(path: String, getVideosOnly: Boolean = false, getImagesOnly: Boolean = false, callback: (ArrayList) -> Unit) { + Thread { + val mediumDao = galleryDB.MediumDao() + val media = (if (path == "/") mediumDao.getAll() else mediumDao.getMediaFromPath(path)) as ArrayList + val shouldShowHidden = config.shouldShowHidden + var filteredMedia = media + if (!shouldShowHidden) { + filteredMedia = media.filter { !it.name.startsWith('.') } as ArrayList + } + + 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 + + callback(filteredMedia) + media.filter { !getDoesFilePathExist(it.path) }.forEach { + mediumDao.deleteMediumPath(it.path) + } + }.start() +} + +fun Context.removeInvalidDBDirectories(dirs: ArrayList? = 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() diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/String.kt index a2e6cbfc8..dd28e208b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/String.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/String.kt @@ -5,9 +5,30 @@ import java.io.File fun String.getFileSignature(): ObjectKey { val file = File(this) - return ObjectKey("${file.name}${file.lastModified()}") + return ObjectKey("${file.absolutePath}${file.lastModified()}") } fun String.isThisOrParentIncluded(includedPaths: MutableSet) = includedPaths.any { startsWith(it, true) } fun String.isThisOrParentExcluded(excludedPaths: MutableSet) = excludedPaths.any { startsWith(it, true) } + +fun String.shouldFolderBeVisible(excludedPaths: MutableSet, includedPaths: MutableSet, 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 + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt index 7d299d026..09a0a976c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt @@ -275,7 +275,7 @@ class PhotoFragment : ViewPagerFragment() { } else { val options = RequestOptions() .diskCacheStrategy(DiskCacheStrategy.NONE) - .transform(GlideRotateTransformation(context!!, degrees)) + .transform(GlideRotateTransformation(degrees)) Glide.with(this) .asBitmap() diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Config.kt index a76deb83c..596a672ab 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Config.kt @@ -6,6 +6,7 @@ import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.simplemobiletools.commons.helpers.BaseConfig import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED +import com.simplemobiletools.commons.helpers.SORT_BY_DATE_TAKEN import com.simplemobiletools.commons.helpers.SORT_DESCENDING import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.models.AlbumCover @@ -17,7 +18,13 @@ class Config(context: Context) : BaseConfig(context) { } var directorySorting: Int - get() = prefs.getInt(DIRECTORY_SORT_ORDER, SORT_BY_DATE_MODIFIED or SORT_DESCENDING) + get(): Int { + var sort = prefs.getInt(DIRECTORY_SORT_ORDER, SORT_BY_DATE_MODIFIED or SORT_DESCENDING) + if (sort and SORT_BY_DATE_TAKEN != 0) { + sort = sort - SORT_BY_DATE_TAKEN + SORT_BY_DATE_MODIFIED + } + return sort + } set(order) = prefs.edit().putInt(DIRECTORY_SORT_ORDER, order).apply() fun saveFileSorting(path: String, value: Int) { @@ -28,7 +35,13 @@ class Config(context: Context) : BaseConfig(context) { } } - fun getFileSorting(path: String) = prefs.getInt(SORT_FOLDER_PREFIX + path.toLowerCase(), sorting) + fun getFileSorting(path: String): Int { + var sort = prefs.getInt(SORT_FOLDER_PREFIX + path.toLowerCase(), sorting) + if (sort and SORT_BY_DATE_TAKEN != 0) { + sort = sort - SORT_BY_DATE_TAKEN + SORT_BY_DATE_MODIFIED + } + return sort + } fun removeFileSorting(path: String) { prefs.edit().remove(SORT_FOLDER_PREFIX + path.toLowerCase()).apply() @@ -118,18 +131,6 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getStringSet(INCLUDED_FOLDERS, HashSet()) set(includedFolders) = prefs.edit().remove(INCLUDED_FOLDERS).putStringSet(INCLUDED_FOLDERS, includedFolders).apply() - fun saveFolderMedia(path: String, json: String) { - prefs.edit().putString(SAVE_FOLDER_PREFIX + path, json).apply() - } - - fun loadFolderMedia(path: String) = prefs.getString(SAVE_FOLDER_PREFIX + path, "") - - fun saveFolderMediaShort(path: String, value: String) { - prefs.edit().putString(SAVE_FOLDER_SHORT_PREFIX + path, value).apply() - } - - fun loadFolderMediaShort(path: String) = prefs.getString(SAVE_FOLDER_SHORT_PREFIX + path, "") - var autoplayVideos: Boolean get() = prefs.getBoolean(AUTOPLAY_VIDEOS, false) set(autoplay) = prefs.edit().putBoolean(AUTOPLAY_VIDEOS, autoplay).apply() @@ -163,7 +164,7 @@ class Config(context: Context) : BaseConfig(context) { set(darkBackground) = prefs.edit().putBoolean(DARK_BACKGROUND, darkBackground).apply() var filterMedia: Int - get() = prefs.getInt(FILTER_MEDIA, IMAGES or VIDEOS or GIFS) + get() = prefs.getInt(FILTER_MEDIA, TYPE_IMAGES or TYPE_VIDEOS or TYPE_GIFS) set(filterMedia) = prefs.edit().putInt(FILTER_MEDIA, filterMedia).apply() var dirColumnCnt: Int @@ -226,10 +227,6 @@ class Config(context: Context) : BaseConfig(context) { private fun getDefaultMediaColumnCount() = context.resources.getInteger(if (scrollHorizontally) R.integer.media_columns_horizontal_scroll else R.integer.media_columns_vertical_scroll) - var directories: String - get() = prefs.getString(DIRECTORIES, "") - set(directories) = prefs.edit().putString(DIRECTORIES, directories).apply() - var albumCovers: String get() = prefs.getString(ALBUM_COVERS, "") set(albumCovers) = prefs.edit().putString(ALBUM_COVERS, albumCovers).apply() @@ -328,6 +325,10 @@ class Config(context: Context) : BaseConfig(context) { set(wasNewAppShown) = prefs.edit().putBoolean(WAS_NEW_APP_SHOWN, wasNewAppShown).apply() var lastFilepickerPath: String - get() = prefs.getString(TEMP_FOLDER_PATH, "") - set(tempFolderPath) = prefs.edit().putString(TEMP_FOLDER_PATH, tempFolderPath).apply() + get() = prefs.getString(LAST_FILEPICKER_PATH, "") + set(lastFilepickerPath) = prefs.edit().putString(LAST_FILEPICKER_PATH, lastFilepickerPath).apply() + + var wasOTGHandled: Boolean + get() = prefs.getBoolean(WAS_OTG_HANDLED, false) + set(wasOTGHandled) = prefs.edit().putBoolean(WAS_OTG_HANDLED, wasOTGHandled).apply() } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Constants.kt index c0431b69c..23d190fab 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Constants.kt @@ -25,8 +25,6 @@ const val MEDIA_LANDSCAPE_COLUMN_CNT = "media_landscape_column_cnt" const val MEDIA_HORIZONTAL_COLUMN_CNT = "media_horizontal_column_cnt" const val MEDIA_LANDSCAPE_HORIZONTAL_COLUMN_CNT = "media_landscape_horizontal_column_cnt" const val SHOW_ALL = "show_all" // display images and videos from all folders together -const val SAVE_FOLDER_PREFIX = "folder2_" -const val SAVE_FOLDER_SHORT_PREFIX = "folder_short_" const val HIDE_FOLDER_TOOLTIP_SHOWN = "hide_folder_tooltip_shown" const val EXCLUDED_FOLDERS = "excluded_folders" const val INCLUDED_FOLDERS = "included_folders" @@ -49,6 +47,7 @@ const val REPLACE_ZOOMABLE_IMAGES = "replace_zoomable_images" const val DO_EXTRA_CHECK = "do_extra_check" const val WAS_NEW_APP_SHOWN = "was_new_app_shown_clock" const val LAST_FILEPICKER_PATH = "last_filepicker_path" +const val WAS_OTG_HANDLED = "was_otg_handled" // slideshow const val SLIDESHOW_INTERVAL = "slideshow_interval" @@ -67,8 +66,6 @@ const val MAX_COLUMN_COUNT = 20 const val SHOW_TEMP_HIDDEN_DURATION = 600000L const val CLICK_MAX_DURATION = 150 const val DRAG_THRESHOLD = 8 -const val SAVE_DIRS_CNT = 60 -const val SAVE_MEDIA_CNT = 80 const val DIRECTORY = "directory" const val MEDIUM = "medium" @@ -77,7 +74,6 @@ const val GET_IMAGE_INTENT = "get_image_intent" const val GET_VIDEO_INTENT = "get_video_intent" const val GET_ANY_INTENT = "get_any_intent" const val SET_WALLPAPER_INTENT = "set_wallpaper_intent" -const val DIRECTORIES = "directories2" const val IS_VIEW_INTENT = "is_view_intent" const val PICKED_PATHS = "picked_paths" @@ -86,11 +82,6 @@ const val ROTATE_BY_SYSTEM_SETTING = 0 const val ROTATE_BY_DEVICE_ROTATION = 1 const val ROTATE_BY_ASPECT_RATIO = 2 -// filter media -const val IMAGES = 1 -const val VIDEOS = 2 -const val GIFS = 4 - // view types const val VIEW_TYPE_GRID = 1 const val VIEW_TYPE_LIST = 2 @@ -109,6 +100,10 @@ const val EXT_ARTIST = 512 const val EXT_ALBUM = 1024 // media types -const val TYPE_IMAGE = 1 -const val TYPE_VIDEO = 2 -const val TYPE_GIF = 3 +const val TYPE_IMAGES = 1 +const val TYPE_VIDEOS = 2 +const val TYPE_GIFS = 4 + +const val LOCAITON_INTERNAL = 1 +const val LOCATION_SD = 2 +const val LOCATION_OTG = 3 diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideDecoder.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideDecoder.kt deleted file mode 100644 index 326b6011c..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideDecoder.kt +++ /dev/null @@ -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 - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt index 84ee4c49f..5f34c6ced 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt @@ -1,13 +1,12 @@ package com.simplemobiletools.gallery.helpers -import android.content.Context import android.graphics.Bitmap import android.graphics.Matrix import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import java.security.MessageDigest -class GlideRotateTransformation(context: Context, val rotateRotationAngle: Int) : BitmapTransformation(context) { +class GlideRotateTransformation(val rotateRotationAngle: Int) : BitmapTransformation() { override fun transform(pool: BitmapPool, bitmap: Bitmap, outWidth: Int, outHeight: Int): Bitmap { if (rotateRotationAngle % 360 == 0) return bitmap diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/MediaFetcher.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/MediaFetcher.kt index 61e88604b..41d876701 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/MediaFetcher.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/MediaFetcher.kt @@ -8,45 +8,48 @@ import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.commons.helpers.photoExtensions import com.simplemobiletools.commons.helpers.videoExtensions -import com.simplemobiletools.gallery.extensions.* +import com.simplemobiletools.gallery.extensions.config +import com.simplemobiletools.gallery.extensions.getOTGFolderChildren +import com.simplemobiletools.gallery.extensions.shouldFolderBeVisible import com.simplemobiletools.gallery.models.Medium import java.io.File -import java.util.LinkedHashMap -import kotlin.collections.ArrayList -import kotlin.collections.HashSet -import kotlin.collections.set class MediaFetcher(val context: Context) { var shouldStop = false - fun getMediaByDirectories(isPickVideo: Boolean, isPickImage: Boolean): HashMap> { - val media = getFilesFrom("", isPickImage, isPickVideo) - return groupDirectories(media) - } - fun getFilesFrom(curPath: String, isPickImage: Boolean, isPickVideo: Boolean): ArrayList { val filterMedia = context.config.filterMedia if (filterMedia == 0) { return ArrayList() } + val curMedia = ArrayList() if (curPath.startsWith(OTG_PATH)) { - val curMedia = ArrayList() - getMediaOnOTG(curPath, curMedia, isPickImage, isPickVideo, filterMedia) - return curMedia + val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia) + curMedia.addAll(newMedia) } else { - val projection = arrayOf(MediaStore.Images.Media.DATA) - val uri = MediaStore.Files.getContentUri("external") + val newMedia = fetchFolderContent(curPath, isPickImage, isPickVideo, filterMedia) + curMedia.addAll(newMedia) + } - val selection = "${getSelectionQuery(curPath, filterMedia)} ${MediaStore.Images.ImageColumns.BUCKET_ID} IS NOT NULL) GROUP BY (${MediaStore.Images.ImageColumns.BUCKET_ID}" - val selectionArgs = getSelectionArgsQuery(curPath, filterMedia).toTypedArray() + Medium.sorting = context.config.getFileSorting(curPath) + curMedia.sort() + return curMedia + } - return try { - val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) - parseCursor(context, cursor, isPickImage, isPickVideo, curPath, filterMedia) - } catch (e: Exception) { - ArrayList() - } + fun getFoldersToScan(path: String): ArrayList { + val filterMedia = context.config.filterMedia + val projection = arrayOf(MediaStore.Images.Media.DATA) + val uri = MediaStore.Files.getContentUri("external") + + val selection = "${getSelectionQuery(path, filterMedia)} ${MediaStore.Images.ImageColumns.BUCKET_ID} IS NOT NULL) GROUP BY (${MediaStore.Images.ImageColumns.BUCKET_ID}" + val selectionArgs = getSelectionArgsQuery(path, filterMedia).toTypedArray() + + return try { + val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) + parseCursor(cursor, path) + } catch (e: Exception) { + ArrayList() } } @@ -57,19 +60,19 @@ class MediaFetcher(val context: Context) { } query.append("(") - if (filterMedia and IMAGES != 0) { + if (filterMedia and TYPE_IMAGES != 0) { photoExtensions.forEach { query.append("${MediaStore.Images.Media.DATA} LIKE ? OR ") } } - if (filterMedia and VIDEOS != 0) { + if (filterMedia and TYPE_VIDEOS != 0) { videoExtensions.forEach { query.append("${MediaStore.Images.Media.DATA} LIKE ? OR ") } } - if (filterMedia and GIFS != 0) { + if (filterMedia and TYPE_GIFS != 0) { query.append("${MediaStore.Images.Media.DATA} LIKE ?") } @@ -85,29 +88,29 @@ class MediaFetcher(val context: Context) { args.add("$path/%/%") } - if (filterMedia and IMAGES != 0) { + if (filterMedia and TYPE_IMAGES != 0) { photoExtensions.forEach { args.add("%$it") } } - if (filterMedia and VIDEOS != 0) { + if (filterMedia and TYPE_VIDEOS != 0) { videoExtensions.forEach { args.add("%$it") } } - if (filterMedia and GIFS != 0) { + if (filterMedia and TYPE_GIFS != 0) { args.add("%.gif") } return args } - private fun parseCursor(context: Context, cursor: Cursor, isPickImage: Boolean, isPickVideo: Boolean, curPath: String, filterMedia: Int): ArrayList { + private fun parseCursor(cursor: Cursor, curPath: String): ArrayList { val config = context.config val includedFolders = config.includedFolders - val foldersToScan = HashSet() + var foldersToScan = ArrayList() cursor.use { if (cursor.moveToFirst()) { @@ -129,81 +132,47 @@ class MediaFetcher(val context: Context) { } } - val curMedia = ArrayList() val showHidden = config.shouldShowHidden val excludedFolders = config.excludedFolders - foldersToScan.filter { shouldFolderBeVisible(it, excludedFolders, includedFolders, showHidden) }.toList().forEach { - fetchFolderContent(it, curMedia, isPickImage, isPickVideo, filterMedia) + foldersToScan = foldersToScan.filter { it.shouldFolderBeVisible(excludedFolders, includedFolders, showHidden) } as ArrayList + if (config.isThirdPartyIntent && curPath.isNotEmpty()) { + foldersToScan.add(curPath) } - if (config.isThirdPartyIntent && curPath.isNotEmpty() && curMedia.isEmpty()) { - getMediaInFolder(curPath, curMedia, isPickImage, isPickVideo, filterMedia) - } - - Medium.sorting = config.getFileSorting(curPath) - curMedia.sort() - - return curMedia + return foldersToScan.distinctBy { it.toLowerCase() } as ArrayList } - private fun addFolder(curFolders: HashSet, folder: String) { + private fun addFolder(curFolders: ArrayList, folder: String) { curFolders.add(folder) - val files = File(folder).listFiles() ?: return - for (file in files) { - if (file.isDirectory) { - addFolder(curFolders, file.absolutePath) + 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") + } } - } - } - - private fun fetchFolderContent(path: String, curMedia: ArrayList, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int) { - if (path.startsWith(OTG_PATH)) { - getMediaOnOTG(path, curMedia, isPickImage, isPickVideo, filterMedia) } else { - getMediaInFolder(path, curMedia, isPickImage, isPickVideo, filterMedia) + val files = File(folder).listFiles() ?: return + for (file in files) { + if (file.isDirectory) { + addFolder(curFolders, file.absolutePath) + } + } } } - private fun groupDirectories(media: ArrayList): HashMap> { - val directories = LinkedHashMap>() - val hasOTG = context.hasOTGConnected() && context.config.OTGBasePath.isNotEmpty() - for (medium in media) { - if (shouldStop) { - break - } - - val parentDir = (if (hasOTG && medium.path.startsWith(OTG_PATH)) medium.path.getParentPath().toLowerCase() else File(medium.path).parent?.toLowerCase()) - ?: continue - if (directories.containsKey(parentDir)) { - directories[parentDir]!!.add(medium) - } else { - directories[parentDir] = arrayListOf(medium) - } - } - return directories - } - - private fun shouldFolderBeVisible(path: String, excludedPaths: MutableSet, includedPaths: MutableSet, 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 + private fun fetchFolderContent(path: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int): ArrayList { + return if (path.startsWith(OTG_PATH)) { + getMediaOnOTG(path, isPickImage, isPickVideo, filterMedia) } else { - true + getMediaInFolder(path, isPickImage, isPickVideo, filterMedia) } } - private fun getMediaInFolder(folder: String, curMedia: ArrayList, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int) { - val files = File(folder).listFiles() ?: return + private fun getMediaInFolder(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int): ArrayList { + val media = ArrayList() + val files = File(folder).listFiles() ?: return media val doExtraCheck = context.config.doExtraCheck val showHidden = context.config.shouldShowHidden @@ -220,13 +189,13 @@ class MediaFetcher(val context: Context) { if (!isImage && !isVideo && !isGif) continue - if (isVideo && (isPickImage || filterMedia and VIDEOS == 0)) + if (isVideo && (isPickImage || filterMedia and TYPE_VIDEOS == 0)) continue - if (isImage && (isPickVideo || filterMedia and IMAGES == 0)) + if (isImage && (isPickVideo || filterMedia and TYPE_IMAGES == 0)) continue - if (isGif && filterMedia and GIFS == 0) + if (isGif && filterMedia and TYPE_GIFS == 0) continue if (!showHidden && filename.startsWith('.')) @@ -240,18 +209,20 @@ class MediaFetcher(val context: Context) { val dateModified = file.lastModified() val type = when { - isImage -> TYPE_IMAGE - isVideo -> TYPE_VIDEO - else -> TYPE_GIF + isImage -> TYPE_IMAGES + isVideo -> TYPE_VIDEOS + else -> TYPE_GIFS } - val medium = Medium(filename, file.absolutePath, dateModified, dateTaken, size, type) - curMedia.add(medium) + val medium = Medium(null, filename, file.absolutePath, folder, dateModified, dateTaken, size, type) + media.add(medium) } + return media } - private fun getMediaOnOTG(folder: String, curMedia: ArrayList, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int) { - val files = context.getDocumentFile(folder)?.listFiles() ?: return + private fun getMediaOnOTG(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int): ArrayList { + val media = ArrayList() + val files = context.getDocumentFile(folder)?.listFiles() ?: return media val doExtraCheck = context.config.doExtraCheck val showHidden = context.config.shouldShowHidden @@ -260,7 +231,7 @@ class MediaFetcher(val context: Context) { break } - val filename = file.name + val filename = file.name ?: continue val isImage = filename.isImageFast() val isVideo = if (isImage) false else filename.isVideoFast() val isGif = if (isImage || isVideo) false else filename.isGif() @@ -268,13 +239,13 @@ class MediaFetcher(val context: Context) { if (!isImage && !isVideo && !isGif) continue - if (isVideo && (isPickImage || filterMedia and VIDEOS == 0)) + if (isVideo && (isPickImage || filterMedia and TYPE_VIDEOS == 0)) continue - if (isImage && (isPickVideo || filterMedia and IMAGES == 0)) + if (isImage && (isPickVideo || filterMedia and TYPE_IMAGES == 0)) continue - if (isGif && filterMedia and GIFS == 0) + if (isGif && filterMedia and TYPE_GIFS == 0) continue if (!showHidden && filename.startsWith('.')) @@ -288,14 +259,16 @@ class MediaFetcher(val context: Context) { val dateModified = file.lastModified() val type = when { - isImage -> TYPE_IMAGE - isVideo -> TYPE_VIDEO - else -> TYPE_GIF + isImage -> TYPE_IMAGES + isVideo -> TYPE_VIDEOS + else -> TYPE_GIFS } - val path = Uri.decode(file.uri.toString().replaceFirst("${context.config.OTGBasePath}%3A", OTG_PATH)) - val medium = Medium(filename, path, dateModified, dateTaken, size, type) - curMedia.add(medium) + val path = Uri.decode(file.uri.toString().replaceFirst("${context.config.OTGTreeUri}/document/${context.config.OTGPartition}%3A", OTG_PATH)) + val medium = Medium(null, filename, path, folder, dateModified, dateTaken, size, type) + media.add(medium) } + + return media } } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/interfaces/DirectoryDao.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/interfaces/DirectoryDao.kt new file mode 100644 index 000000000..62bef007a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/interfaces/DirectoryDao.kt @@ -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 + + @Insert(onConflict = REPLACE) + fun insert(directory: Directory) + + @Insert(onConflict = REPLACE) + fun insertAll(directories: List) + + @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) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/interfaces/MediumDao.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/interfaces/MediumDao.kt new file mode 100644 index 000000000..eee0430c4 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/interfaces/MediumDao.kt @@ -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 + + @Query("SELECT filename, full_path, parent_path, last_modified, date_taken, size, type FROM media WHERE parent_path = :path") + fun getMediaFromPath(path: String): List + + @Insert(onConflict = REPLACE) + fun insert(medium: Medium) + + @Insert(onConflict = REPLACE) + fun insertAll(media: List) + + @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) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Directory.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Directory.kt index 3bb84637e..6d8c217a1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Directory.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Directory.kt @@ -1,14 +1,29 @@ package com.simplemobiletools.gallery.models +import android.arch.persistence.room.ColumnInfo +import android.arch.persistence.room.Entity +import android.arch.persistence.room.Index +import android.arch.persistence.room.PrimaryKey import com.simplemobiletools.commons.extensions.formatDate import com.simplemobiletools.commons.extensions.formatSize import com.simplemobiletools.commons.helpers.* import java.io.Serializable -data class Directory(var path: String, var tmb: String, var name: String, var mediaCnt: Int, val modified: Long, val taken: Long, - val size: Long, val isOnSDCard: Boolean) : Serializable, Comparable { +@Entity(tableName = "directories", indices = [Index(value = "path", unique = true)]) +data class Directory( + @PrimaryKey(autoGenerate = true) var id: Long?, + @ColumnInfo(name = "path") var path: String, + @ColumnInfo(name = "thumbnail") var tmb: String, + @ColumnInfo(name = "filename") var name: String, + @ColumnInfo(name = "media_count") var mediaCnt: Int, + @ColumnInfo(name = "last_modified") var modified: Long, + @ColumnInfo(name = "date_taken") var taken: Long, + @ColumnInfo(name = "size") var size: Long, + @ColumnInfo(name = "location") val location: Int, + @ColumnInfo(name = "media_types") var types: Int) : Serializable, Comparable { + companion object { - private val serialVersionUID = -6553345863555455L + private const val serialVersionUID = -6553345863555455L var sorting: Int = 0 } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt index f00aedda9..2327feadb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt @@ -1,31 +1,42 @@ package com.simplemobiletools.gallery.models +import android.arch.persistence.room.ColumnInfo +import android.arch.persistence.room.Entity +import android.arch.persistence.room.Index +import android.arch.persistence.room.PrimaryKey import com.simplemobiletools.commons.extensions.formatDate import com.simplemobiletools.commons.extensions.formatSize -import com.simplemobiletools.commons.extensions.getMimeType import com.simplemobiletools.commons.extensions.isDng import com.simplemobiletools.commons.helpers.* -import com.simplemobiletools.gallery.helpers.TYPE_GIF -import com.simplemobiletools.gallery.helpers.TYPE_IMAGE -import com.simplemobiletools.gallery.helpers.TYPE_VIDEO +import com.simplemobiletools.gallery.helpers.TYPE_GIFS +import com.simplemobiletools.gallery.helpers.TYPE_IMAGES +import com.simplemobiletools.gallery.helpers.TYPE_VIDEOS import java.io.Serializable -data class Medium(var name: String, var path: String, val modified: Long, val taken: Long, val size: Long, val type: Int) : Serializable, Comparable { +@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 { + companion object { - private val serialVersionUID = -6553149366975455L + private const val serialVersionUID = -6553149366975455L var sorting: Int = 0 } - fun isGif() = type == TYPE_GIF + fun isGif() = type == TYPE_GIFS - fun isImage() = type == TYPE_IMAGE + fun isImage() = type == TYPE_IMAGES - fun isVideo() = type == TYPE_VIDEO + fun isVideo() = type == TYPE_VIDEOS fun isDng() = path.isDng() - fun getMimeType() = path.getMimeType() - override fun compareTo(other: Medium): Int { var result: Int when { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/InstallReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/InstallReceiver.kt deleted file mode 100644 index fc3566bc0..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/InstallReceiver.kt +++ /dev/null @@ -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() - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/RefreshMediaReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/RefreshMediaReceiver.kt index 589f98b45..8b3f9921e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/RefreshMediaReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/RefreshMediaReceiver.kt @@ -3,14 +3,32 @@ package com.simplemobiletools.gallery.receivers import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import com.google.gson.Gson -import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask -import com.simplemobiletools.gallery.extensions.config +import com.simplemobiletools.commons.extensions.getFilenameFromPath +import com.simplemobiletools.commons.extensions.getParentPath +import com.simplemobiletools.commons.extensions.isImageFast +import com.simplemobiletools.commons.extensions.isVideoFast +import com.simplemobiletools.commons.helpers.REFRESH_PATH +import com.simplemobiletools.gallery.extensions.galleryDB +import com.simplemobiletools.gallery.helpers.TYPE_GIFS +import com.simplemobiletools.gallery.helpers.TYPE_IMAGES +import com.simplemobiletools.gallery.helpers.TYPE_VIDEOS +import com.simplemobiletools.gallery.models.Medium +import java.io.File class RefreshMediaReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - GetDirectoriesAsynctask(context, false, false) { - context.config.directories = Gson().toJson(it) - }.execute() + val path = intent.getStringExtra(REFRESH_PATH) ?: return + + Thread { + val medium = Medium(null, path.getFilenameFromPath(), path, path.getParentPath(), System.currentTimeMillis(), System.currentTimeMillis(), + File(path).length(), getFileType(path)) + context.galleryDB.MediumDao().insert(medium) + }.start() + } + + private fun getFileType(path: String) = when { + path.isImageFast() -> TYPE_IMAGES + path.isVideoFast() -> TYPE_VIDEOS + else -> TYPE_GIFS } } diff --git a/app/src/main/res/layout/dialog_change_sorting.xml b/app/src/main/res/layout/dialog_change_sorting.xml index e0e642acc..812840f40 100644 --- a/app/src/main/res/layout/dialog_change_sorting.xml +++ b/app/src/main/res/layout/dialog_change_sorting.xml @@ -52,14 +52,6 @@ android:paddingTop="@dimen/medium_margin" android:text="@string/last_modified"/> - - + + Simple Gallery + Galleri + Rediger + Åbn kamera + (skjult) + (ekskluderet) + Pin folder + Unpin folder + Pin to the top + Vis indholdet af alle mappert + Alle mapper + Skift til mappevisning + Anden mappe + Vis på kort + Ukendt placering + Flere kolonner + Færre kolonner + Change cover image + Select photo + Use default + Volume + Brightness + Lås orientering + Lås orientering op + + + Filtrer medier + Billeder + Videoer + GIF\'er + Der blev ikke fundet nogen filer med det valgte filter. + Skift filter + + + 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? + Ekskluder + Ekskluderede mapper + Administrer ekskluderede mapper + Dette vil kun ekskludere de valgte mapper (og deres undermapper) fra Simple Gallery. Du kan administrere ekskluderede mapper i indstillingerne. + Ekskluder en overliggende mappe i stedet? + 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. + Fjern alle + Fjern alle fra listen med ekskluderede mapper? Det vil ikke slette mapperne. + Skjulte mapper + Administrer skjulte mapper + Det ser ikke ud til at du har nogen skjulte mapper med en \".nomedia\"-fil. + + + Inkluderede mapper + Administrer inkluderede mapper + Tilføj mappe + 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. + + + Skaler + Resize selection and save + Width + Height + Keep aspect ratio + Please enter a valid resolution + + + Editor + Gem + Roter + Sti + Ugyldig sti til billede + Redigering af billede mislykkedes + Rediger billede med: + Der blev ikke fundet en editor til billedbehandling + Ukendt filplacering + Kunne ikke overskrive kildefilen + Roter mod venstre + Roter mod højre + Roter 180º + Spejlvend + Spejlvend vandret + Spejlvend lodret + Rediger med + + + Simple Wallpaper + Sæt som baggrundsbillede + Det mislykkedes at sætte billedet som baggrund + Sæt som baggrundsbillede med: + Sætter baggrundsbillede… + Sat som baggrundsbillede + Stående billedformat + Liggende billedformat + Hjemmeskærm + Låseskærm + Hjemme- og låseskærm + + + Slideshow + Frekvens (sekunder): + Inkluder billeder + Inkluder videoer + Inkluder GIF\'er + Tilfældig rækkefølge + Use fade animations + Kør baglæns + Loop slideshow + Slideshowet endte + Der blev ikke funket nogen mediefiler til slideshowet + + + Skift visning + Gitter + Liste + + + Afspil automatisk videoer + Toggle filename visibility + Kør videoer i sløjfe + Animér GIF\'er i miniaturer + Maksimal lysstyrke ved fuldskærmsvisning af medier + Beskær miniaturer til kvadrater + Roter fuldskærmsmedier efter + Systemindstilling + Enhedens orientering + Billedformat + Sort baggrund og statuslinje ved medievisning i fuldskærm + Scroll miniaturer vandret + Skjul automatisk systemets brugerflade ved fuldskærmsvisning af medier + Slet tomme mapper efter sletning af deres indhold + Tillad kontrol af lysstyrke på billeder med lodrette bevægelser + Tillad kontrol af videolyd og lysstyrke med lodrette bevægelser + Vis antal filer i hver mappe i oversigten + Erstat Del med Roter i fuldskærmsmenuen + Vis udvidede oplysninger over medier i fuldskærm + Manage extended details + Tillad zoom med en finger når medier er i fuldskærm + Tillad skift af medie ved klik på skærmens sider + RErstat stærkt zoombare billeder med nogle i bedre kvalitet + Skjul udvidede oplysninger når statuslinjen er skjult + Tjek en ekstra gang for at undgå visning af ugyldige filer + + + Thumbnails + Fullscreen media + Extended details + + + How can I make Simple Gallery the default device gallery? + 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. + I locked the app with a password, but I forgot it. What can I do? + 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. + How can I make an album always appear at the top? + 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. + How can I fast-forward videos? + You can click on the current or max duration texts near the seekbar, that will move the video either backward, or forward. + What is the difference between hiding and excluding a folder? + 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. + Why do folders with music cover art or stickers show up? + 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. + A folder with images isn\'t showing up, what can I do? + 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. + What if I want just a few particular folders visible? + 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. + Fullscreen images have weird artifacts, can I somehow improve the quality? + 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. + Can I crop images with this app? + 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. + + + + A gallery for viewing photos and videos without ads. + + 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 + + + + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ec87162d8..eeeb3ccba 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -5,7 +5,7 @@ Modifica Apri fotocamera (nascosta) - (excluded) + (esclusa) Blocca cartella Sblocca cartella Fissa in alto @@ -165,7 +165,7 @@ Le immagini a schermo intero hanno strani artefatti, posso migliorarne la qualità in qualche modo? 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. Posso ritagliare le immagini con questa app? - 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. + 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. diff --git a/app/src/main/res/values-lt b/app/src/main/res/values-lt/strings.xml similarity index 98% rename from app/src/main/res/values-lt rename to app/src/main/res/values-lt/strings.xml index 64542d1b7..aa9a2c9f4 100644 --- a/app/src/main/res/values-lt +++ b/app/src/main/res/values-lt/strings.xml @@ -34,7 +34,7 @@ Pakeisti filtrus - Ši funkcija slepia aplanką, pridedant į jį \ '. Nomedia \' bylą, jis taip pat slėps visus subaplankus. Galite juos peržiūrėti, perjunkite parinktį "Rodyti paslėptus elementus \" skiltyje Nustatymai. Tęsti? + Ši funkcija slepia aplanką, pridedant į jį \'. Nomedia \' bylą, jis taip pat slėps visus subaplankus. Galite juos peržiūrėti, perjunkite parinktį "Rodyti paslėptus elementus \" skiltyje Nustatymai. Tęsti? Išskirti Išskirti aplankai Tvarkyti išskirtus aplankus @@ -45,7 +45,7 @@ Pašalinti visus aplankus iš išskirtųjų sąrašo? Tai neištrins aplankų. Paslėpti aplankai Tvarkyti paslėptus aplankus - Atrodo, kad neturite jokių aplankų, paslėptų \ ". Nomedia \" bylos. + Atrodo, kad neturite jokių aplankų, paslėptų \". Nomedia \" bylos. Įtraukti aplankai diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 8b00f02b6..eacb6094d 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -5,7 +5,7 @@ Rediger Åpne kamera (skjult) - (excluded) + (ekskludert) Fest mappe Løsne mappe Fest til toppen diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index fe8f22d90..de1924bed 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -5,7 +5,7 @@ Editar Abrir câmara (oculta) - (excluded) + (excluída) Fixar pasta Desafixar pasta Fixar no topo diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index b6cfae674..118ae9cae 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -3,9 +3,9 @@ Simple Gallery Galleri Redigera - Starta kameran + Öppna kameran (dold) - (excluded) + (utesluten) Fäst mapp Lossa mapp Fäst högst upp diff --git a/build.gradle b/build.gradle index e6abf43e1..1a38f0b61 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.2.31' + ext.kotlin_version = '1.2.40' repositories { jcenter() @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0' + classpath 'com.android.tools.build:gradle:3.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong