diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ab949842..ce87f8506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,41 @@ Changelog ========== +Version 2.15.1 *(2017-10-01)* +---------------------------- + + * Updated commons library with minor fixes + +Version 2.15.0 *(2017-10-01)* +---------------------------- + + * Added fingerprint to hidden item protection + * Added a new List view type + * Fixed an issue with some hidden items being shown at "Show all folders content" + * Fixed typing in color hex codes manually with some keyboards + * Do not autosave rotated images in any case + * Tons of other performance, stability and UX improvements + +Version 2.14.4 *(2017-09-18)* +---------------------------- + + * Revert to the old way of loading fullscreen images to avoid issues on Android 7+ + +Version 2.14.3 *(2017-09-17)* +---------------------------- + + * Removed some error toast messages after delete, or if image loading failed + * Fixed some visual glitches at horizontal scrolling + * Disable pull-to-refresh at horizontal scrolling + * Many other smaller bugfixes and improvements + +Version 2.14.2 *(2017-09-11)* +---------------------------- + + * Fixing some glitches with fullscreen images + * Add an extra check to avoid displaying non-existing media + * Fix opening media from third party intents + Version 2.14.1 *(2017-09-07)* ---------------------------- diff --git a/LICENSE b/LICENSE index 28c26588c..ff97af7d9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,201 @@ -Copyright 2016 SimpleMobileTools + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - https://www.apache.org/licenses/LICENSE-2.0 + 1. Definitions. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 SimpleMobileTools + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/app/build.gradle b/app/build.gradle index a4500bd24..41c499e1e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.simplemobiletools.gallery" minSdkVersion 16 targetSdkVersion 23 - versionCode 129 - versionName "2.14.1" + versionCode 134 + versionName "2.15.1" } signingConfigs { @@ -37,7 +37,7 @@ android { } dependencies { - compile 'com.simplemobiletools:commons:2.27.9' + compile 'com.simplemobiletools:commons:2.29.1' compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.6.0' compile 'com.theartofdev.edmodo:android-image-cropper:2.4.0' compile 'com.bignerdranch.android:recyclerview-multiselect:0.2' @@ -52,7 +52,7 @@ dependencies { } buildscript { - ext.kotlin_version = '1.1.4-3' + ext.kotlin_version = '1.1.51' repositories { mavenCentral() } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/App.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/App.kt index 4cbda3237..48ef23b19 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/App.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/App.kt @@ -1,12 +1,14 @@ package com.simplemobiletools.gallery import android.app.Application +import com.github.ajalt.reprint.core.Reprint import com.squareup.leakcanary.LeakCanary class App : Application() { val USE_LEAK_CANARY = false override fun onCreate() { super.onCreate() + Reprint.initialize(this) if (USE_LEAK_CANARY) { if (LeakCanary.isInAnalyzerProcess(this)) { return 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 c484be0a4..b25521bb1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MainActivity.kt @@ -2,10 +2,10 @@ package com.simplemobiletools.gallery.activities import android.Manifest import android.app.Activity +import android.content.ClipData import android.content.Intent import android.content.pm.PackageManager import android.net.Uri -import android.os.AsyncTask import android.os.Build import android.os.Bundle import android.os.Handler @@ -19,7 +19,11 @@ import android.widget.FrameLayout import com.google.gson.Gson 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.SORT_BY_DATE_MODIFIED +import com.simplemobiletools.commons.helpers.SORT_BY_DATE_TAKEN +import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.models.Release import com.simplemobiletools.commons.views.MyScalableRecyclerView import com.simplemobiletools.gallery.BuildConfig @@ -54,10 +58,10 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { private var mStoredAnimateGifs = true private var mStoredCropThumbnails = true private var mStoredScrollHorizontally = true + private var mStoredTextColor = 0 private var mLoadedInitialPhotos = false - private var mLastMediaModified = 0 + private var mLatestMediaId = 0L private var mLastMediaHandler = Handler() - private var mCurrAsyncTask: GetDirectoriesAsynctask? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -79,6 +83,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { mStoredAnimateGifs = config.animateGifs mStoredCropThumbnails = config.cropThumbnails mStoredScrollHorizontally = config.scrollHorizontally + mStoredTextColor = config.textColor storeStoragePaths() checkWhatsNewDialog() @@ -92,8 +97,8 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { menuInflater.inflate(R.menu.menu_main_intent, menu) } else { menuInflater.inflate(R.menu.menu_main, menu) - menu.findItem(R.id.increase_column_count).isVisible = config.dirColumnCnt < 10 - menu.findItem(R.id.reduce_column_count).isVisible = config.dirColumnCnt > 1 + menu.findItem(R.id.increase_column_count).isVisible = config.viewTypeFolders == VIEW_TYPE_GRID && config.dirColumnCnt < 10 + menu.findItem(R.id.reduce_column_count).isVisible = config.viewTypeFolders == VIEW_TYPE_GRID && config.dirColumnCnt > 1 } menu.findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden menu.findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden @@ -106,6 +111,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { R.id.filter -> showFilterMediaDialog() R.id.open_camera -> launchCamera() R.id.show_all -> showAllMedia() + R.id.change_view_type -> changeViewType() R.id.temporarily_show_hidden -> tryToggleTemporarilyShowHidden() R.id.stop_showing_hidden -> tryToggleTemporarilyShowHidden() R.id.create_new_folder -> createNewFolder() @@ -130,13 +136,17 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { } if (mStoredScrollHorizontally != config.scrollHorizontally) { - directories_grid.adapter?.let { - (it as DirectoryAdapter).scrollVertically = !config.scrollHorizontally - it.notifyDataSetChanged() + (directories_grid.adapter as? DirectoryAdapter)?.apply { + scrollVertically = config.viewTypeFolders == VIEW_TYPE_LIST || !config.scrollHorizontally + notifyDataSetChanged() } setupScrollDirection() } + if (mStoredTextColor != config.textColor) { + (directories_grid.adapter as? DirectoryAdapter)?.updateTextColor(config.textColor) + } + tryloadGallery() invalidateOptionsMenu() directories_empty_text_label.setTextColor(config.textColor) @@ -145,15 +155,16 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { override fun onPause() { super.onPause() - mCurrAsyncTask?.shouldStop = true storeDirectories() directories_refresh_layout.isRefreshing = false mIsGettingDirs = false mStoredAnimateGifs = config.animateGifs mStoredCropThumbnails = config.cropThumbnails mStoredScrollHorizontally = config.scrollHorizontally + mStoredTextColor = config.textColor directories_grid.listener = null mLastMediaHandler.removeCallbacksAndMessages(null) + mCurrAsyncTask?.stopFetching() } override fun onDestroy() { @@ -178,6 +189,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { showAllMedia() else getDirectories() + setupLayoutManager() checkIfColorChanged() } else { @@ -216,12 +228,16 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { mCurrAsyncTask = GetDirectoriesAsynctask(applicationContext, mIsPickVideoIntent || mIsGetVideoContentIntent, mIsPickImageIntent || mIsGetImageContentIntent) { gotDirectories(addTempFolderIfNeeded(it), false) } - mCurrAsyncTask!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + mCurrAsyncTask!!.execute() } private fun showSortingDialog() { ChangeSortingDialog(this, true, false) { - getDirectories() + if (config.directorySorting and SORT_BY_DATE_MODIFIED > 0 || config.directorySorting and SORT_BY_DATE_TAKEN > 0) { + getDirectories() + } else { + gotDirectories(mDirs, true) + } } } @@ -236,9 +252,28 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { config.showAll = true Intent(this, MediaActivity::class.java).apply { putExtra(DIRECTORY, "/") - startActivity(this) + + if (mIsThirdPartyIntent) { + handleMediaIntent(this) + } else { + startActivity(this) + finish() + } + } + } + + private fun changeViewType() { + val items = arrayListOf( + RadioItem(VIEW_TYPE_GRID, getString(R.string.grid)), + RadioItem(VIEW_TYPE_LIST, getString(R.string.list))) + + RadioGroupDialog(this, items, config.viewTypeFolders) { + config.viewTypeFolders = it as Int + invalidateOptionsMenu() + setupLayoutManager() + directories_grid.adapter = null + setupAdapter() } - finish() } private fun tryToggleTemporarilyShowHidden() { @@ -278,6 +313,13 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { private fun getRecyclerAdapter() = (directories_grid.adapter as DirectoryAdapter) private fun setupLayoutManager() { + if (config.viewTypeFolders == VIEW_TYPE_GRID) + setupGridLayoutManager() + else + setupListLayoutManager() + } + + private fun setupGridLayoutManager() { val layoutManager = directories_grid.layoutManager as GridLayoutManager if (config.scrollHorizontally) { layoutManager.orientation = GridLayoutManager.HORIZONTAL @@ -315,6 +357,16 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { } } + private fun setupListLayoutManager() { + directories_grid.isDragSelectionEnabled = true + directories_grid.isZoomingEnabled = false + + val layoutManager = directories_grid.layoutManager as GridLayoutManager + layoutManager.spanCount = 1 + layoutManager.orientation = GridLayoutManager.VERTICAL + directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + private fun createNewFolder() { FilePickerDialog(this, internalStoragePath, false, config.shouldShowHidden) { CreateNewFolderDialog(this, it) { @@ -366,36 +418,22 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { if (resultCode == Activity.RESULT_OK) { - if (requestCode == PICK_MEDIA && resultData?.data != null) { - Intent().apply { + if (requestCode == PICK_MEDIA && resultData != null) { + val resultIntent = Intent() + if (mIsGetImageContentIntent || mIsGetVideoContentIntent || mIsGetAnyContentIntent) { + when { + intent.extras?.containsKey(MediaStore.EXTRA_OUTPUT) == true -> fillExtraOutput(resultData) + resultData.extras?.containsKey(PICKED_PATHS) == true -> fillPickedPaths(resultData, resultIntent) + else -> fillIntentPath(resultData, resultIntent) + } + } else if ((mIsPickImageIntent || mIsPickVideoIntent)) { val path = resultData.data.path val uri = Uri.fromFile(File(path)) - if (mIsGetImageContentIntent || mIsGetVideoContentIntent || mIsGetAnyContentIntent) { - if (intent.extras?.containsKey(MediaStore.EXTRA_OUTPUT) == true) { - var inputStream: InputStream? = null - var outputStream: OutputStream? = null - try { - val output = intent.extras.get(MediaStore.EXTRA_OUTPUT) as Uri - inputStream = FileInputStream(File(path)) - outputStream = contentResolver.openOutputStream(output) - inputStream.copyTo(outputStream) - } catch (ignored: FileNotFoundException) { - } finally { - inputStream?.close() - outputStream?.close() - } - } else { - val type = File(path).getMimeType("image/jpeg") - setDataAndTypeAndNormalize(uri, type) - flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - } - } else if (mIsPickImageIntent || mIsPickVideoIntent) { - data = uri - flags = Intent.FLAG_GRANT_READ_URI_PERMISSION - } - - setResult(Activity.RESULT_OK, this) + resultIntent.data = uri + resultIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION } + + setResult(Activity.RESULT_OK, resultIntent) finish() } else if (requestCode == PICK_WALLPAPER) { setResult(Activity.RESULT_OK) @@ -405,10 +443,51 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { super.onActivityResult(requestCode, resultCode, resultData) } + private fun fillExtraOutput(resultData: Intent) { + val path = resultData.data.path + var inputStream: InputStream? = null + var outputStream: OutputStream? = null + try { + val output = intent.extras.get(MediaStore.EXTRA_OUTPUT) as Uri + inputStream = FileInputStream(File(path)) + outputStream = contentResolver.openOutputStream(output) + inputStream.copyTo(outputStream) + } catch (ignored: FileNotFoundException) { + } finally { + inputStream?.close() + outputStream?.close() + } + } + + private fun fillPickedPaths(resultData: Intent, resultIntent: Intent) { + val paths = resultData.extras.getStringArrayList(PICKED_PATHS) + val uris = paths.map { Uri.fromFile(File(it)) } as ArrayList + val clipData = ClipData("Attachment", arrayOf("image/*", "video/*"), ClipData.Item(uris.removeAt(0))) + + uris.forEach { + clipData.addItem(ClipData.Item(it)) + } + + resultIntent.clipData = clipData + } + + private fun fillIntentPath(resultData: Intent, resultIntent: Intent) { + val path = resultData.data.path + val uri = Uri.fromFile(File(path)) + val type = File(path).getMimeType("image/jpeg") + resultIntent.setDataAndTypeAndNormalize(uri, type) + resultIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + } + private fun itemClicked(path: String) { Intent(this, MediaActivity::class.java).apply { putExtra(DIRECTORY, path) + handleMediaIntent(this) + } + } + private fun handleMediaIntent(intent: Intent) { + intent.apply { if (mIsSetWallpaperIntent) { putExtra(SET_WALLPAPER_INTENT, true) startActivityForResult(this, PICK_WALLPAPER) @@ -416,13 +495,16 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { putExtra(GET_IMAGE_INTENT, mIsPickImageIntent || mIsGetImageContentIntent) putExtra(GET_VIDEO_INTENT, mIsPickVideoIntent || mIsGetVideoContentIntent) putExtra(GET_ANY_INTENT, mIsGetAnyContentIntent) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)) startActivityForResult(this, PICK_MEDIA) } } } - private fun gotDirectories(dirs: ArrayList, isFromCache: Boolean) { - mLastMediaModified = getLastMediaModified() + private fun gotDirectories(newDirs: ArrayList, isFromCache: Boolean) { + val dirs = getSortedDirectories(newDirs) + + mLatestMediaId = getLatestMediaId() directories_refresh_layout.isRefreshing = false mIsGettingDirs = false @@ -430,8 +512,9 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { directories_empty_text.beVisibleIf(dirs.isEmpty() && !isFromCache) checkLastMediaChanged() - if (dirs.hashCode() == mDirs.hashCode()) + if (dirs.hashCode() == mDirs.hashCode()) { return + } mDirs = dirs @@ -462,13 +545,16 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { } private fun setupScrollDirection() { + val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFolders == VIEW_TYPE_GRID + directories_refresh_layout.isEnabled = !config.scrollHorizontally + directories_vertical_fastscroller.isHorizontal = false - directories_vertical_fastscroller.beGoneIf(config.scrollHorizontally) + directories_vertical_fastscroller.beGoneIf(allowHorizontalScroll) directories_horizontal_fastscroller.isHorizontal = true - directories_horizontal_fastscroller.beVisibleIf(config.scrollHorizontally) + directories_horizontal_fastscroller.beVisibleIf(allowHorizontalScroll) - if (config.scrollHorizontally) { + if (allowHorizontalScroll) { directories_horizontal_fastscroller.setViews(directories_grid, directories_refresh_layout) } else { directories_vertical_fastscroller.setViews(directories_grid, directories_refresh_layout) @@ -482,9 +568,9 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { mLastMediaHandler.removeCallbacksAndMessages(null) mLastMediaHandler.postDelayed({ Thread({ - val lastModified = getLastMediaModified() - if (mLastMediaModified != lastModified) { - mLastMediaModified = lastModified + val mediaId = getLatestMediaId() + if (mLatestMediaId != mediaId) { + mLatestMediaId = mediaId runOnUiThread { getDirectories() } @@ -503,6 +589,10 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { directories_grid.setDragSelectActive(position) } + override fun recheckPinnedFolders() { + gotDirectories(movePinnedDirectoriesToFront(mDirs), true) + } + private fun checkWhatsNewDialog() { arrayListOf().apply { add(Release(46, R.string.release_46)) @@ -540,6 +630,7 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { add(Release(123, R.string.release_123)) add(Release(125, R.string.release_125)) add(Release(127, R.string.release_127)) + add(Release(133, R.string.release_133)) checkWhatsNew(this, BuildConfig.VERSION_CODE) } } 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 e5d2d7fc7..4851c7305 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MediaActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MediaActivity.kt @@ -20,7 +20,9 @@ import com.bumptech.glide.request.transition.Transition import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.simplemobiletools.commons.dialogs.ConfirmationDialog +import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.views.MyScalableRecyclerView import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.adapters.MediaAdapter @@ -36,8 +38,7 @@ import java.io.File import java.io.IOException class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { - private val TAG = MediaActivity::class.java.simpleName - private val SAVE_MEDIA_CNT = 40 + private val SAVE_MEDIA_CNT = 100 private val LAST_MEDIA_CHECK_PERIOD = 3000L private var mPath = "" @@ -45,14 +46,17 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { private var mIsGetVideoIntent = false private var mIsGetAnyIntent = false private var mIsGettingMedia = false + private var mAllowPickingMultiple = false private var mShowAll = false private var mLoadedInitialPhotos = false private var mStoredAnimateGifs = true private var mStoredCropThumbnails = true private var mStoredScrollHorizontally = true + private var mStoredTextColor = 0 private var mLastDrawnHashCode = 0 - private var mLastMediaModified = 0 + private var mLatestMediaId = 0L private var mLastMediaHandler = Handler() + private var mCurrAsyncTask: GetMediaAsynctask? = null companion object { var mMedia = ArrayList() @@ -65,6 +69,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { mIsGetImageIntent = getBooleanExtra(GET_IMAGE_INTENT, false) mIsGetVideoIntent = getBooleanExtra(GET_VIDEO_INTENT, false) mIsGetAnyIntent = getBooleanExtra(GET_ANY_INTENT, false) + mAllowPickingMultiple = getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) } media_refresh_layout.setOnRefreshListener({ getMedia() }) @@ -72,6 +77,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { mStoredAnimateGifs = config.animateGifs mStoredCropThumbnails = config.cropThumbnails mStoredScrollHorizontally = config.scrollHorizontally + mStoredTextColor = config.textColor mShowAll = config.showAll if (mShowAll) supportActionBar?.setDisplayHomeAsUpEnabled(false) @@ -92,13 +98,17 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { } if (mStoredScrollHorizontally != config.scrollHorizontally) { - media_grid.adapter?.let { - (it as MediaAdapter).scrollVertically = !config.scrollHorizontally - it.notifyDataSetChanged() + (media_grid.adapter as? MediaAdapter)?.apply { + scrollVertically = config.viewTypeFiles == VIEW_TYPE_LIST || !config.scrollHorizontally + notifyDataSetChanged() } setupScrollDirection() } + if (mStoredTextColor != config.textColor) { + (media_grid.adapter as? MediaAdapter)?.updateTextColor(config.textColor) + } + tryloadGallery() invalidateOptionsMenu() media_empty_text_label.setTextColor(config.textColor) @@ -112,8 +122,10 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { mStoredAnimateGifs = config.animateGifs mStoredCropThumbnails = config.cropThumbnails mStoredScrollHorizontally = config.scrollHorizontally + mStoredTextColor = config.textColor media_grid.listener = null mLastMediaHandler.removeCallbacksAndMessages(null) + mCurrAsyncTask?.stopFetching() } override fun onDestroy() { @@ -149,7 +161,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { val currAdapter = media_grid.adapter if (currAdapter == null) { - media_grid.adapter = MediaAdapter(this, mMedia, this, mIsGetAnyIntent) { + media_grid.adapter = MediaAdapter(this, mMedia, this, mIsGetAnyIntent, mAllowPickingMultiple) { itemClicked(it.path) } } else { @@ -159,13 +171,16 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { } private fun setupScrollDirection() { + val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID + media_refresh_layout.isEnabled = !config.scrollHorizontally + media_vertical_fastscroller.isHorizontal = false - media_vertical_fastscroller.beGoneIf(config.scrollHorizontally) + media_vertical_fastscroller.beGoneIf(allowHorizontalScroll) media_horizontal_fastscroller.isHorizontal = true - media_horizontal_fastscroller.beVisibleIf(config.scrollHorizontally) + media_horizontal_fastscroller.beVisibleIf(allowHorizontalScroll) - if (config.scrollHorizontally) { + if (allowHorizontalScroll) { media_horizontal_fastscroller.setViews(media_grid, media_refresh_layout) } else { media_vertical_fastscroller.setViews(media_grid, media_refresh_layout) @@ -179,9 +194,9 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { mLastMediaHandler.removeCallbacksAndMessages(null) mLastMediaHandler.postDelayed({ Thread({ - val lastModified = getLastMediaModified() - if (mLastMediaModified != lastModified) { - mLastMediaModified = lastModified + val mediaId = getLatestMediaId() + if (mLatestMediaId != mediaId) { + mLatestMediaId = mediaId runOnUiThread { getMedia() } @@ -207,8 +222,10 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden - findItem(R.id.increase_column_count).isVisible = config.mediaColumnCnt < 10 - findItem(R.id.reduce_column_count).isVisible = config.mediaColumnCnt > 1 + findItem(R.id.increase_column_count).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID && config.mediaColumnCnt < 10 + findItem(R.id.reduce_column_count).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID && config.mediaColumnCnt > 1 + + findItem(R.id.toggle_filename).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID } return true @@ -221,6 +238,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { R.id.toggle_filename -> toggleFilenameVisibility() R.id.open_camera -> launchCamera() R.id.folder_view -> switchToFolderView() + R.id.change_view_type -> changeViewType() R.id.hide_folder -> tryHideFolder() R.id.unhide_folder -> unhideFolder() R.id.exclude_folder -> tryExcludeFolder() @@ -260,6 +278,20 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { finish() } + private fun changeViewType() { + val items = arrayListOf( + RadioItem(VIEW_TYPE_GRID, getString(R.string.grid)), + RadioItem(VIEW_TYPE_LIST, getString(R.string.list))) + + RadioGroupDialog(this, items, config.viewTypeFiles) { + config.viewTypeFiles = it as Int + invalidateOptionsMenu() + setupLayoutManager() + media_grid.adapter = null + setupAdapter() + } + } + private fun tryHideFolder() { if (config.wasHideFolderTooltipShown) { hideFolder() @@ -309,7 +341,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { mIsGettingMedia = true val token = object : TypeToken>() {}.type - val media = Gson().fromJson>(config.loadFolderMedia(mPath), token) ?: ArrayList(1) + val media = Gson().fromJson>(config.loadFolderMedia(mPath), token) ?: ArrayList(1) if (media.isNotEmpty() && !mLoadedInitialPhotos) { gotMedia(media, true) } else { @@ -317,9 +349,10 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { } mLoadedInitialPhotos = true - GetMediaAsynctask(applicationContext, mPath, mIsGetVideoIntent, mIsGetImageIntent, mShowAll) { + mCurrAsyncTask = GetMediaAsynctask(applicationContext, mPath, mIsGetVideoIntent, mIsGetImageIntent, mShowAll) { gotMedia(it) - }.execute() + } + mCurrAsyncTask!!.execute() } private fun isDirEmpty(): Boolean { @@ -350,6 +383,13 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { private fun getRecyclerAdapter() = (media_grid.adapter as MediaAdapter) private fun setupLayoutManager() { + if (config.viewTypeFiles == VIEW_TYPE_GRID) + setupGridLayoutManager() + else + setupListLayoutManager() + } + + private fun setupGridLayoutManager() { val layoutManager = media_grid.layoutManager as GridLayoutManager if (config.scrollHorizontally) { layoutManager.orientation = GridLayoutManager.HORIZONTAL @@ -387,6 +427,16 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { } } + private fun setupListLayoutManager() { + media_grid.isDragSelectionEnabled = true + media_grid.isZoomingEnabled = false + + val layoutManager = media_grid.layoutManager as GridLayoutManager + layoutManager.spanCount = 1 + layoutManager.orientation = GridLayoutManager.VERTICAL + media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + private fun increaseColumnCount() { config.mediaColumnCnt = ++(media_grid.layoutManager as GridLayoutManager).spanCount invalidateOptionsMenu() @@ -461,7 +511,7 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { } private fun gotMedia(media: ArrayList, isFromCache: Boolean = false) { - mLastMediaModified = getLastMediaModified() + mLatestMediaId = getLatestMediaId() mIsGettingMedia = false media_refresh_layout.isRefreshing = false @@ -513,4 +563,12 @@ class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { override fun itemLongClicked(position: Int) { media_grid.setDragSelectActive(position) } + + override fun selectedPaths(paths: ArrayList) { + Intent().apply { + putExtra(PICKED_PATHS, paths) + setResult(Activity.RESULT_OK, this) + } + finish() + } } 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 96fc44b10..4a5b48097 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoVideoActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoVideoActivity.kt @@ -21,6 +21,7 @@ import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.fragments.PhotoFragment import com.simplemobiletools.gallery.fragments.VideoFragment import com.simplemobiletools.gallery.fragments.ViewPagerFragment +import com.simplemobiletools.gallery.helpers.IS_FROM_GALLERY import com.simplemobiletools.gallery.helpers.IS_VIEW_INTENT import com.simplemobiletools.gallery.helpers.MEDIUM import com.simplemobiletools.gallery.models.Medium @@ -31,6 +32,7 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList private val STORAGE_PERMISSION = 1 private var mMedium: Medium? = null private var mIsFullScreen = false + private var mIsFromGallery = false private var mFragment: ViewPagerFragment? = null lateinit var mUri: Uri @@ -52,6 +54,7 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList private fun checkIntent(savedInstanceState: Bundle? = null) { mUri = intent.data ?: return + mIsFromGallery = intent.getBooleanExtra(IS_FROM_GALLERY, false) if (mUri.scheme == "file") { scanPath(mUri.path) {} @@ -126,6 +129,7 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList private fun sendViewPagerIntent(path: String) { Intent(this, ViewPagerActivity::class.java).apply { putExtra(IS_VIEW_INTENT, true) + putExtra(IS_FROM_GALLERY, mIsFromGallery) putExtra(MEDIUM, path) startActivity(this) } 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 92329d023..8ba896f00 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SettingsActivity.kt @@ -8,6 +8,7 @@ import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.dialogs.SecurityDialog import com.simplemobiletools.commons.extensions.handleHiddenFolderPasswordProtection import com.simplemobiletools.commons.extensions.updateTextColors +import com.simplemobiletools.commons.helpers.PROTECTION_FINGERPRINT import com.simplemobiletools.commons.helpers.SHOW_ALL_TABS import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.gallery.R @@ -169,7 +170,9 @@ class SettingsActivity : SimpleActivity() { config.protectionType = type if (config.isPasswordProtectionOn) { - ConfirmationDialog(this, "", R.string.protection_setup_successfully, R.string.ok, 0) { } + val confirmationTextId = if (config.protectionType == PROTECTION_FINGERPRINT) + R.string.fingerprint_setup_successfully else R.string.protection_setup_successfully + ConfirmationDialog(this, "", confirmationTextId, R.string.ok, 0) { } } } } 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 fb809047b..7aacc2376 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt @@ -161,6 +161,10 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View if (config.isThirdPartyIntent) { config.isThirdPartyIntent = false + + if (intent.extras == null || !intent.getBooleanExtra(IS_FROM_GALLERY, false)) { + mMedia.clear() + } } } @@ -326,7 +330,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View val dragPosition = animation.animatedValue as Int val dragOffset = dragPosition - oldDragPosition oldDragPosition = dragPosition - view_pager.fakeDragBy(dragOffset * (if (forward) 1f else -1f)) + view_pager?.fakeDragBy(dragOffset * (if (forward) 1f else -1f)) } }) @@ -433,34 +437,6 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View } private fun rotateImage() { - val currentMedium = getCurrentMedium() ?: return - if (currentMedium.isJpg() && !isPathOnSD(currentMedium.path)) { - rotateByExif() - } else { - rotateByDegrees() - } - } - - private fun rotateByExif() { - val exif = ExifInterface(getCurrentPath()) - val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) - val newOrientation = getNewOrientation(orientation) - exif.setAttribute(ExifInterface.TAG_ORIENTATION, newOrientation) - exif.saveAttributes() - File(getCurrentPath()).setLastModified(System.currentTimeMillis()) - (getCurrentFragment() as? PhotoFragment)?.refreshBitmap() - } - - private fun getNewOrientation(rotation: Int): String { - return when (rotation) { - ExifInterface.ORIENTATION_ROTATE_90 -> ExifInterface.ORIENTATION_ROTATE_180 - ExifInterface.ORIENTATION_ROTATE_180 -> ExifInterface.ORIENTATION_ROTATE_270 - ExifInterface.ORIENTATION_ROTATE_270 -> ExifInterface.ORIENTATION_NORMAL - else -> ExifInterface.ORIENTATION_ROTATE_90 - }.toString() - } - - private fun rotateByDegrees() { mRotationDegrees = (mRotationDegrees + 90) % 360 getCurrentFragment()?.let { (it as? PhotoFragment)?.rotateImageViewBy(mRotationDegrees) @@ -474,7 +450,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View Thread({ toast(R.string.saving) val selectedFile = File(it) - val tmpFile = File(selectedFile.parent, "tmp_${it.getFilenameFromPath()}") + val tmpFile = File(selectedFile.parent, ".tmp_${it.getFilenameFromPath()}") try { val bitmap = BitmapFactory.decodeFile(currPath) getFileOutputStream(tmpFile) { @@ -497,7 +473,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View toast(R.string.out_of_memory_error) deleteFile(tmpFile) {} } catch (e: Exception) { - toast(R.string.unknown_error_occurred) + showErrorToast(e) deleteFile(tmpFile) {} } }).start() @@ -510,11 +486,12 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View val bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) bmp.compress(file.getCompressionFormat(), 90, out) out.flush() - toast(R.string.file_saved) out.close() + toast(R.string.file_saved) + mRotationDegrees = 0f + invalidateOptionsMenu() } - private fun isShowHiddenFlagNeeded(): Boolean { val file = File(mPath) if (file.isHidden) 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 08e08e439..9454f0ca4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/DirectoryAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/DirectoryAdapter.kt @@ -20,9 +20,10 @@ import com.simplemobiletools.gallery.activities.SimpleActivity import com.simplemobiletools.gallery.dialogs.ExcludeFolderDialog import com.simplemobiletools.gallery.dialogs.PickMediumDialog import com.simplemobiletools.gallery.extensions.* +import com.simplemobiletools.gallery.helpers.VIEW_TYPE_LIST import com.simplemobiletools.gallery.models.AlbumCover import com.simplemobiletools.gallery.models.Directory -import kotlinx.android.synthetic.main.directory_item.view.* +import kotlinx.android.synthetic.main.directory_item_list.view.* import java.io.File import java.util.* @@ -31,11 +32,13 @@ class DirectoryAdapter(val activity: SimpleActivity, var dirs: MutableList() val selectedPositions = HashSet() var primaryColor = config.primaryColor + var textColor = config.textColor var pinnedFolders = config.pinnedFolders var scrollVertically = !config.scrollHorizontally @@ -56,12 +59,12 @@ class DirectoryAdapter(val activity: SimpleActivity, var dirs: MutableList = selectedPositions } - val multiSelectorMode = object : ModalMultiSelectorCallback(multiSelector) { + private val multiSelectorMode = object : ModalMultiSelectorCallback(multiSelector) { override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { when (item.itemId) { R.id.cab_properties -> showProperties() @@ -119,7 +122,7 @@ class DirectoryAdapter(val activity: SimpleActivity, var dirs: MutableList() - var curIndex = 0 - for (i in 0..itemViews.size() - 1) { - if (itemViews[i] != null) { - newItems.put(curIndex, itemViews[i]) - curIndex++ - } - } + (0 until itemViews.size()) + .filter { itemViews[it] != null } + .forEachIndexed { curIndex, i -> newItems.put(curIndex, itemViews[i]) } itemViews = newItems } @@ -330,13 +329,14 @@ class DirectoryAdapter(val activity: SimpleActivity, var dirs: MutableList) { dirs = newDirs notifyDataSetChanged() + actMode?.finish() + } + + fun updateTextColor(textColor: Int) { + this.textColor = textColor + notifyDataSetChanged() } fun selectItem(pos: Int) { @@ -369,7 +375,7 @@ class DirectoryAdapter(val activity: SimpleActivity, var dirs: MutableList -1 && min < to) { - (min..to - 1).filter { it != from } + (min until to).filter { it != from } .forEach { toggleItemSelection(false, it) } } if (max > -1) { @@ -386,7 +392,7 @@ class DirectoryAdapter(val activity: SimpleActivity, var dirs: MutableList -1) { - for (i in min..from - 1) + for (i in min until from) toggleItemSelection(false, i) } } @@ -395,47 +401,52 @@ class DirectoryAdapter(val activity: SimpleActivity, var dirs: MutableList (Unit)) : SwappingHolder(view, MultiSelector()) { - fun bindView(directory: Directory, isPinned: Boolean, scrollVertically: Boolean): View { + fun bindView(directory: Directory, isPinned: Boolean, scrollVertically: Boolean, isListView: Boolean, textColor: Int): View { itemView.apply { dir_name.text = directory.name + dir_path?.text = "${directory.path.substringBeforeLast("/")}/" photo_cnt.text = directory.mediaCnt.toString() activity.loadImage(directory.tmb, dir_thumbnail, scrollVertically) dir_pin.beVisibleIf(isPinned) dir_sd_card.beVisibleIf(activity.isPathOnSD(directory.path)) + if (isListView) { + dir_name.setTextColor(textColor) + dir_path.setTextColor(textColor) + photo_cnt.setTextColor(textColor) + dir_pin.setColorFilter(textColor, PorterDuff.Mode.SRC_IN) + dir_sd_card.setColorFilter(textColor, PorterDuff.Mode.SRC_IN) + } + setOnClickListener { viewClicked(directory) } setOnLongClickListener { if (isPickIntent) viewClicked(directory) else viewLongClicked(); true } - - } return itemView } - fun viewClicked(directory: Directory) { + private fun viewClicked(directory: Directory) { if (multiSelector.isSelectable) { - val isSelected = adapterListener.getSelectedPositions().contains(layoutPosition) - adapterListener.toggleItemSelectionAdapter(!isSelected, layoutPosition) + val isSelected = adapterListener.getSelectedPositions().contains(adapterPosition) + adapterListener.toggleItemSelectionAdapter(!isSelected, adapterPosition) } else { itemClick(directory) } } - fun viewLongClicked() { + private fun viewLongClicked() { if (listener != null) { if (!multiSelector.isSelectable) { activity.startSupportActionMode(multiSelectorCallback) - adapterListener.toggleItemSelectionAdapter(true, layoutPosition) + adapterListener.toggleItemSelectionAdapter(true, adapterPosition) } - listener.itemLongClicked(layoutPosition) + listener.itemLongClicked(adapterPosition) } } fun stopLoad() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed) - return - - Glide.with(activity).clear(view.dir_thumbnail) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !activity.isDestroyed) + Glide.with(activity).clear(view.dir_thumbnail) } } @@ -451,5 +462,7 @@ class DirectoryAdapter(val activity: SimpleActivity, var dirs: MutableList) fun itemLongClicked(position: Int) + + fun recheckPinnedFolders() } } 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 eb9a97a91..7296f51d7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MediaAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MediaAdapter.kt @@ -18,21 +18,24 @@ import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.activities.SimpleActivity import com.simplemobiletools.gallery.extensions.* +import com.simplemobiletools.gallery.helpers.VIEW_TYPE_LIST import com.simplemobiletools.gallery.models.Medium -import kotlinx.android.synthetic.main.photo_video_item.view.* +import kotlinx.android.synthetic.main.photo_video_item_grid.view.* import java.io.File import java.util.* class MediaAdapter(val activity: SimpleActivity, var media: MutableList, val listener: MediaOperationsListener?, val isPickIntent: Boolean, - val itemClick: (Medium) -> Unit) : RecyclerView.Adapter() { + val allowMultiplePicks: Boolean, val itemClick: (Medium) -> Unit) : RecyclerView.Adapter() { val multiSelector = MultiSelector() val config = activity.config + val isListViewType = config.viewTypeFiles == VIEW_TYPE_LIST var actMode: ActionMode? = null var itemViews = SparseArray() val selectedPositions = HashSet() var primaryColor = config.primaryColor + var textColor = config.textColor var displayFilenames = config.displayFileNames var scrollVertically = !config.scrollHorizontally @@ -53,12 +56,12 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, updateTitle(selectedPositions.size) } - fun updateTitle(cnt: Int) { + private fun updateTitle(cnt: Int) { actMode?.title = "$cnt / ${media.size}" actMode?.invalidate() } - val adapterListener = object : MyAdapterListener { + private val adapterListener = object : MyAdapterListener { override fun toggleItemSelectionAdapter(select: Boolean, position: Int) { toggleItemSelection(select, position) } @@ -66,9 +69,10 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, override fun getSelectedPositions(): HashSet = selectedPositions } - val multiSelectorMode = object : ModalMultiSelectorCallback(multiSelector) { + private val multiSelectorMode = object : ModalMultiSelectorCallback(multiSelector) { override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { when (item.itemId) { + R.id.cab_confirm_selection -> confirmSelection() R.id.cab_properties -> showProperties() R.id.cab_rename -> renameFile() R.id.cab_edit -> editFile() @@ -94,6 +98,7 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, override fun onPrepareActionMode(actionMode: ActionMode?, menu: Menu): Boolean { menu.findItem(R.id.cab_rename).isVisible = selectedPositions.size <= 1 menu.findItem(R.id.cab_edit).isVisible = selectedPositions.size == 1 && media.size > selectedPositions.first() && media[selectedPositions.first()].isImage() + menu.findItem(R.id.cab_confirm_selection).isVisible = isPickIntent && allowMultiplePicks && selectedPositions.size > 0 checkHideBtnVisibility(menu) @@ -112,7 +117,7 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, fun checkHideBtnVisibility(menu: Menu) { var hiddenCnt = 0 var unhiddenCnt = 0 - selectedPositions.map { media.getOrNull(it) }.filterNotNull().forEach { + selectedPositions.mapNotNull { media.getOrNull(it) }.forEach { if (it.name.startsWith('.')) hiddenCnt++ else @@ -124,6 +129,11 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, } } + private fun confirmSelection() { + val paths = getSelectedMedia().map { it.path } as ArrayList + listener?.selectedPaths(paths) + } + private fun showProperties() { if (selectedPositions.size <= 1) { PropertiesDialog(activity, media[selectedPositions.first()].path, config.shouldShowHidden) @@ -184,7 +194,7 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, fun selectAll() { val cnt = media.size - for (i in 0..cnt - 1) { + for (i in 0 until cnt) { selectedPositions.add(i) multiSelector.setSelected(i, 0, true) notifyItemChanged(i) @@ -227,13 +237,9 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, listener?.deleteFiles(files) val newItems = SparseArray() - var curIndex = 0 - for (i in 0..itemViews.size() - 1) { - if (itemViews[i] != null) { - newItems.put(curIndex, itemViews[i]) - curIndex++ - } - } + (0 until itemViews.size()) + .filter { itemViews[it] != null } + .forEachIndexed { curIndex, i -> newItems.put(curIndex, itemViews[i]) } itemViews = newItems } @@ -246,12 +252,13 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder { - val view = LayoutInflater.from(parent?.context).inflate(R.layout.photo_video_item, parent, false) - return ViewHolder(view, adapterListener, activity, multiSelectorMode, multiSelector, listener, isPickIntent, itemClick) + val layoutType = if (isListViewType) R.layout.photo_video_item_list else R.layout.photo_video_item_grid + val view = LayoutInflater.from(parent?.context).inflate(layoutType, parent, false) + return ViewHolder(view, adapterListener, activity, multiSelectorMode, multiSelector, listener, allowMultiplePicks || !isPickIntent, itemClick) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - itemViews.put(position, holder.bindView(media[position], displayFilenames, scrollVertically)) + itemViews.put(position, holder.bindView(media[position], displayFilenames, scrollVertically, isListViewType, textColor)) toggleItemSelection(selectedPositions.contains(position), position) holder.itemView.tag = holder } @@ -266,6 +273,7 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, fun updateMedia(newMedia: ArrayList) { media = newMedia notifyDataSetChanged() + actMode?.finish() } fun updateDisplayFilenames(display: Boolean) { @@ -273,6 +281,11 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, notifyDataSetChanged() } + fun updateTextColor(textColor: Int) { + this.textColor = textColor + notifyDataSetChanged() + } + fun selectItem(pos: Int) { toggleItemSelection(true, pos) } @@ -289,7 +302,7 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, toggleItemSelection(true, i) if (min > -1 && min < to) { - (min..to - 1).filter { it != from } + (min until to).filter { it != from } .forEach { toggleItemSelection(false, it) } } if (max > -1) { @@ -306,53 +319,57 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, } if (min > -1) { - for (i in min..from - 1) + for (i in min until from) toggleItemSelection(false, i) } } } class ViewHolder(val view: View, val adapterListener: MyAdapterListener, val activity: SimpleActivity, val multiSelectorCallback: ModalMultiSelectorCallback, - val multiSelector: MultiSelector, val listener: MediaOperationsListener?, val isPickIntent: Boolean, val itemClick: (Medium) -> (Unit)) : + val multiSelector: MultiSelector, val listener: MediaOperationsListener?, val allowMultiplePicks: Boolean, + val itemClick: (Medium) -> (Unit)) : SwappingHolder(view, MultiSelector()) { - fun bindView(medium: Medium, displayFilenames: Boolean, scrollVertically: Boolean): View { + fun bindView(medium: Medium, displayFilenames: Boolean, scrollVertically: Boolean, isListViewType: Boolean, textColor: Int): View { itemView.apply { play_outline.visibility = if (medium.video) View.VISIBLE else View.GONE - photo_name.beVisibleIf(displayFilenames) + photo_name.beVisibleIf(displayFilenames || isListViewType) photo_name.text = medium.name activity.loadImage(medium.path, medium_thumbnail, scrollVertically) + if (isListViewType) { + photo_name.setTextColor(textColor) + play_outline.setColorFilter(textColor, PorterDuff.Mode.SRC_IN) + } + setOnClickListener { viewClicked(medium) } - setOnLongClickListener { if (isPickIntent) viewClicked(medium) else viewLongClicked(); true } + setOnLongClickListener { if (allowMultiplePicks) viewLongClicked() else viewClicked(medium); true } } return itemView } - fun viewClicked(medium: Medium) { + private fun viewClicked(medium: Medium) { if (multiSelector.isSelectable) { - val isSelected = adapterListener.getSelectedPositions().contains(layoutPosition) - adapterListener.toggleItemSelectionAdapter(!isSelected, layoutPosition) + val isSelected = adapterListener.getSelectedPositions().contains(adapterPosition) + adapterListener.toggleItemSelectionAdapter(!isSelected, adapterPosition) } else { itemClick(medium) } } - fun viewLongClicked() { + private fun viewLongClicked() { if (listener != null) { if (!multiSelector.isSelectable) { activity.startSupportActionMode(multiSelectorCallback) - adapterListener.toggleItemSelectionAdapter(true, layoutPosition) + adapterListener.toggleItemSelectionAdapter(true, adapterPosition) } - listener.itemLongClicked(layoutPosition) + listener.itemLongClicked(adapterPosition) } } fun stopLoad() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed) - return - - Glide.with(activity).clear(view.medium_thumbnail) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !activity.isDestroyed) + Glide.with(activity).clear(view.medium_thumbnail) } } @@ -368,5 +385,7 @@ class MediaAdapter(val activity: SimpleActivity, var media: MutableList, fun deleteFiles(files: ArrayList) fun itemLongClicked(position: Int) + + 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 index b68d69d09..676b45f8b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetDirectoriesAsynctask.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetDirectoriesAsynctask.kt @@ -6,125 +6,70 @@ import com.simplemobiletools.commons.extensions.getFilenameFromPath import com.simplemobiletools.commons.extensions.hasWriteStoragePermission import com.simplemobiletools.commons.extensions.internalStoragePath import com.simplemobiletools.commons.extensions.sdCardPath +import com.simplemobiletools.commons.helpers.SORT_DESCENDING import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.extensions.containsNoMedia -import com.simplemobiletools.gallery.extensions.getFilesFrom +import com.simplemobiletools.gallery.extensions.sumByLong +import com.simplemobiletools.gallery.helpers.MediaFetcher import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.models.Medium import java.io.File -import java.util.* class GetDirectoriesAsynctask(val context: Context, val isPickVideo: Boolean, val isPickImage: Boolean, val callback: (dirs: ArrayList) -> Unit) : AsyncTask>() { - var config = context.config - var shouldStop = false - val showHidden = config.shouldShowHidden + val mediaFetcher = MediaFetcher(context) override fun doInBackground(vararg params: Void): ArrayList { if (!context.hasWriteStoragePermission()) return ArrayList() - val media = context.getFilesFrom("", isPickImage, isPickVideo) - val excludedPaths = config.excludedFolders - val includedPaths = config.includedFolders - val directories = groupDirectories(media) - val dirs = ArrayList(directories.values - .filter { File(it.path).exists() }) - .filter { shouldFolderBeVisible(it.path, excludedPaths, includedPaths) } as ArrayList - Directory.sorting = config.directorySorting - dirs.sort() - return movePinnedToFront(dirs) - } - - private fun groupDirectories(media: ArrayList): Map { - val albumCovers = config.parseAlbumCovers() + val config = context.config + val groupedMedia = mediaFetcher.getMediaByDirectories(isPickVideo, isPickImage) + val directories = ArrayList() val hidden = context.resources.getString(R.string.hidden) - val directories = LinkedHashMap() - for ((name, path, isVideo, dateModified, dateTaken, size) in media) { - if (shouldStop) - cancel(true) + val albumCovers = config.parseAlbumCovers() + for ((path, curMedia) in groupedMedia) { + Medium.sorting = config.getFileSorting(path) + curMedia.sort() - val parentDir = File(path).parent ?: continue - if (directories.containsKey(parentDir.toLowerCase())) { - val directory = directories[parentDir.toLowerCase()]!! - val newImageCnt = directory.mediaCnt + 1 - directory.mediaCnt = newImageCnt - directory.addSize(size) - } else { - var dirName = parentDir.getFilenameFromPath() - if (parentDir == context.internalStoragePath) { - dirName = context.getString(R.string.internal) - } else if (parentDir == context.sdCardPath) { - dirName = context.getString(R.string.sd_card) + val firstItem = curMedia.first() + val lastItem = curMedia.last() + val parentDir = File(firstItem.path).parent + var thumbnail = firstItem.path + albumCovers.forEach { + if (it.path == parentDir && File(it.tmb).exists()) { + thumbnail = it.tmb } - - if (File(parentDir).containsNoMedia()) { - dirName += " $hidden" - - if (!showHidden) - continue - } - - var thumbnail = path - albumCovers.forEach { - if (it.path == parentDir && File(it.tmb).exists()) { - thumbnail = it.tmb - } - } - - val directory = Directory(parentDir, thumbnail, dirName, 1, dateModified, dateTaken, size) - directories.put(parentDir.toLowerCase(), directory) } + + var dirName = when (parentDir) { + context.internalStoragePath -> context.getString(R.string.internal) + context.sdCardPath -> context.getString(R.string.sd_card) + else -> parentDir.getFilenameFromPath() + } + + if (File(parentDir).containsNoMedia()) { + dirName += " $hidden" + } + + 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) + directories.add(directory) } + return directories } - private fun shouldFolderBeVisible(path: String, excludedPaths: MutableSet, includedPaths: MutableSet): Boolean { - val file = File(path) - return if (includedPaths.contains(path)) { - true - } else if (isThisOrParentExcluded(path, excludedPaths, includedPaths)) { - false - } else if (!config.shouldShowHidden && file.isDirectory && file.canonicalFile == file.absoluteFile) { - var containsNoMediaOrDot = file.containsNoMedia() || path.contains("/.") - if (!containsNoMediaOrDot) { - containsNoMediaOrDot = checkParentHasNoMedia(file.parentFile) - } - !containsNoMediaOrDot - } else { - true - } - } - - private fun checkParentHasNoMedia(file: File): Boolean { - var curFile = file - while (true) { - if (curFile.containsNoMedia()) { - return true - } - curFile = curFile.parentFile - if (curFile.absolutePath == "/") - break - } - return false - } - - private fun isThisOrParentExcluded(path: String, excludedPaths: MutableSet, includedPaths: MutableSet) = - includedPaths.none { path.startsWith(it) } && excludedPaths.any { path.startsWith(it) } - - private fun movePinnedToFront(dirs: ArrayList): ArrayList { - val foundFolders = ArrayList() - val pinnedFolders = config.pinnedFolders - - dirs.forEach { if (pinnedFolders.contains(it.path)) foundFolders.add(it) } - dirs.removeAll(foundFolders) - dirs.addAll(0, foundFolders) - return dirs - } - 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 68595ae46..2c99f54d6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetMediaAsynctask.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetMediaAsynctask.kt @@ -2,21 +2,39 @@ package com.simplemobiletools.gallery.asynctasks import android.content.Context import android.os.AsyncTask -import com.simplemobiletools.gallery.extensions.getFilesFrom +import com.simplemobiletools.gallery.extensions.config +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, val showAll: Boolean, val callback: (media: ArrayList) -> Unit) : AsyncTask>() { + val mediaFetcher = MediaFetcher(context) override fun doInBackground(vararg params: Void): ArrayList { - val path = if (showAll) "" else mPath - return context.getFilesFrom(path, isPickImage, isPickVideo) + return if (showAll) { + val mediaMap = mediaFetcher.getMediaByDirectories(isPickVideo, isPickImage) + val media = ArrayList() + for ((path, curMedia) in mediaMap) { + media.addAll(curMedia) + } + + Medium.sorting = context.config.getFileSorting("") + media.sort() + media + } else { + mediaFetcher.getFilesFrom(mPath, isPickImage, isPickVideo) + } } override fun onPostExecute(media: ArrayList) { super.onPostExecute(media) callback(media) } + + fun stopFetching() { + mediaFetcher.shouldStop = true + cancel(true) + } } 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 a52a395cd..1d53684cb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickDirectoryDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickDirectoryDialog.kt @@ -15,6 +15,8 @@ import com.simplemobiletools.gallery.adapters.DirectoryAdapter import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.extensions.getCachedDirectories +import com.simplemobiletools.gallery.extensions.getSortedDirectories +import com.simplemobiletools.gallery.helpers.VIEW_TYPE_GRID import com.simplemobiletools.gallery.models.Directory import kotlinx.android.synthetic.main.dialog_directory_picker.view.* @@ -22,11 +24,12 @@ class PickDirectoryDialog(val activity: SimpleActivity, val sourcePath: String, var dialog: AlertDialog var shownDirectories: ArrayList = ArrayList() var view: View = LayoutInflater.from(activity).inflate(R.layout.dialog_directory_picker, null) + var isGridViewType = activity.config.viewTypeFolders == VIEW_TYPE_GRID init { (view.directories_grid.layoutManager as GridLayoutManager).apply { - orientation = if (activity.config.scrollHorizontally) GridLayoutManager.HORIZONTAL else GridLayoutManager.VERTICAL - spanCount = activity.config.dirColumnCnt + orientation = if (activity.config.scrollHorizontally && isGridViewType) GridLayoutManager.HORIZONTAL else GridLayoutManager.VERTICAL + spanCount = if (isGridViewType) activity.config.dirColumnCnt else 1 } dialog = AlertDialog.Builder(activity) @@ -47,19 +50,20 @@ class PickDirectoryDialog(val activity: SimpleActivity, val sourcePath: String, } } - fun showOtherFolder() { + private fun showOtherFolder() { val showHidden = activity.config.shouldShowHidden FilePickerDialog(activity, sourcePath, false, showHidden, true) { callback(it) } } - private fun gotDirectories(directories: ArrayList) { - if (directories.hashCode() == shownDirectories.hashCode()) + private fun gotDirectories(newDirs: ArrayList) { + val dirs = activity.getSortedDirectories(newDirs) + if (dirs.hashCode() == shownDirectories.hashCode()) return - shownDirectories = directories - val adapter = DirectoryAdapter(activity, directories, null, true) { + shownDirectories = dirs + val adapter = DirectoryAdapter(activity, dirs, null, true) { if (it.path.trimEnd('/') == sourcePath) { activity.toast(R.string.source_and_destination_same) return@DirectoryAdapter @@ -69,7 +73,7 @@ class PickDirectoryDialog(val activity: SimpleActivity, val sourcePath: String, } } - val scrollHorizontally = activity.config.scrollHorizontally + val scrollHorizontally = activity.config.scrollHorizontally && isGridViewType view.apply { directories_grid.adapter = adapter 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 4c57c68f1..fe0a86add 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickMediumDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickMediumDialog.kt @@ -47,7 +47,7 @@ class PickMediumDialog(val activity: SimpleActivity, val path: String, val callb return shownMedia = media - val adapter = MediaAdapter(activity, media, null, true) { + val adapter = MediaAdapter(activity, media, null, true, false) { callback(it.path) dialog.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 d66248274..756f9578b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/activity.kt @@ -24,6 +24,7 @@ import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.gallery.BuildConfig import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.activities.SimpleActivity +import com.simplemobiletools.gallery.helpers.IS_FROM_GALLERY import com.simplemobiletools.gallery.helpers.NOMEDIA import com.simplemobiletools.gallery.helpers.REQUEST_EDIT_IMAGE import com.simplemobiletools.gallery.helpers.REQUEST_SET_AS @@ -91,14 +92,14 @@ fun Activity.setAs(uri: Uri, file: File, showToast: Boolean = true): Boolean { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) val chooser = Intent.createChooser(this, getString(R.string.set_as)) - if (resolveActivity(packageManager) != null) { + success = if (resolveActivity(packageManager) != null) { startActivityForResult(chooser, REQUEST_SET_AS) - success = true + true } else { if (showToast) { toast(R.string.no_capable_app_found) } - success = false + false } } @@ -130,6 +131,7 @@ fun Activity.openWith(file: File, forceChooser: Boolean = true) { action = Intent.ACTION_VIEW setDataAndType(uri, file.getMimeType()) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + putExtra(IS_FROM_GALLERY, true) if (resolveActivity(packageManager) != null) { val chooser = Intent.createChooser(this, getString(R.string.open_with)) @@ -250,10 +252,10 @@ fun SimpleActivity.removeNoMedia(path: String, callback: () -> Unit) { fun SimpleActivity.toggleFileVisibility(oldFile: File, hide: Boolean, callback: (newFile: File) -> Unit) { val path = oldFile.parent var filename = oldFile.name - if (hide) { - filename = ".${filename.trimStart('.')}" + filename = if (hide) { + ".${filename.trimStart('.')}" } else { - filename = filename.substring(1, filename.length) + filename.substring(1, filename.length) } val newFile = File(path, filename) renameFile(oldFile, newFile) { 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..8d5d3ad27 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/arrayList.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.gallery.extensions + +import java.util.* + +fun ArrayList.sumByLong(selector: (E) -> Long) = map { selector(it) }.sum() 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 d0d6dcd00..f7cd61529 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/context.kt @@ -4,21 +4,45 @@ import android.content.Context import android.content.Intent import android.content.res.Configuration import android.database.Cursor +import android.graphics.Point import android.media.AudioManager import android.net.Uri +import android.os.Build import android.provider.MediaStore -import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED -import com.simplemobiletools.commons.helpers.SORT_BY_NAME -import com.simplemobiletools.commons.helpers.SORT_BY_SIZE -import com.simplemobiletools.commons.helpers.SORT_DESCENDING +import android.view.WindowManager +import com.simplemobiletools.commons.extensions.humanizePath import com.simplemobiletools.gallery.activities.SettingsActivity -import com.simplemobiletools.gallery.helpers.* -import com.simplemobiletools.gallery.models.Medium -import java.io.File +import com.simplemobiletools.gallery.helpers.Config +import com.simplemobiletools.gallery.models.Directory val Context.portrait get() = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT val Context.audioManager get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager +val Context.windowManager: WindowManager get() = getSystemService(Context.WINDOW_SERVICE) as WindowManager +val Context.navigationBarRight: Boolean get() = usableScreenSize.x < realScreenSize.x +val Context.navigationBarBottom: Boolean get() = usableScreenSize.y < realScreenSize.y +val Context.navigationBarHeight: Int get() = if (navigationBarBottom) navigationBarSize.y else 0 + +internal val Context.navigationBarSize: Point + get() = when { + navigationBarRight -> Point(realScreenSize.x - usableScreenSize.x, usableScreenSize.y) + navigationBarBottom -> Point(usableScreenSize.x, realScreenSize.y - usableScreenSize.y) + else -> Point() + } + +val Context.usableScreenSize: Point + get() { + val size = Point() + windowManager.defaultDisplay.getSize(size) + return size + } + +val Context.realScreenSize: Point + get() { + val size = Point() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) + windowManager.defaultDisplay.getRealSize(size) + return size + } fun Context.getRealPathFromURI(uri: Uri): String? { var cursor: Cursor? = null @@ -47,213 +71,24 @@ fun Context.launchSettings() { val Context.config: Config get() = Config.newInstance(this) -fun Context.getFilesFrom(curPath: String, isPickImage: Boolean, isPickVideo: Boolean): ArrayList { - val projection = arrayOf(MediaStore.Images.Media._ID, - MediaStore.Images.Media.DISPLAY_NAME, - MediaStore.Images.Media.DATE_TAKEN, - MediaStore.Images.Media.DATE_MODIFIED, - MediaStore.Images.Media.DATA, - MediaStore.Images.Media.SIZE) - val uri = MediaStore.Files.getContentUri("external") - val selection = if (curPath.isEmpty()) null else "(${MediaStore.Images.Media.DATA} LIKE ? AND ${MediaStore.Images.Media.DATA} NOT LIKE ?)" - val selectionArgs = if (curPath.isEmpty()) null else arrayOf("$curPath/%", "$curPath/%/%") +fun Context.movePinnedDirectoriesToFront(dirs: ArrayList): ArrayList { + val foundFolders = ArrayList() + val pinnedFolders = config.pinnedFolders - return try { - val cur = contentResolver.query(uri, projection, selection, selectionArgs, getSortingForFolder(curPath)) - parseCursor(this, cur, isPickImage, isPickVideo, curPath) - } catch (e: Exception) { - ArrayList() + dirs.forEach { + if (pinnedFolders.contains(it.path)) + foundFolders.add(it) } + + dirs.removeAll(foundFolders) + dirs.addAll(0, foundFolders) + return dirs } -private fun parseCursor(context: Context, cur: Cursor, isPickImage: Boolean, isPickVideo: Boolean, curPath: String): ArrayList { - val curMedia = ArrayList() - val config = context.config - val filterMedia = config.filterMedia - val showHidden = config.shouldShowHidden - val includedFolders = config.includedFolders.map { "${it.trimEnd('/')}/" } - val excludedFolders = config.excludedFolders.map { "${it.trimEnd('/')}/" } - val noMediaFolders = context.getNoMediaFolders() - val isThirdPartyIntent = config.isThirdPartyIntent - - cur.use { - if (cur.moveToFirst()) { - do { - try { - val path = cur.getStringValue(MediaStore.Images.Media.DATA) - var size = cur.getLongValue(MediaStore.Images.Media.SIZE) - if (size == 0L) { - size = File(path).length() - } - - if (size <= 0L) { - continue - } - - var filename = cur.getStringValue(MediaStore.Images.Media.DISPLAY_NAME) ?: "" - if (filename.isEmpty()) - filename = path.getFilenameFromPath() - - val isImage = filename.isImageFast() - val isVideo = if (isImage) false else filename.isVideoFast() - val isGif = if (isImage || isVideo) false else filename.isGif() - - if (!isImage && !isVideo && !isGif) - continue - - if (isVideo && (isPickImage || filterMedia and VIDEOS == 0)) - continue - - if (isImage && (isPickVideo || filterMedia and IMAGES == 0)) - continue - - if (isGif && filterMedia and GIFS == 0) - continue - - if (!showHidden && filename.startsWith('.')) - continue - - var isExcluded = false - excludedFolders.forEach { - if (path.startsWith(it)) { - isExcluded = true - includedFolders.forEach { - if (path.startsWith(it)) { - isExcluded = false - } - } - } - } - - if (!isExcluded && !showHidden) { - noMediaFolders.forEach { - if (path.startsWith(it)) { - isExcluded = true - } - } - } - - if (!isExcluded && !showHidden && path.contains("/.")) { - isExcluded = true - } - - if (!isExcluded || isThirdPartyIntent) { - val dateTaken = cur.getLongValue(MediaStore.Images.Media.DATE_TAKEN) - val dateModified = cur.getIntValue(MediaStore.Images.Media.DATE_MODIFIED) * 1000L - - val medium = Medium(filename, path, isVideo, dateModified, dateTaken, size) - curMedia.add(medium) - } - } catch (e: Exception) { - continue - } - } while (cur.moveToNext()) - } - } - - config.includedFolders.filter { it.isNotEmpty() && (curPath.isEmpty() || it == curPath) }.forEach { - getMediaInFolder(it, curMedia, isPickImage, isPickVideo, filterMedia) - } - - if (isThirdPartyIntent && curPath.isNotEmpty() && curMedia.isEmpty()) { - getMediaInFolder(curPath, curMedia, isPickImage, isPickVideo, filterMedia) - } - - Medium.sorting = config.getFileSorting(curPath) - curMedia.sort() - - return curMedia -} - -private fun getMediaInFolder(folder: String, curMedia: ArrayList, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int) { - val files = File(folder).listFiles() ?: return - for (file in files) { - val size = file.length() - if (size <= 0L) { - continue - } - - val filename = file.name - val isImage = filename.isImageFast() - val isVideo = if (isImage) false else filename.isVideoFast() - val isGif = if (isImage || isVideo) false else filename.isGif() - - if (!isImage && !isVideo) - continue - - if (isVideo && (isPickImage || filterMedia and VIDEOS == 0)) - continue - - if (isImage && (isPickVideo || filterMedia and IMAGES == 0)) - continue - - if (isGif && filterMedia and GIFS == 0) - continue - - val dateTaken = file.lastModified() - val dateModified = file.lastModified() - - val medium = Medium(filename, file.absolutePath, isVideo, dateModified, dateTaken, size) - val isAlreadyAdded = curMedia.any { it.path == file.absolutePath } - if (!isAlreadyAdded) - curMedia.add(medium) - } -} - -fun Context.getSortingForFolder(path: String): String { - val sorting = config.getFileSorting(path) - val sortValue = when { - sorting and SORT_BY_NAME > 0 -> MediaStore.Images.Media.DISPLAY_NAME - sorting and SORT_BY_SIZE > 0 -> MediaStore.Images.Media.SIZE - sorting and SORT_BY_DATE_MODIFIED > 0 -> MediaStore.Images.Media.DATE_MODIFIED - else -> MediaStore.Images.Media.DATE_TAKEN - } - - return if (sorting and SORT_DESCENDING > 0) - "$sortValue DESC" - else - "$sortValue ASC" -} - -fun Context.getNoMediaFolders(): ArrayList { - val folders = ArrayList() - val noMediaCondition = "${MediaStore.Files.FileColumns.MEDIA_TYPE} = ${MediaStore.Files.FileColumns.MEDIA_TYPE_NONE}" - - val uri = MediaStore.Files.getContentUri("external") - val columns = arrayOf(MediaStore.Files.FileColumns.DATA) - val where = "$noMediaCondition AND ${MediaStore.Files.FileColumns.TITLE} LIKE ?" - val args = arrayOf("%$NOMEDIA%") - var cursor: Cursor? = null - - try { - cursor = contentResolver.query(uri, columns, where, args, null) - if (cursor?.moveToFirst() == true) { - do { - val path = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA)) ?: continue - val noMediaFile = File(path) - if (noMediaFile.exists()) - folders.add("${noMediaFile.parent}/") - } while (cursor.moveToNext()) - } - } finally { - cursor?.close() - } - - return folders -} - -fun Context.getLastMediaModified(): Int { - val max = "max" - val uri = MediaStore.Files.getContentUri("external") - val projection = arrayOf(MediaStore.Images.Media._ID, "MAX(${MediaStore.Images.Media.DATE_MODIFIED}) AS $max") - var cursor: Cursor? = null - try { - cursor = contentResolver.query(uri, projection, null, null, null) - if (cursor?.moveToFirst() == true) { - return cursor.getIntValue(max) - } - } finally { - cursor?.close() - } - return 0 +@Suppress("UNCHECKED_CAST") +fun Context.getSortedDirectories(source: ArrayList): ArrayList { + Directory.sorting = config.directorySorting + val dirs = source.clone() as ArrayList + dirs.sort() + return movePinnedDirectoriesToFront(dirs) } 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 8a1f24ffc..108cf9bc9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt @@ -33,7 +33,6 @@ import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.extensions.getFileSignature import com.simplemobiletools.gallery.extensions.getRealPathFromURI import com.simplemobiletools.gallery.extensions.portrait -import com.simplemobiletools.gallery.helpers.GlideDecoder import com.simplemobiletools.gallery.helpers.GlideRotateTransformation import com.simplemobiletools.gallery.helpers.MEDIUM import com.simplemobiletools.gallery.models.Medium @@ -200,8 +199,7 @@ class PhotoFragment : ViewPagerFragment() { private fun addZoomableView() { if ((medium.isImage()) && isFragmentVisible && view.subsampling_view.visibility == View.GONE) { view.subsampling_view.apply { - setBitmapDecoderClass(GlideDecoder::class.java) - setMaxTileSize(10000) + //setBitmapDecoderClass(GlideDecoder::class.java) // causing random crashes on Android 7+ maxScale = 10f beVisible() setImage(ImageSource.uri(medium.path)) @@ -221,7 +219,7 @@ class PhotoFragment : ViewPagerFragment() { override fun onPreviewReleased() { } - override fun onImageLoadError(e: Exception?) { + override fun onImageLoadError(e: Exception) { background = ColorDrawable(Color.TRANSPARENT) beGone() } @@ -255,11 +253,6 @@ class PhotoFragment : ViewPagerFragment() { } } - fun refreshBitmap() { - view.subsampling_view.beGone() - loadBitmap() - } - fun rotateImageViewBy(degrees: Float) { view.subsampling_view.beGone() loadBitmap(degrees) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt index a2762d99a..1e945a15e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt @@ -20,10 +20,7 @@ import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.extensions.updateTextColors import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.activities.ViewPagerActivity -import com.simplemobiletools.gallery.extensions.audioManager -import com.simplemobiletools.gallery.extensions.config -import com.simplemobiletools.gallery.extensions.getNavBarHeight -import com.simplemobiletools.gallery.extensions.hasNavBar +import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.helpers.MEDIUM import com.simplemobiletools.gallery.models.Medium import kotlinx.android.synthetic.main.pager_video_item.view.* @@ -168,7 +165,9 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee mLastTouchY = event.y } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - if (System.currentTimeMillis() - mTouchDownTime < CLICK_MAX_DURATION) { + val diffX = Math.abs(event.x - mTouchDownX) + val diffY = Math.abs(event.y - mTouchDownY) + if (System.currentTimeMillis() - mTouchDownTime < CLICK_MAX_DURATION && diffX < 20 && diffY < 20) { mView.video_holder.performClick() } } @@ -204,7 +203,9 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee mLastTouchY = event.y } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - if (System.currentTimeMillis() - mTouchDownTime < CLICK_MAX_DURATION) { + val diffX = Math.abs(event.x - mTouchDownX) + val diffY = Math.abs(event.y - mTouchDownY) + if (System.currentTimeMillis() - mTouchDownTime < CLICK_MAX_DURATION && diffX < 20 && diffY < 20) { mView.video_holder.performClick() } mTouchDownBrightness = mTempBrightness @@ -273,6 +274,7 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee bottom += height } else { right += height + bottom += context.navigationBarHeight } mTimeHolder!!.setPadding(left, top, right, bottom) } 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 f17e9eed1..26f679028 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Config.kt @@ -28,17 +28,17 @@ class Config(context: Context) : BaseConfig(context) { if (path.isEmpty()) { fileSorting = value } else { - prefs.edit().putInt(SORT_FOLDER_PREFIX + path, value).apply() + prefs.edit().putInt(SORT_FOLDER_PREFIX + path.toLowerCase(), value).apply() } } - fun getFileSorting(path: String) = prefs.getInt(SORT_FOLDER_PREFIX + path, fileSorting) + fun getFileSorting(path: String) = prefs.getInt(SORT_FOLDER_PREFIX + path.toLowerCase(), fileSorting) fun removeFileSorting(path: String) { - prefs.edit().remove(SORT_FOLDER_PREFIX + path).apply() + prefs.edit().remove(SORT_FOLDER_PREFIX + path.toLowerCase()).apply() } - fun hasCustomSorting(path: String) = prefs.contains(SORT_FOLDER_PREFIX + path) + fun hasCustomSorting(path: String) = prefs.contains(SORT_FOLDER_PREFIX + path.toLowerCase()) var wasHideFolderTooltipShown: Boolean get() = prefs.getBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, false) @@ -280,4 +280,12 @@ class Config(context: Context) : BaseConfig(context) { var tempFolderPath: String get() = prefs.getString(TEMP_FOLDER_PATH, "") set(tempFolderPath) = prefs.edit().putString(TEMP_FOLDER_PATH, tempFolderPath).apply() + + var viewTypeFolders: Int + get() = prefs.getInt(VIEW_TYPE_FOLDERS, VIEW_TYPE_GRID) + set(viewTypeFolders) = prefs.edit().putInt(VIEW_TYPE_FOLDERS, viewTypeFolders).apply() + + var viewTypeFiles: Int + get() = prefs.getInt(VIEW_TYPE_FILES, VIEW_TYPE_GRID) + set(viewTypeFiles) = prefs.edit().putInt(VIEW_TYPE_FILES, viewTypeFiles).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 705c91d76..d875a0f18 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Constants.kt @@ -37,6 +37,8 @@ val REPLACE_SHARE_WITH_ROTATE = "replace_share_with_rotate" val DELETE_EMPTY_FOLDERS = "delete_empty_folders" val ALLOW_VIDEO_GESTURES = "allow_video_gestures" val TEMP_FOLDER_PATH = "temp_folder_path" +val VIEW_TYPE_FOLDERS = "view_type_folders" +val VIEW_TYPE_FILES = "view_type_files" // slideshow val SLIDESHOW_INTERVAL = "slideshow_interval" @@ -60,6 +62,8 @@ val GET_ANY_INTENT = "get_any_intent" val SET_WALLPAPER_INTENT = "set_wallpaper_intent" val DIRECTORIES = "directories2" val IS_VIEW_INTENT = "is_view_intent" +val IS_FROM_GALLERY = "is_from_gallery" +val PICKED_PATHS = "picked_paths" val REQUEST_EDIT_IMAGE = 1 val REQUEST_SET_AS = 2 @@ -77,3 +81,7 @@ val ORIENT_LANDSCAPE_RIGHT = 2 val IMAGES = 1 val VIDEOS = 2 val GIFS = 4 + +// view types +val VIEW_TYPE_GRID = 1 +val VIEW_TYPE_LIST = 2 diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideDecoder.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideDecoder.kt index fe7c7f9e5..fbddb788b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideDecoder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideDecoder.kt @@ -8,7 +8,6 @@ import android.graphics.drawable.Drawable import android.media.ExifInterface import android.net.Uri import com.bumptech.glide.Glide -import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.target.Target @@ -26,11 +25,13 @@ class GlideDecoder : ImageDecoder { val options = RequestOptions() .signature(uri.path.getFileSignature()) - .format(DecodeFormat.PREFER_ARGB_8888) .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .transform(GlideRotateTransformation(context, getRotationDegrees(orientation))) .override(targetWidth, targetHeight) + val degrees = getRotationDegrees(orientation) + if (degrees != 0f) + options.transform(GlideRotateTransformation(context, getRotationDegrees(orientation))) + val drawable = Glide.with(context) .load(uri) .apply(options) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/MediaFetcher.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/MediaFetcher.kt new file mode 100644 index 000000000..ac3178dc7 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/MediaFetcher.kt @@ -0,0 +1,321 @@ +package com.simplemobiletools.gallery.helpers + +import android.content.Context +import android.database.Cursor +import android.provider.MediaStore +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED +import com.simplemobiletools.commons.helpers.SORT_BY_NAME +import com.simplemobiletools.commons.helpers.SORT_BY_SIZE +import com.simplemobiletools.commons.helpers.SORT_DESCENDING +import com.simplemobiletools.gallery.extensions.config +import com.simplemobiletools.gallery.extensions.containsNoMedia +import com.simplemobiletools.gallery.models.Medium +import java.io.File +import java.util.LinkedHashMap +import kotlin.collections.ArrayList +import kotlin.collections.component1 +import kotlin.collections.component2 + +class MediaFetcher(val context: Context) { + var shouldStop = false + + fun getMediaByDirectories(isPickVideo: Boolean, isPickImage: Boolean): HashMap> { + val media = getFilesFrom("", isPickImage, isPickVideo) + val excludedPaths = context.config.excludedFolders + val includedPaths = context.config.includedFolders + val showHidden = context.config.shouldShowHidden + val directories = groupDirectories(media) + + val removePaths = ArrayList() + for ((path, curMedia) in directories) { + // make sure the path has uppercase letters wherever appropriate + val groupPath = File(curMedia.first().path).parent + if (!File(groupPath).exists() || !shouldFolderBeVisible(groupPath, excludedPaths, includedPaths, showHidden)) { + removePaths.add(groupPath.toLowerCase()) + } + } + + removePaths.forEach { + directories.remove(it) + } + + return directories + } + + fun getFilesFrom(curPath: String, isPickImage: Boolean, isPickVideo: Boolean): ArrayList { + val projection = arrayOf(MediaStore.Images.Media._ID, + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.DATE_TAKEN, + MediaStore.Images.Media.DATE_MODIFIED, + MediaStore.Images.Media.DATA, + MediaStore.Images.Media.SIZE) + val uri = MediaStore.Files.getContentUri("external") + val selection = getSelectionQuery(curPath) + val selectionArgs = getSelectionArgsQuery(curPath) + + return try { + val cur = context.contentResolver.query(uri, projection, selection, selectionArgs, getSortingForFolder(curPath)) + parseCursor(context, cur, isPickImage, isPickVideo, curPath) + } catch (e: Exception) { + ArrayList() + } + } + + private fun getSelectionQuery(path: String): String { + val dataQuery = "${MediaStore.Images.Media.DATA} LIKE ?" + return if (path.isEmpty()) { + var query = "($dataQuery)" + if (context.hasExternalSDCard()) { + query += " OR ($dataQuery)" + } + query + } else { + "($dataQuery AND ${MediaStore.Images.Media.DATA} NOT LIKE ?)" + } + } + + private fun getSelectionArgsQuery(path: String): Array { + return if (path.isEmpty()) { + if (context.hasExternalSDCard()) arrayOf("${context.internalStoragePath}/%", "${context.sdCardPath}/%") else arrayOf("${context.internalStoragePath}/%") + } else { + arrayOf("$path/%", "$path/%/%") + } + } + + private fun parseCursor(context: Context, cur: Cursor, isPickImage: Boolean, isPickVideo: Boolean, curPath: String): ArrayList { + val curMedia = ArrayList() + val config = context.config + val filterMedia = config.filterMedia + val showHidden = config.shouldShowHidden + val includedFolders = config.includedFolders.map { "${it.trimEnd('/')}/" } + val excludedFolders = config.excludedFolders.map { "${it.trimEnd('/')}/" } + val noMediaFolders = getNoMediaFolders() + val isThirdPartyIntent = config.isThirdPartyIntent + + cur.use { + if (cur.moveToFirst()) { + do { + try { + if (shouldStop) + break + + val path = cur.getStringValue(MediaStore.Images.Media.DATA) + var filename = cur.getStringValue(MediaStore.Images.Media.DISPLAY_NAME) ?: "" + if (filename.isEmpty()) + filename = path.getFilenameFromPath() + + val isImage = filename.isImageFast() + val isVideo = if (isImage) false else filename.isVideoFast() + val isGif = if (isImage || isVideo) false else filename.isGif() + + if (!isImage && !isVideo && !isGif) + continue + + if (isVideo && (isPickImage || filterMedia and VIDEOS == 0)) + continue + + if (isImage && (isPickVideo || filterMedia and IMAGES == 0)) + continue + + if (isGif && filterMedia and GIFS == 0) + continue + + if (!showHidden && filename.startsWith('.')) + continue + + var size = cur.getLongValue(MediaStore.Images.Media.SIZE) + val file = File(path) + if (size == 0L) { + size = file.length() + } + + if (size <= 0L) + continue + + var isExcluded = false + excludedFolders.forEach { + if (path.startsWith(it)) { + isExcluded = true + includedFolders.forEach { + if (path.startsWith(it)) { + isExcluded = false + } + } + } + } + + if (!isExcluded && !showHidden) { + noMediaFolders.forEach { + if (path.startsWith(it)) { + isExcluded = true + } + } + } + + if (!isExcluded && !showHidden && path.contains("/.")) { + isExcluded = true + } + + if (!isExcluded || isThirdPartyIntent) { + if (!file.exists()) + continue + + val dateTaken = cur.getLongValue(MediaStore.Images.Media.DATE_TAKEN) + val dateModified = cur.getIntValue(MediaStore.Images.Media.DATE_MODIFIED) * 1000L + + val medium = Medium(filename, path, isVideo, dateModified, dateTaken, size) + curMedia.add(medium) + } + } catch (e: Exception) { + continue + } + } while (cur.moveToNext()) + } + } + + config.includedFolders.filter { it.isNotEmpty() && (curPath.isEmpty() || it == curPath) }.forEach { + getMediaInFolder(it, curMedia, isPickImage, isPickVideo, filterMedia) + } + + if (isThirdPartyIntent && curPath.isNotEmpty() && curMedia.isEmpty()) { + getMediaInFolder(curPath, curMedia, isPickImage, isPickVideo, filterMedia) + } + + Medium.sorting = config.getFileSorting(curPath) + curMedia.sort() + + return curMedia + } + + private fun groupDirectories(media: ArrayList): HashMap> { + val directories = LinkedHashMap>() + for (medium in media) { + if (shouldStop) + break + + val parentDir = File(medium.path).parent?.toLowerCase() ?: continue + if (directories.containsKey(parentDir)) { + directories[parentDir]!!.add(medium) + } else { + directories.put(parentDir, arrayListOf(medium)) + } + } + return directories + } + + private fun shouldFolderBeVisible(path: String, excludedPaths: MutableSet, includedPaths: MutableSet, showHidden: Boolean): Boolean { + val file = File(path) + return if (includedPaths.contains(path)) { + true + } else if (isThisOrParentExcluded(path, excludedPaths, includedPaths)) { + false + } else if (!showHidden && file.isDirectory && file.canonicalFile == file.absoluteFile) { + var containsNoMediaOrDot = file.containsNoMedia() || path.contains("/.") + if (!containsNoMediaOrDot) { + containsNoMediaOrDot = checkParentHasNoMedia(file.parentFile) + } + !containsNoMediaOrDot + } else { + true + } + } + + private fun checkParentHasNoMedia(file: File): Boolean { + var curFile = file + while (true) { + if (curFile.containsNoMedia()) { + return true + } + curFile = curFile.parentFile + if (curFile.absolutePath == "/") + break + } + return false + } + + private fun isThisOrParentExcluded(path: String, excludedPaths: MutableSet, includedPaths: MutableSet) = + includedPaths.none { path.startsWith(it) } && excludedPaths.any { path.startsWith(it) } + + + private fun getMediaInFolder(folder: String, curMedia: ArrayList, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int) { + val files = File(folder).listFiles() ?: return + for (file in files) { + if (shouldStop) + break + + val filename = file.name + val isImage = filename.isImageFast() + val isVideo = if (isImage) false else filename.isVideoFast() + val isGif = if (isImage || isVideo) false else filename.isGif() + + if (!isImage && !isVideo) + continue + + if (isVideo && (isPickImage || filterMedia and VIDEOS == 0)) + continue + + if (isImage && (isPickVideo || filterMedia and IMAGES == 0)) + continue + + if (isGif && filterMedia and GIFS == 0) + continue + + val size = file.length() + if (size <= 0L) + continue + + val dateTaken = file.lastModified() + val dateModified = file.lastModified() + + val medium = Medium(filename, file.absolutePath, isVideo, dateModified, dateTaken, size) + val isAlreadyAdded = curMedia.any { it.path == file.absolutePath } + if (!isAlreadyAdded) { + curMedia.add(medium) + context.scanPath(file.absolutePath) {} + } + } + } + + private fun getSortingForFolder(path: String): String { + val sorting = context.config.getFileSorting(path) + val sortValue = when { + sorting and SORT_BY_NAME > 0 -> MediaStore.Images.Media.DISPLAY_NAME + sorting and SORT_BY_SIZE > 0 -> MediaStore.Images.Media.SIZE + sorting and SORT_BY_DATE_MODIFIED > 0 -> MediaStore.Images.Media.DATE_MODIFIED + else -> MediaStore.Images.Media.DATE_TAKEN + } + + return if (sorting and SORT_DESCENDING > 0) + "$sortValue DESC" + else + "$sortValue ASC" + } + + private fun getNoMediaFolders(): ArrayList { + val folders = ArrayList() + val noMediaCondition = "${MediaStore.Files.FileColumns.MEDIA_TYPE} = ${MediaStore.Files.FileColumns.MEDIA_TYPE_NONE}" + + val uri = MediaStore.Files.getContentUri("external") + val columns = arrayOf(MediaStore.Files.FileColumns.DATA) + val where = "$noMediaCondition AND ${MediaStore.Files.FileColumns.TITLE} LIKE ?" + val args = arrayOf("%$NOMEDIA%") + var cursor: Cursor? = null + + try { + cursor = context.contentResolver.query(uri, columns, where, args, null) + if (cursor?.moveToFirst() == true) { + do { + val path = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA)) ?: continue + val noMediaFile = File(path) + if (noMediaFile.exists()) + folders.add("${noMediaFile.parent}/") + } while (cursor.moveToNext()) + } + } finally { + cursor?.close() + } + + return folders + } +} 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 6293eba76..7271dc2e8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Directory.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Directory.kt @@ -16,29 +16,23 @@ data class Directory(val path: String, val tmb: String, val name: String, var me override fun compareTo(other: Directory): Int { var result: Int - if (sorting and SORT_BY_NAME != 0) { - result = AlphanumComparator().compare(name.toLowerCase(), other.name.toLowerCase()) - } else if (sorting and SORT_BY_SIZE != 0) { - result = if (size == other.size) - 0 - else if (size > other.size) - 1 - else - -1 - } else if (sorting and SORT_BY_DATE_MODIFIED != 0) { - result = if (modified == other.modified) - 0 - else if (modified > other.modified) - 1 - else - -1 - } else { - result = if (taken == other.taken) - 0 - else if (taken > other.taken) - 1 - else - -1 + when { + sorting and SORT_BY_NAME != 0 -> result = AlphanumericComparator().compare(name.toLowerCase(), other.name.toLowerCase()) + sorting and SORT_BY_SIZE != 0 -> result = when { + size == other.size -> 0 + size > other.size -> 1 + else -> -1 + } + sorting and SORT_BY_DATE_MODIFIED != 0 -> result = when { + modified == other.modified -> 0 + modified > other.modified -> 1 + else -> -1 + } + else -> result = when { + taken == other.taken -> 0 + taken > other.taken -> 1 + else -> -1 + } } if (sorting and SORT_DESCENDING != 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 1663c60b1..fadf715ea 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt @@ -25,29 +25,23 @@ data class Medium(var name: String, var path: String, val video: Boolean, val mo override fun compareTo(other: Medium): Int { var result: Int - if (sorting and SORT_BY_NAME != 0) { - result = AlphanumComparator().compare(name.toLowerCase(), other.name.toLowerCase()) - } else if (sorting and SORT_BY_SIZE != 0) { - result = if (size == other.size) - 0 - else if (size > other.size) - 1 - else - -1 - } else if (sorting and SORT_BY_DATE_MODIFIED != 0) { - result = if (modified == other.modified) - 0 - else if (modified > other.modified) - 1 - else - -1 - } else { - result = if (taken == other.taken) - 0 - else if (taken > other.taken) - 1 - else - -1 + when { + sorting and SORT_BY_NAME != 0 -> result = AlphanumericComparator().compare(name.toLowerCase(), other.name.toLowerCase()) + sorting and SORT_BY_SIZE != 0 -> result = when { + size == other.size -> 0 + size > other.size -> 1 + else -> -1 + } + sorting and SORT_BY_DATE_MODIFIED != 0 -> result = when { + modified == other.modified -> 0 + modified > other.modified -> 1 + else -> -1 + } + else -> result = when { + taken == other.taken -> 0 + taken > other.taken -> 1 + else -> -1 + } } if (sorting and SORT_DESCENDING != 0) { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/views/MySquareImageView.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/views/MySquareImageView.kt index 3e0c79df1..f254d4183 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/views/MySquareImageView.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/views/MySquareImageView.kt @@ -14,8 +14,7 @@ class MySquareImageView : ImageView { constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - val spec = if (isVerticalScrolling) measuredWidth else measuredHeight - setMeasuredDimension(spec, spec) + val spec = if (isVerticalScrolling) widthMeasureSpec else heightMeasureSpec + super.onMeasure(spec, spec) } } diff --git a/app/src/main/res/drawable-hdpi/img_play_outline_empty.png b/app/src/main/res/drawable-hdpi/img_play_outline_empty.png new file mode 100644 index 000000000..6212066d5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/img_play_outline_empty.png differ diff --git a/app/src/main/res/drawable-xhdpi/img_play_outline_empty.png b/app/src/main/res/drawable-xhdpi/img_play_outline_empty.png new file mode 100644 index 000000000..2753608b6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/img_play_outline_empty.png differ diff --git a/app/src/main/res/drawable-xxhdpi/img_play_outline_empty.png b/app/src/main/res/drawable-xxhdpi/img_play_outline_empty.png new file mode 100644 index 000000000..95a4f7d28 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/img_play_outline_empty.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/img_play_outline_empty.png b/app/src/main/res/drawable-xxxhdpi/img_play_outline_empty.png new file mode 100644 index 000000000..df8727abd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/img_play_outline_empty.png differ diff --git a/app/src/main/res/layout/dialog_directory_picker.xml b/app/src/main/res/layout/dialog_directory_picker.xml index 0a586affb..bc18bfb19 100644 --- a/app/src/main/res/layout/dialog_directory_picker.xml +++ b/app/src/main/res/layout/dialog_directory_picker.xml @@ -10,7 +10,7 @@ diff --git a/app/src/main/res/layout/directory_item.xml b/app/src/main/res/layout/directory_item_grid.xml similarity index 95% rename from app/src/main/res/layout/directory_item.xml rename to app/src/main/res/layout/directory_item_grid.xml index f6d296e07..6903b7f46 100644 --- a/app/src/main/res/layout/directory_item.xml +++ b/app/src/main/res/layout/directory_item_grid.xml @@ -39,7 +39,9 @@ android:id="@+id/dir_shadow_holder" android:layout_width="match_parent" android:layout_height="@dimen/tmb_shadow_height" + android:layout_alignLeft="@+id/dir_bottom_holder" android:layout_alignParentBottom="true" + android:layout_alignRight="@+id/dir_bottom_holder" android:background="@drawable/gradient_background"/> + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/photo_video_item.xml b/app/src/main/res/layout/photo_video_item_grid.xml similarity index 100% rename from app/src/main/res/layout/photo_video_item.xml rename to app/src/main/res/layout/photo_video_item_grid.xml diff --git a/app/src/main/res/layout/photo_video_item_list.xml b/app/src/main/res/layout/photo_video_item_list.xml new file mode 100644 index 000000000..dd1e17da8 --- /dev/null +++ b/app/src/main/res/layout/photo_video_item_list.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/cab_media.xml b/app/src/main/res/menu/cab_media.xml index 4b47e6025..fa787d520 100644 --- a/app/src/main/res/menu/cab_media.xml +++ b/app/src/main/res/menu/cab_media.xml @@ -1,6 +1,11 @@ + + + The slideshow ended No media for the slideshow have been found + + Change view type + Grid + List + Show hidden media Play videos automatically diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ee99698ff..af7703ab4 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -102,6 +102,11 @@ The slideshow ended No media for the slideshow have been found + + Change view type + Grid + List + Zobrazit skryté média Automaticky přehrávat videa diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6815e2f67..96580f927 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -19,7 +19,7 @@ Keine Kamera-App gefunden Kacheln verkleinern Kacheln vergrößern - Cover-Bild ändern + Coverbild ändern Auswählen Standard Festlegen als @@ -27,12 +27,12 @@ Helligkeit - Filter media - Images + Medienfilter + Bilder Videos GIFs - No media files have been found with the selected filters. - Change filters + Keine Medien für die ausgewählten Filter gefunden + Filter ändern Diese Funktion versteckt ausgewählte Ordner (auch für andere Apps), indem dort im Dateisystem eine \'.nomedia\'-Datei abgelegt wird. Dadurch werden auch deren Unterordner versteckt. Solche Ordner werden nur gezeigt, wenn die Einstellung \'Versteckte Ordner zeigen\' aktiv ist (auch andere Apps bieten üblicherweise eine solche Option). Fortfahren? @@ -94,14 +94,19 @@ Intervall (Sekunden): Bilder verwenden Videos verwenden - GIF verwenden + GIFs verwenden Zufällige Reihenfolge Übergänge animieren Rückwärts abspielen - Loop slideshow + Endlos abspielen Diashow beendet Keine Medien für Diashow gefunden + + Change view type + Grid + List + Versteckte Ordner zeigen Videos automatisch abspielen @@ -117,8 +122,8 @@ Schwarzer Hintergrund im Vollbild Kacheln horizontal scrollen Systemleisten ausblenden im Vollbild - Delete empty folders after deleting their content - Allow controlling video volume and brightness with vertical gestures + Nach Löschen leere Ordner löschen + Gesten für Videolautstärke/Helligkeit Teilen/Drehen im Vollbild-Menü vertauschen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 6aad7a9c4..5b5350d6c 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -102,6 +102,11 @@ The slideshow ended No media for the slideshow have been found + + Change view type + Grid + List + Mostrar carpetas ocultas Reproducir vídeos automáticamente diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index ec733b59c..ca60589f2 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -102,6 +102,11 @@ Diaporama terminé Aucun média trouvé pour le diaporama + + Change view type + Grid + List + Afficher les dossiers cachés Lecture automatique des vidéos @@ -125,13 +130,13 @@ Un album pour visionner photos et vidéos sans publicité. - Un simple outil pour visionner les photos et les vidéos. Elles peuvent être triées par dates, tailles, noms dans les deux sens (alphabétique comme désalphabétique), il est possible de zoomer sur les photos. Les fichiers sont affichés sur de multiple colonnes en fonction de la taille de l\'écran, vous pouvez changer le nombre de colonnes par pincement. Elles peuvent être renommées, partagées, supprimées, copiées et déplacées. Les images peuvent en plus être tournées, rognées ou être définies comme fond d\'écran directement depuis l\'application. La galerie est aussi offerte pour l\'utiliser comme une tierce partie pour de la prévisualisation des images/vidéos, joindre aux clients mail etc. C\'est parfait pour un usage au quotidien. + Un simple outil pour visionner les photos et les vidéos. Elles peuvent être triées par dates, tailles, noms dans les deux sens (alphabétique comme désalphabétique), il est possible de zoomer sur les photos. Les fichiers sont affichés sur de multiple colonnes en fonction de la taille de l\'écran, vous pouvez changer le nombre de colonnes par pincement. Elles peuvent être renommées, partagées, supprimées, copiées et déplacées. Les images peuvent en plus être tournées, rognées ou être définies comme fond d\'écran directement depuis l\'application. + + La galerie est aussi offerte pour l\'utiliser comme une tierce partie pour de la prévisualisation des images/vidéos, joindre aux clients mail etc. C\'est parfait pour un usage au quotidien. L\'application ne contient ni de publicité ni d\'autorisation inutile. Elle est totalement OpenSource et est aussi fournie avec un thème sombre. - Cette application est juste l\'une des applications d\'une plus grande suite. - - Vous pouvez trouver les autres sur http://www.simplemobiletools.com + Cette application est juste l\'une des applications d\'une plus grande suite. Vous pouvez trouver les autres sur http://www.simplemobiletools.com + Change view type + Grid + List + Show hidden media Play videos automatically diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b723068ba..e77d073ef 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -27,12 +27,12 @@ Luminosità - Filter media - Images - Videos - GIFs - No media files have been found with the selected filters. - Change filters + Filtra i media + Immagini + Video + GIF + Nessun file trovato per il filtro selezionato. + Cambia filtro Questa funzione nasconde la cartella aggiungendo un file \'.nomedia\' all\'interno, nasconderà anche tutte le sottocartelle. Puoi vederle attivando l\'opzione \'Mostra cartelle nascoste\' nelle impostazioni. Continuare? @@ -98,10 +98,15 @@ Ordine sparso Usa animazioni a dissolvenza Scorri al contrario - Loop slideshow + Ripeti presentazione La presentazione è terminata Nessun media trovato per la presentazione + + Change view type + Grid + List + Mostra cartelle nascoste Riproduci video automaticamente @@ -117,8 +122,8 @@ Sfondo scuro a schermo intero Scorri miniature orizzontalmente Nascondi UI di sistema con media a schermo intero - Delete empty folders after deleting their content - Allow controlling video volume and brightness with vertical gestures + Elimina cartelle vuote dopo averne eliminato il contenuto + Gestisci il volume e la luminosità dei video con gesti verticali Sostituisci Condividi con Ruota a schermo intero diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index f0fbd9f19..ef90a0d11 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -4,122 +4,127 @@ ギャラリー 編集 カメラを開く - …で開く + 別のアプリで開く 有効なアプリが見つかりません (非表示) - Pin folder - Unpin folder - Show all folders content - All media - Switch to folder view - Other folder - Show on map - Unknown location - No application with maps has been found - No Camera app has been found - Increase column count - Reduce column count - Change cover image - Select photo - Use default - Set as - Volume - Brightness + フォルダーをピン留めする + フォルダーのピン留めを外す + 全てを表示 + すべてのフォルダー + フォルダーを選択する + その他のフォルダー + 地図で表示 + 位置情報がありません + 地図アプリが見つかりません + カメラアプリが見つかりません + 列数を増やす + 列数を減らす + カバー画像を変更 + 写真を選択 + デフォルトに戻す + 他で使う + 音量 + 明るさ - Filter media - Images - Videos - GIFs - No media files have been found with the selected filters. - Change filters + 表示メディア種 + 画像 + ビデオ + GIF + 絞り込み条件に該当するメディアがありません。 + 絞り込み条件を変更 - This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden folders\' option in Settings. Continue? - Exclude - Excluded folders - Manage excluded folders - This will exclude the selection together with its subfolders from Simple Gallery only. You can manage excluded folders in Settings. - Exclude a parent instead? - Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\\n\\nIf you want to hide them from other apps too, use the Hide function. - Remove all - Remove all folders from the list of excluded? This will not delete the folders. + 対象のフォルダーに「.nomedia」というファイルを作成し、フォルダーを非表示にします。そのフォルダー以下のすべてのサブフォルダーも、同様に非表示となります。非表示となったフォルダーを見るには、「設定」の中にある「非表示のフォルダーを表示」オプションを切り替えてください。このフォルダーを非表示にしますか? + 除外する + 除外フォルダー + 除外フォルダーを管理 + 選択したフォルダーとそのサブフォルダーを、Simple Galleyの一覧から除外します。除外したフォルダーは「設定」で管理できます。 + 親フォルダーを選択して除外することもできます。 + フォルダーを除外すると、サブフォルダーも含めSimple Galleyの一覧から除外します。他のアプリでは引き続き表示されます。\\n\\n他のアプリでも非表示にしたい場合は、「非表示」機能を使用してください。 + すべて解除 + 除外するフォルダーの登録をすべて解除しますか? フォルダー自体は削除されません。 - Included folders - Manage included folders - Add folder - If you have some folders which contain media, but were not recognized by the app, you can add them manually here. + 追加フォルダー + 追加フォルダーを管理 + フォルダーを追加 + メディアを含んでいるフォルダーがアプリから認識されていない場合は、手動で追加できます。 - Resize - Resize selection and save - Width - Height - Keep aspect ratio - Please enter a valid resolution + リサイズ + 選択領域をリサイズして保存 + + 高さ + 縦横比を固定 + 解像度を正しく入力してください エディター 保存 回転 - Path + パス 無効な画像パス 画像の編集に失敗しました 画像を編集: 画像エディターが見つかりません ファイルの場所が不明です 元のファイルを上書きできません - Rotate left - Rotate right - Rotate by 180º - Flip - Flip horizontally - Flip vertically - Edit with + 左に回転 + 右に回転 + 180º回転 + 反転 + 水平方向に反転 + 垂直方向に反転 + 他のアプリで編集 シンプル壁紙 - 壁紙として設定 - 壁紙としての設定に失敗しました - 壁紙として設定: + 壁紙に設定 + 壁紙の設定に失敗しました + 壁紙に設定: 対応できるアプリが見つかりません - 壁紙の設定… + 壁紙に設定中… 壁紙を正常に設定しました - Portrait aspect ratio - Landscape aspect ratio + 縦向きの縦横比 + 横向きの縦横比 - Slideshow - Interval (seconds): - Include photos - Include videos - Include GIFs - Random order - Use fade animations - Move backwards - Loop slideshow - The slideshow ended - No media for the slideshow have been found + スライドショー + 間隔 (秒): + 写真を含める + ビデオを含める + GIFを含める + ランダムな順序 + フェードアニメーションを使用する + 逆方向に進む + スライドショーをリピート再生する + スライドショーが終了しました + スライドショーに表示するメディアがありません + + + Change view type + Grid + List 非表示フォルダーを表示 - 自動的にビデオを再生 + ビデオを自動再生する ファイル名の表示を切り替え - Loop videos - Animate GIFs at thumbnails - Max brightness when viewing media - Crop thumbnails into squares - Rotate fullscreen media by - System setting - Device rotation - Aspect ratio - Dark background at fullscreen media - Scroll thumbnails horizontally - Automatically hide system UI at fullscreen media - Delete empty folders after deleting their content - Allow controlling video volume and brightness with vertical gestures - Replace Share with Rotate at fullscreen menu + ビデオをリピート再生する + GIF画像のサムネイルをアニメーション表示する + メディア再生時に明るさを最大にする + サムネイルを正方形に切り取る + メディア再生時のフルスクリーン表示切り替え + システム設定に従う + 端末の向きに従う + メディアの縦横比に従う + 黒背景でフルスクリーン表示 + サムネイル画面を横方向にスクロール + フルスクリーン時にシステムUIを非表示にする + メディアの削除後にフォルダーが空になった場合、そのフォルダーを削除する + ビデオ再生中に、音量と明るさを縦方向のジェスチャーで変更する + フルスクリーンメニューの「共有」を「回転」に置き換える diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 7b9f6575f..1e4c90da8 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -57,7 +57,7 @@ Szerokość Wysokość Zachowaj proporcje - Wpisz poprawną rozdzielczość +    Podaj poprawną rozdzielczość Edycja @@ -98,10 +98,15 @@ Losowa kolejność Używaj płynnych przejść    Odwrotna kolejność - Loop slideshow +    Zapętlaj Pokaz slajdów zakończony Nie znalazłem multimediów do pokazu slajdów + +    Zmień typ widoku +    Siatka +    Lista + Pokazuj ukryte foldery Odtwarzaj filmy automatycznie diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 3c4b4032f..e549de037 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -102,6 +102,11 @@ The slideshow ended No media for the slideshow have been found + + Change view type + Grid + List + Mostrar pastas ocultas Reproduzir vídeos automaticamente diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 0ac84d7d6..5128dfaf4 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -31,7 +31,7 @@ Imagens Vídeos GIFs - Naõ foram encontrados ficheiros que cumpram os requisitos. + Não foram encontrados ficheiros que cumpram os requisitos. Alterar filtros @@ -98,10 +98,15 @@ Ordem aleatória Usar animações Mover para trás - Loop slideshow + Apresentação em ciclo Apresentação terminada Não foram encontrados ficheiros para a apresentação + + Change view type + Grid + List + Mostrar pastas ocultas Reproduzir vídeos automaticamente diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1576a4b44..f0ff4a8df 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -102,6 +102,11 @@ Слайдшоу завершилось Никаких медиафайлов для слайдшоу не было найдено. + + Change view type + Grid + List + Показать скрытые папки Воспроизводить видео автоматически diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 0d7fe9f86..89a8fe5bb 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -102,6 +102,11 @@ Prezentácia skončila Pre prezentáciu sa nenašli žiadne vhodné súbory + + Zmeniť typ zobrazenia + Mriežka + Zoznam + Zobraziť skryté médiá Spúšťať videá automaticky diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 6442a31fe..834755adf 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -23,16 +23,16 @@ Välj foto Använd standard Ange som - Volume - Brightness + Volym + Ljusstyrka - Filter media - Images - Videos - GIFs - No media files have been found with the selected filters. - Change filters + Filtrera media + Bilder + Videor + GIF-bilder + Inga mediefiler hittades med valda filter. + Ändra filter Denna funktion döljer mappen och alla dess undermappar genom att lägga till en \'.nomedia\'-fil i den. Du kan se dem genom att växla \'Visa dolda mappar\'-alternativet i Inställningar. Vill du fortsätta? @@ -49,7 +49,7 @@ Inkluderade mappar Hantera inkluderade mappar Lägg till mapp - Om du har vissa mappar som innehåller media men som inte känns igen av appen kan du lägga till dem manuellt här. + Om du har vissa mappar som innehåller media men som inte känns igen av appen, kan du lägga till dem manuellt här. Ändra storlek @@ -90,17 +90,22 @@ Liggande bildförhållande - Slideshow - Interval (seconds): - Include photos - Include videos - Include GIFs - Random order - Use fade animations - Move backwards - Loop slideshow - The slideshow ended - No media for the slideshow have been found + Bildspel + Intervall (sekunder): + Inkludera foton + Inkludera videor + Inkludera GIF-bilder + Spela upp i slumpmässig ordning + Använd toningsanimationer + Spela upp i omvänd ordning + Spela upp i en slinga + Bildspelet har avslutats + Ingen media hittades för bildspelet + + + Change view type + Grid + List Visa dolda mappar @@ -117,17 +122,17 @@ Mörk bakgrund när media visas i helskärmsläge Rulla horisontellt genom miniatyrer Dölj systemanvändargränssnittet automatiskt när media visas i helskärmsläge - Delete empty folders after deleting their content - Allow controlling video volume and brightness with vertical gestures - Replace Share with Rotate at fullscreen menu + Ta bort tomma mappar när deras innehåll tas bort + Tillåt styrning av videovolym och videoljusstyrka med vertikala gester + Ersätt Dela med Rotera i helskärmsmenyn Ett Galleri för att visa bilder och videos utan en massa reklam. - Ett enkelt verktyg för att visa bilder och vdeos. Objekten kan sorteras efter datum, storlek, namn både stigande och fallande, bilder kan zoomas in. Mediafiler visas i flera kolumner beroende av skärmens storlek, du kan ändra antalet kolumner genom en nyp-rörelse. De går att döpa om, dela, ta bort, kopiera, flytta. Bilder kan också beskäras, roteras och anges som bakgrundsbild direkt från appen. + Ett enkelt verktyg för att visa bilder och videos. Objekten kan sorteras efter datum, storlek, namn både stigande och fallande, bilder kan zoomas in. Mediafiler visas i flera kolumner beroende av skärmens storlek, du kan ändra antalet kolumner genom en nyp-rörelse. De går att döpa om, dela, ta bort, kopiera, flytta. Bilder kan också beskäras, roteras och anges som bakgrundsbild direkt från appen. - Galleriet kan också användas av tredjeparts för förhandsgranskning av bilder / videos, bifoga bilagor i e-postklienter etc. Den är perfekt för det dagliga användandet. + Galleriet kan också användas av tredjepartsappar för förhandsgranskning av bilder / videos, bifoga bilagor i e-postklienter etc. Den är perfekt för det dagliga användandet. Innehåller ingen reklam eller onödiga behörigheter. Det är helt och hållet opensource, innehåller anpassningsbara färger. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index ded134a6d..e98004c41 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -102,6 +102,11 @@ The slideshow ended No media for the slideshow have been found + + Change view type + Grid + List + Gizli klasörleri göster Videoları otomatik olarak oynat diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ccbd2c8cd..1b6a61f9e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -102,6 +102,11 @@ 幻灯片结束 未发现可用媒体 + + Change view type + Grid + List + 显示所有 自动播放 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a2b80e15e..6f7ec0dc1 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -102,6 +102,11 @@ 投影片結束 找不到投影片的媒體檔案 + + Change view type + Grid + List + 顯示隱藏的媒體檔案 自動播放影片 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index a67c01562..58e7e8c8e 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -3,9 +3,11 @@ 150dp 100dp 20dp + 22dp 26dp 150dp 24dp 50dp 150dp + 72dp diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index b880748b9..10ac92ba7 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,6 +2,10 @@ + + Added fingerprint to hidden item protection\n + Added a new List view type + Added a switch for disabling video gestures\n Added a switch for deleting empty folders after deleting content diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 13d30d4ba..ddc156702 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -102,6 +102,11 @@ The slideshow ended No media for the slideshow have been found + + Change view type + Grid + List + Show hidden media Play videos automatically