Merge branch 'master' into stringlate-fr-8830

This commit is contained in:
Tibor Kaputa 2019-02-10 22:09:36 +01:00 committed by GitHub
commit d374cd5afa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
227 changed files with 11109 additions and 4878 deletions

5
.gitignore vendored
View file

@ -1,4 +1,5 @@
*.iml *.iml
*.aab
.gradle .gradle
/local.properties /local.properties
/gradle.properties /gradle.properties
@ -6,5 +7,5 @@
.DS_Store .DS_Store
/build /build
/captures /captures
release.keystore keystore.jks
signing.properties keystore.properties

View file

@ -1,6 +1,226 @@
Changelog Changelog
========== ==========
Version 6.5.0 *(2019-02-07)*
----------------------------
* Allow rotating fullscreen images with gestures, if "Allow deep zooming images" option is enabled
* Zoom out videos and gifs after device rotation
Version 6.4.1 *(2019-01-29)*
----------------------------
* Fixed some crashes related to zoomable videos
* Disable the Close Down gesture at GIFs and videos, if they are zoomed in
Version 6.4.0 *(2019-01-29)*
----------------------------
* Implemented export/importing for app settings and other preferences, like sorting
* Allow hiding Notch on fullscreen view on Android 9+
* Some gif/video zoom related improvements
* Autosave images zoomed at the fullscreen view
* Many other UX and stability improvements
Version 6.3.2 *(2019-01-23)*
----------------------------
* Fixed some fullscreen image and gif issues related to zooming
* Show directly included folders even if they contain a .nomedia file
Version 6.3.1 *(2019-01-22)*
----------------------------
* Fixed fullscreen images crashing when the app was installed on an SD card
* A couple other fullscreen image viewer improvements
* Allow batch rotating only images, ignore other file types
Version 6.3.0 *(2019-01-17)*
----------------------------
* Allow zooming GIFs and videos
* Allow sharing images directly from the editor
* Allow drawing in the editor
* If a folder is directly excluded, make it a higher priority than some included parent folder
* Added batch rotating from the thumbnails view
* Many other smaller improvements
Version 6.2.2 *(2019-01-10)*
----------------------------
* Reverted to the old way of playing videos, opening them on a separate screen can be enabled in the app settings
* Added some memory related improvements at displaying fullscreen images
* Allow showing videos in slideshows
Version 6.2.1 *(2019-01-08)*
----------------------------
* Fixed some menu buttons at the video player activity
* Added buttons to the videoplayer for going to the previous/next item
* Allow pressing play/pause at the video player at fullscreen mode
* Properly retain exif values after editing a file, when overwriting the source file
Version 6.2.0 *(2019-01-04)*
----------------------------
* Rewrote video playback, use a separate screen + added fast-forwarding with horizontal swiping
* Added optional 1:1 pixel ratio zooming with two double taps at fullscreen view
* Allow adding Copy at the fullscreen bottom actions
* Always include images at slideshows, never videos
* Fixed scanning of some predefined folders for images
* Some other stability/performance/translation improvements
Version 6.1.3 *(2018-12-26)*
----------------------------
* Fixed a glitch at zooming fullscreen images with double tap
* Hide favorite items from hidden folders, if showing hidden items is disabled
Version 6.1.2 *(2018-12-24)*
----------------------------
* Done a few performance improvements here and there
* Allow changing view type individually per folder
* Merry Christmas!
Version 6.1.1 *(2018-12-18)*
----------------------------
* Fixing some crashes
Version 6.1.0 *(2018-12-17)*
----------------------------
* Added an initial widget implementation for creating homescreen folder shortcuts
* Added optional grouping of direct subfolders, as a check at the "Change view type" dialog
* Added an option to set custom crop aspect ratio at the editor
* Save exif data at edited files on Android 7+
* Handle only Mass Storage USB devices, ignore the rest
* Many other smaller UX/stability/performance improvements
Version 6.0.4 *(2018-12-04)*
----------------------------
* Limit automatic spam folder exclusion to the "/Android/data" folder
Version 6.0.3 *(2018-12-02)*
----------------------------
* Added multiple predefined aspect ratios at the Editor + remember the last used ratio
* Fix some issue with deleted items not appearing in the Recycle Bin, causing the app to take up too much space
* At delete/copy/move operations on folders apply them only on the visible files, take filters/hiding into account
* Do not exclude whole Data folder by default, be smarter about filtering out spam folders
* Added support for Sony RAW ".arw" files
* Optimize video duration fetching at thumbnails
Version 6.0.2 *(2018-11-19)*
----------------------------
* Adding a crashfix related to showing video duration
Version 6.0.1 *(2018-11-19)*
----------------------------
* Added optional displaying video duration on thumbnails
* Fixed keeping last_modified value at copy/move in some cases
* Exclude the Data folder by default
* Many translation, UX and stability improvements
Version 6.0.0 *(2018-11-04)*
----------------------------
* Initial Pro version
Version 5.1.4 *(2018-11-28)*
----------------------------
* Make sure the "Upgrade to Pro" popup isn't shown at first launch
* This version of the app is no longer maintained, please upgrade to the Pro version. You can find the Upgrade button at the top of the app Settings.
Version 5.1.3 *(2018-11-04)*
----------------------------
* Adding an option to store last video playback position (by mathevs)
* Adding a "Keep both" conflict resolution at copy/move (by Doubl3MM)
* Improved panoramic video detection
* Remove some glitches related to third party file opening
* Do not exclude the Data folder by default
* Removed the "Avoid showing Whats New at app startup" option
Version 5.1.2 *(2018-10-30)*
----------------------------
* Added a new option for password protecting file deletion/move
* Improved panorama video detection
* Improved the opening of media files without file extension
* Disabled move operation on Recycle bin items, use Restore
* Fixed handling of some third party image picker intents
* Fixed slideshow looping and a couple other UX glitches
* Improved the stability of retrieving cached files
* Hi
Version 5.1.1 *(2018-10-23)*
----------------------------
* Fixing the inability to delete SD card files
Version 5.1.0 *(2018-10-23)*
----------------------------
* Added support for panorama videos
* Added an extra check to avoid trying deleting files without write permission
* Added an initial implementation of renaming multiple items at once
* Added some performance improvements at item de/selection
* Allow enabling hidden item visibility at the copy/move destination picker
* Allow closing fullscreen view with swipe down gestures (can be disabled in settings)
* Fixed a glitch with Favorite items getting unselected every day
* Fixed exposure time displayed at long exposure photos
* Fixed fullscreen images being sometimes totally zoomed in after device rotation
* Fixed slideshow direction
* Made loading initial fullscreen view quicker and fullscreen toggling more reliable
* Not sure what else, nobody reads this anyway
Version 5.0.1 *(2018-10-17)*
----------------------------
* Adding some crashfixes
Version 5.0.0 *(2018-10-17)*
----------------------------
* Increased the minimal required Android OS version to 5 (Lollipop)
* Rewrote file selection for more robustness
* Added a new option for showing the Recycle Bin as the last folder
* Added Search for searching folders by names
* Replaced the G+ button with Reddit
* Couple smaller glitch fixes and improvements
Version 4.6.5 *(2018-10-02)*
----------------------------
* Added notch support for Android 9
* Allow faster video seeking by dragging a finger at the bottom seekbar
* Use a different way of displaying fullscreen GIFs
* Added a new toggle for trying to show the best possible image quality
* Keep Favorite items marked after moving
* Fixed some glitches related to toggling fullscreen mode
* Many other smaller improvements
Version 4.6.4 *(2018-09-22)*
----------------------------
* Fixed lag at zooming fullscreen images on some devices
Version 4.6.3 *(2018-09-21)*
----------------------------
* Improved zooming performance at fullscreen view
* Fixed showing conflict resolution dialog at Move
* Fixed selection check icons at horizontal scrolling
* Fixed displaying some fullscreen images, where file path contained percentage sign or hashtag
* Optimized many database operations
* Fixed many other smaller issues
Version 4.6.2 *(2018-09-05)* Version 4.6.2 *(2018-09-05)*
---------------------------- ----------------------------

View file

@ -4,21 +4,39 @@
A gallery for viewing photos and videos. A gallery for viewing photos and videos.
A simple tool usable for viewing photos and videos. Items can be sorted by date, size, name both ascending or descending, photos can be zoomed in. Media files are shown in multiple columns depending on the size of the display, you can change the column count by pinch gestures. They can be renamed, shared, deleted, copied, moved. Images can also be cropped, rotated, flipped or set as Wallpaper directly from the app. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos.
The Gallery is also offered for third party usage for previewing images / videos, adding attachments at email clients etc. It's perfect for everyday usage. It is open source, contains no ads or unnecessary permissions.
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. Let's list some of its features worth mentioning:
1. Search
2. Slideshow
3. Notch support
4. Pinning folders to the top
5. Filtering media files by type
6. Recycle bin for easy file recovery
7. Fullscreen view orientation locking
8. Marking favorite files for easy access
9. Quick fullscreen media closing with down gesture
10. An editor for modifying images and applying filters
11. Password protection for protecting hidden items or the whole app
12. Changing the thumbnail column count with gestures or menu buttons
13. Customizable bottom actions at the fullscreen view for quick access
14. Showing extended details over fullscreen media with desired file properties
15. Several different ways of sorting or grouping items, both ascending and descending
16. Hiding folders (affects other apps too), excluding folders (affects only Simple Gallery)
This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com The fingerprint permission is needed for locking either hidden item visibility, the whole app, or protecting files from being deleted.
<a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.gallery'><img src='http://simplemobiletools.github.io/assets/public/google-play.png' alt='Get it on Google Play' height='45' /></a> This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com
<a href='https://f-droid.org/packages/com.simplemobiletools.gallery'><img src='http://simplemobiletools.github.io/assets/public/f-droid.png' alt='Get it on F-Droid' height='45' /></a>
<a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.gallery.pro'><img src='http://simplemobiletools.github.io/assets/public/google-play.png' alt='Get it on Google Play' height='45' /></a>
<a href='https://f-droid.org/packages/com.simplemobiletools.gallery.pro'><img src='http://simplemobiletools.github.io/assets/public/f-droid.png' alt='Get it on F-Droid' height='45' /></a>
<div style="display:flex;"> <div style="display:flex;">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app.jpg" width="30%"> <img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app.jpg" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.jpg" width="30%"> <img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.jpg" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_5.jpg" width="30%"> <img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_3.jpg" width="30%">
</div> </div>
License License

View file

@ -3,22 +3,31 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android { android {
compileSdkVersion 28 compileSdkVersion 28
buildToolsVersion "28.0.2" buildToolsVersion "28.0.3"
defaultConfig { defaultConfig {
applicationId "com.simplemobiletools.gallery" applicationId "com.simplemobiletools.gallery.pro"
minSdkVersion 16 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 28
versionCode 198 versionCode 228
versionName "4.6.2" versionName "6.5.0"
multiDexEnabled true multiDexEnabled true
setProperty("archivesBaseName", "gallery") setProperty("archivesBaseName", "gallery")
} }
signingConfigs { signingConfigs {
release release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
} }
buildTypes { buildTypes {
@ -40,49 +49,36 @@ android {
checkReleaseBuilds false checkReleaseBuilds false
abortOnError false abortOnError false
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
exclude 'META-INF/library_release.kotlin_module'
}
} }
dependencies { dependencies {
implementation 'com.simplemobiletools:commons:4.7.14' implementation 'com.simplemobiletools:commons:5.7.6'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.7.0' implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
implementation 'com.android.support:multidex:1.0.3' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'it.sephiroth.android.exif:library:1.0.1' implementation 'it.sephiroth.android.exif:library:1.0.1'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15' implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.16'
implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
implementation 'com.google.android.exoplayer:exoplayer-core:2.8.4' implementation 'com.google.android.exoplayer:exoplayer-core:2.9.2'
implementation 'com.google.vr:sdk-panowidget:1.150.0' implementation 'com.google.vr:sdk-panowidget:1.180.0'
implementation 'com.google.vr:sdk-videowidget:1.180.0'
implementation 'org.apache.sanselan:sanselan:0.97-incubator' implementation 'org.apache.sanselan:sanselan:0.97-incubator'
implementation 'info.androidhive:imagefilters:1.0.7' implementation 'info.androidhive:imagefilters:1.0.7'
implementation 'com.squareup.picasso:picasso:2.71828' implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.caverock:androidsvg-aar:1.3' implementation 'com.caverock:androidsvg-aar:1.3'
implementation 'com.github.tibbi:gestureviews:bd0a8e67a1'
implementation 'com.github.tibbi:subsampling-scale-image-view:2fe77ff361'
kapt 'com.github.bumptech.glide:compiler:4.8.0' // keep it here too, not just in Commons, else loading SVGs wont work kapt 'com.github.bumptech.glide:compiler:4.8.0' // keep it here too, not just in Commons, else loading SVGs wont work
kapt "android.arch.persistence.room:compiler:1.1.1" kapt 'androidx.room:room-compiler:2.0.0'
implementation "android.arch.persistence.room:runtime:1.1.1" implementation 'androidx.room:room-runtime:2.0.0'
annotationProcessor "android.arch.persistence.room:compiler:1.1.1" annotationProcessor 'androidx.room:room-compiler:2.0.0'
//implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.9.0'
implementation 'com.github.tibbi:subsampling-scale-image-view:v3.10.0-fork'
// implementation 'com.github.chrisbanes:PhotoView:2.1.4'
implementation 'com.github.tibbi:PhotoView:2.1.4-fork'
}
Properties props = new Properties()
def propFile = new File('signing.properties')
if (propFile.canRead()) {
props.load(new FileInputStream(propFile))
if (props != null && props.containsKey('STORE_FILE') && props.containsKey('KEY_ALIAS') && props.containsKey('PASSWORD')) {
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
android.signingConfigs.release.storePassword = props['PASSWORD']
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
android.signingConfigs.release.keyPassword = props['PASSWORD']
} else {
println 'signing.properties found but some entries are missing'
android.buildTypes.release.signingConfig = null
}
} else {
println 'signing.properties not found'
android.buildTypes.release.signingConfig = null
} }

View file

@ -2,7 +2,7 @@
<manifest <manifest
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.simplemobiletools.gallery" package="com.simplemobiletools.gallery.pro"
android:installLocation="auto"> android:installLocation="auto">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@ -32,6 +32,14 @@
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
android:resizeableActivity="true"> android:resizeableActivity="true">
<meta-data
android:name="android.app.default_searchable"
android:resource="@xml/searchable"/>
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.PICK"/> <action android:name="android.intent.action.PICK"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
@ -115,7 +123,17 @@
android:configChanges="orientation|keyboardHidden|screenSize"/> android:configChanges="orientation|keyboardHidden|screenSize"/>
<activity <activity
android:name=".activities.PanoramaActivity" android:name=".activities.VideoPlayerActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName=".activities.MediaActivity"/>
<activity
android:name=".activities.PanoramaPhotoActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/FullScreenTheme"/>
<activity
android:name=".activities.PanoramaVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/FullScreenTheme"/> android:theme="@style/FullScreenTheme"/>
@ -202,8 +220,17 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".activities.WidgetConfigureActivity"
android:screenOrientation="portrait"
android:theme="@style/MyWidgetConfigTheme">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
<provider <provider
android:name="android.support.v4.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
@ -220,6 +247,18 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name=".helpers.MyWidgetProvider"
android:icon="@drawable/img_widget_preview">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_info"/>
</receiver>
<activity-alias <activity-alias
android:name=".activities.SplashActivity.Red" android:name=".activities.SplashActivity.Red"
android:enabled="false" android:enabled="false"

View file

@ -1,483 +0,0 @@
package com.simplemobiletools.gallery.activities
import android.content.Intent
import android.content.res.Resources
import android.os.Bundle
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.dialogs.SecurityDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PROTECTION_FINGERPRINT
import com.simplemobiletools.commons.helpers.SHOW_ALL_TABS
import com.simplemobiletools.commons.helpers.sumByLong
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.dialogs.ManageBottomActionsDialog
import com.simplemobiletools.gallery.dialogs.ManageExtendedDetailsDialog
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.extensions.emptyTheRecycleBin
import com.simplemobiletools.gallery.extensions.galleryDB
import com.simplemobiletools.gallery.extensions.showRecycleBinEmptyingDialog
import com.simplemobiletools.gallery.helpers.DEFAULT_BOTTOM_ACTIONS
import com.simplemobiletools.gallery.helpers.ROTATE_BY_ASPECT_RATIO
import com.simplemobiletools.gallery.helpers.ROTATE_BY_DEVICE_ROTATION
import com.simplemobiletools.gallery.helpers.ROTATE_BY_SYSTEM_SETTING
import kotlinx.android.synthetic.main.activity_settings.*
import java.util.*
class SettingsActivity : SimpleActivity() {
lateinit var res: Resources
private var mRecycleBinContentSize = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
res = resources
}
override fun onResume() {
super.onResume()
setupPurchaseThankYou()
setupCustomizeColors()
setupUseEnglish()
setupAvoidWhatsNew()
setupManageIncludedFolders()
setupManageExcludedFolders()
setupManageHiddenFolders()
setupShowHiddenItems()
setupDoExtraCheck()
setupAutoplayVideos()
setupLoopVideos()
setupAnimateGifs()
setupMaxBrightness()
setupCropThumbnails()
setupDarkBackground()
setupScrollHorizontally()
setupScreenRotation()
setupHideSystemUI()
setupPasswordProtection()
setupAppPasswordProtection()
setupDeleteEmptyFolders()
setupAllowPhotoGestures()
setupAllowVideoGestures()
setupBottomActions()
setupShowMediaCount()
setupKeepLastModified()
setupShowInfoBubble()
setupEnablePullToRefresh()
setupAllowZoomingImages()
setupOneFingerZoom()
setupAllowInstantChange()
setupShowExtendedDetails()
setupHideExtendedDetails()
setupManageExtendedDetails()
setupSkipDeleteConfirmation()
setupManageBottomActions()
setupUseRecycleBin()
setupShowRecycleBin()
setupEmptyRecycleBin()
updateTextColors(settings_holder)
setupSectionColors()
}
private fun setupSectionColors() {
val adjustedPrimaryColor = getAdjustedPrimaryColor()
arrayListOf(visibility_label, videos_label, thumbnails_label, scrolling_label, fullscreen_media_label, security_label,
file_operations_label, extended_details_label, bottom_actions_label, recycle_bin_label).forEach {
it.setTextColor(adjustedPrimaryColor)
}
}
private fun setupPurchaseThankYou() {
settings_purchase_thank_you_holder.beVisibleIf(config.appRunCount > 10 && !isThankYouInstalled())
settings_purchase_thank_you_holder.setOnClickListener {
launchPurchaseThankYouIntent()
}
}
private fun setupCustomizeColors() {
settings_customize_colors_holder.setOnClickListener {
startCustomizationActivity()
}
}
private fun setupUseEnglish() {
settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en")
settings_use_english.isChecked = config.useEnglish
settings_use_english_holder.setOnClickListener {
settings_use_english.toggle()
config.useEnglish = settings_use_english.isChecked
System.exit(0)
}
}
private fun setupAvoidWhatsNew() {
settings_avoid_whats_new.isChecked = config.avoidWhatsNew
settings_avoid_whats_new_holder.setOnClickListener {
settings_avoid_whats_new.toggle()
config.avoidWhatsNew = settings_avoid_whats_new.isChecked
}
}
private fun setupManageIncludedFolders() {
settings_manage_included_folders_holder.setOnClickListener {
startActivity(Intent(this, IncludedFoldersActivity::class.java))
}
}
private fun setupManageExcludedFolders() {
settings_manage_excluded_folders_holder.setOnClickListener {
startActivity(Intent(this, ExcludedFoldersActivity::class.java))
}
}
private fun setupManageHiddenFolders() {
settings_manage_hidden_folders_holder.setOnClickListener {
handleHiddenFolderPasswordProtection {
startActivity(Intent(this, HiddenFoldersActivity::class.java))
}
}
}
private fun setupShowHiddenItems() {
settings_show_hidden_items.isChecked = config.showHiddenMedia
settings_show_hidden_items_holder.setOnClickListener {
if (config.showHiddenMedia) {
toggleHiddenItems()
} else {
handleHiddenFolderPasswordProtection {
toggleHiddenItems()
}
}
}
}
private fun toggleHiddenItems() {
settings_show_hidden_items.toggle()
config.showHiddenMedia = settings_show_hidden_items.isChecked
}
private fun setupDoExtraCheck() {
settings_do_extra_check.isChecked = config.doExtraCheck
settings_do_extra_check_holder.setOnClickListener {
settings_do_extra_check.toggle()
config.doExtraCheck = settings_do_extra_check.isChecked
}
}
private fun setupAutoplayVideos() {
settings_autoplay_videos.isChecked = config.autoplayVideos
settings_autoplay_videos_holder.setOnClickListener {
settings_autoplay_videos.toggle()
config.autoplayVideos = settings_autoplay_videos.isChecked
}
}
private fun setupLoopVideos() {
settings_loop_videos.isChecked = config.loopVideos
settings_loop_videos_holder.setOnClickListener {
settings_loop_videos.toggle()
config.loopVideos = settings_loop_videos.isChecked
}
}
private fun setupAnimateGifs() {
settings_animate_gifs.isChecked = config.animateGifs
settings_animate_gifs_holder.setOnClickListener {
settings_animate_gifs.toggle()
config.animateGifs = settings_animate_gifs.isChecked
}
}
private fun setupMaxBrightness() {
settings_max_brightness.isChecked = config.maxBrightness
settings_max_brightness_holder.setOnClickListener {
settings_max_brightness.toggle()
config.maxBrightness = settings_max_brightness.isChecked
}
}
private fun setupCropThumbnails() {
settings_crop_thumbnails.isChecked = config.cropThumbnails
settings_crop_thumbnails_holder.setOnClickListener {
settings_crop_thumbnails.toggle()
config.cropThumbnails = settings_crop_thumbnails.isChecked
}
}
private fun setupDarkBackground() {
settings_black_background.isChecked = config.blackBackground
settings_black_background_holder.setOnClickListener {
settings_black_background.toggle()
config.blackBackground = settings_black_background.isChecked
}
}
private fun setupScrollHorizontally() {
settings_scroll_horizontally.isChecked = config.scrollHorizontally
settings_scroll_horizontally_holder.setOnClickListener {
settings_scroll_horizontally.toggle()
config.scrollHorizontally = settings_scroll_horizontally.isChecked
if (config.scrollHorizontally) {
config.enablePullToRefresh = false
settings_enable_pull_to_refresh.isChecked = false
}
}
}
private fun setupHideSystemUI() {
settings_hide_system_ui.isChecked = config.hideSystemUI
settings_hide_system_ui_holder.setOnClickListener {
settings_hide_system_ui.toggle()
config.hideSystemUI = settings_hide_system_ui.isChecked
}
}
private fun setupPasswordProtection() {
settings_password_protection.isChecked = config.isPasswordProtectionOn
settings_password_protection_holder.setOnClickListener {
val tabToShow = if (config.isPasswordProtectionOn) config.protectionType else SHOW_ALL_TABS
SecurityDialog(this, config.passwordHash, tabToShow) { hash, type, success ->
if (success) {
val hasPasswordProtection = config.isPasswordProtectionOn
settings_password_protection.isChecked = !hasPasswordProtection
config.isPasswordProtectionOn = !hasPasswordProtection
config.passwordHash = if (hasPasswordProtection) "" else hash
config.protectionType = type
if (config.isPasswordProtectionOn) {
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) { }
}
}
}
}
}
private fun setupAppPasswordProtection() {
settings_app_password_protection.isChecked = config.appPasswordProtectionOn
settings_app_password_protection_holder.setOnClickListener {
val tabToShow = if (config.appPasswordProtectionOn) config.appProtectionType else SHOW_ALL_TABS
SecurityDialog(this, config.appPasswordHash, tabToShow) { hash, type, success ->
if (success) {
val hasPasswordProtection = config.appPasswordProtectionOn
settings_app_password_protection.isChecked = !hasPasswordProtection
config.appPasswordProtectionOn = !hasPasswordProtection
config.appPasswordHash = if (hasPasswordProtection) "" else hash
config.appProtectionType = type
if (config.appPasswordProtectionOn) {
val confirmationTextId = if (config.appProtectionType == PROTECTION_FINGERPRINT)
R.string.fingerprint_setup_successfully else R.string.protection_setup_successfully
ConfirmationDialog(this, "", confirmationTextId, R.string.ok, 0) { }
}
}
}
}
}
private fun setupDeleteEmptyFolders() {
settings_delete_empty_folders.isChecked = config.deleteEmptyFolders
settings_delete_empty_folders_holder.setOnClickListener {
settings_delete_empty_folders.toggle()
config.deleteEmptyFolders = settings_delete_empty_folders.isChecked
}
}
private fun setupAllowPhotoGestures() {
settings_allow_photo_gestures.isChecked = config.allowPhotoGestures
settings_allow_photo_gestures_holder.setOnClickListener {
settings_allow_photo_gestures.toggle()
config.allowPhotoGestures = settings_allow_photo_gestures.isChecked
}
}
private fun setupAllowVideoGestures() {
settings_allow_video_gestures.isChecked = config.allowVideoGestures
settings_allow_video_gestures_holder.setOnClickListener {
settings_allow_video_gestures.toggle()
config.allowVideoGestures = settings_allow_video_gestures.isChecked
}
}
private fun setupShowMediaCount() {
settings_show_media_count.isChecked = config.showMediaCount
settings_show_media_count_holder.setOnClickListener {
settings_show_media_count.toggle()
config.showMediaCount = settings_show_media_count.isChecked
}
}
private fun setupKeepLastModified() {
settings_keep_last_modified.isChecked = config.keepLastModified
settings_keep_last_modified_holder.setOnClickListener {
settings_keep_last_modified.toggle()
config.keepLastModified = settings_keep_last_modified.isChecked
}
}
private fun setupShowInfoBubble() {
settings_show_info_bubble.isChecked = config.showInfoBubble
settings_show_info_bubble_holder.setOnClickListener {
settings_show_info_bubble.toggle()
config.showInfoBubble = settings_show_info_bubble.isChecked
}
}
private fun setupEnablePullToRefresh() {
settings_enable_pull_to_refresh.isChecked = config.enablePullToRefresh
settings_enable_pull_to_refresh_holder.setOnClickListener {
settings_enable_pull_to_refresh.toggle()
config.enablePullToRefresh = settings_enable_pull_to_refresh.isChecked
}
}
private fun setupAllowZoomingImages() {
settings_allow_zooming_images.isChecked = config.allowZoomingImages
settings_allow_zooming_images_holder.setOnClickListener {
settings_allow_zooming_images.toggle()
config.allowZoomingImages = settings_allow_zooming_images.isChecked
settings_one_finger_zoom_holder.beVisibleIf(config.allowZoomingImages)
}
}
private fun setupOneFingerZoom() {
settings_one_finger_zoom.isChecked = config.oneFingerZoom
settings_one_finger_zoom_holder.setOnClickListener {
settings_one_finger_zoom.toggle()
config.oneFingerZoom = settings_one_finger_zoom.isChecked
}
}
private fun setupAllowInstantChange() {
settings_allow_instant_change.isChecked = config.allowInstantChange
settings_allow_instant_change_holder.setOnClickListener {
settings_allow_instant_change.toggle()
config.allowInstantChange = settings_allow_instant_change.isChecked
}
}
private fun setupShowExtendedDetails() {
settings_show_extended_details.isChecked = config.showExtendedDetails
settings_show_extended_details_holder.setOnClickListener {
settings_show_extended_details.toggle()
config.showExtendedDetails = settings_show_extended_details.isChecked
settings_manage_extended_details_holder.beVisibleIf(config.showExtendedDetails)
settings_hide_extended_details_holder.beVisibleIf(config.showExtendedDetails)
}
}
private fun setupHideExtendedDetails() {
settings_hide_extended_details_holder.beVisibleIf(config.showExtendedDetails)
settings_hide_extended_details.isChecked = config.hideExtendedDetails
settings_hide_extended_details_holder.setOnClickListener {
settings_hide_extended_details.toggle()
config.hideExtendedDetails = settings_hide_extended_details.isChecked
}
}
private fun setupManageExtendedDetails() {
settings_manage_extended_details_holder.beVisibleIf(config.showExtendedDetails)
settings_manage_extended_details_holder.setOnClickListener {
ManageExtendedDetailsDialog(this) {
if (config.extendedDetails == 0) {
settings_show_extended_details_holder.callOnClick()
}
}
}
}
private fun setupSkipDeleteConfirmation() {
settings_skip_delete_confirmation.isChecked = config.skipDeleteConfirmation
settings_skip_delete_confirmation_holder.setOnClickListener {
settings_skip_delete_confirmation.toggle()
config.skipDeleteConfirmation = settings_skip_delete_confirmation.isChecked
}
}
private fun setupScreenRotation() {
settings_screen_rotation.text = getScreenRotationText()
settings_screen_rotation_holder.setOnClickListener {
val items = arrayListOf(
RadioItem(ROTATE_BY_SYSTEM_SETTING, res.getString(R.string.screen_rotation_system_setting)),
RadioItem(ROTATE_BY_DEVICE_ROTATION, res.getString(R.string.screen_rotation_device_rotation)),
RadioItem(ROTATE_BY_ASPECT_RATIO, res.getString(R.string.screen_rotation_aspect_ratio)))
RadioGroupDialog(this@SettingsActivity, items, config.screenRotation) {
config.screenRotation = it as Int
settings_screen_rotation.text = getScreenRotationText()
}
}
}
private fun getScreenRotationText() = getString(when (config.screenRotation) {
ROTATE_BY_SYSTEM_SETTING -> R.string.screen_rotation_system_setting
ROTATE_BY_DEVICE_ROTATION -> R.string.screen_rotation_device_rotation
else -> R.string.screen_rotation_aspect_ratio
})
private fun setupBottomActions() {
settings_bottom_actions.isChecked = config.bottomActions
settings_bottom_actions_holder.setOnClickListener {
settings_bottom_actions.toggle()
config.bottomActions = settings_bottom_actions.isChecked
settings_manage_bottom_actions_holder.beVisibleIf(config.bottomActions)
}
}
private fun setupManageBottomActions() {
settings_manage_bottom_actions_holder.beVisibleIf(config.bottomActions)
settings_manage_bottom_actions_holder.setOnClickListener {
ManageBottomActionsDialog(this) {
if (config.visibleBottomActions == 0) {
settings_bottom_actions_holder.callOnClick()
config.bottomActions = false
config.visibleBottomActions = DEFAULT_BOTTOM_ACTIONS
}
}
}
}
private fun setupUseRecycleBin() {
settings_empty_recycle_bin_holder.beVisibleIf(config.useRecycleBin)
settings_show_recycle_bin_holder.beVisibleIf(config.useRecycleBin)
settings_use_recycle_bin.isChecked = config.useRecycleBin
settings_use_recycle_bin_holder.setOnClickListener {
settings_use_recycle_bin.toggle()
config.useRecycleBin = settings_use_recycle_bin.isChecked
settings_empty_recycle_bin_holder.beVisibleIf(config.useRecycleBin)
}
}
private fun setupShowRecycleBin() {
settings_show_recycle_bin.isChecked = config.showRecycleBinAtFolders
settings_show_recycle_bin_holder.setOnClickListener {
settings_show_recycle_bin.toggle()
config.showRecycleBinAtFolders = settings_show_recycle_bin.isChecked
}
}
private fun setupEmptyRecycleBin() {
Thread {
mRecycleBinContentSize = galleryDB.MediumDao().getDeletedMedia().sumByLong { it.size }
runOnUiThread {
settings_empty_recycle_bin_size.text = mRecycleBinContentSize.formatSize()
}
}.start()
settings_empty_recycle_bin_holder.setOnClickListener {
if (mRecycleBinContentSize == 0L) {
toast(R.string.recycle_bin_empty)
} else {
showRecycleBinEmptyingDialog {
emptyTheRecycleBin()
mRecycleBinContentSize = 0L
settings_empty_recycle_bin_size.text = 0L.formatSize()
}
}
}
}
}

View file

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

View file

@ -1,98 +0,0 @@
package com.simplemobiletools.gallery.dialogs
import android.support.v7.app.AlertDialog
import android.support.v7.widget.GridLayoutManager
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.beGoneIf
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.DirectoryAdapter
import com.simplemobiletools.gallery.extensions.addTempFolderIfNeeded
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.*
class PickDirectoryDialog(val activity: BaseSimpleActivity, val sourcePath: String, val callback: (path: String) -> Unit) {
var dialog: AlertDialog
var shownDirectories = ArrayList<Directory>()
var view = activity.layoutInflater.inflate(R.layout.dialog_directory_picker, null)
var isGridViewType = activity.config.viewTypeFolders == VIEW_TYPE_GRID
init {
(view.directories_grid.layoutManager as MyGridLayoutManager).apply {
orientation = if (activity.config.scrollHorizontally && isGridViewType) GridLayoutManager.HORIZONTAL else GridLayoutManager.VERTICAL
spanCount = if (isGridViewType) activity.config.dirColumnCnt else 1
}
dialog = AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.other_folder, { dialogInterface, i -> showOtherFolder() })
.create().apply {
activity.setupDialogStuff(view, this, R.string.select_destination)
}
activity.getCachedDirectories {
if (it.isNotEmpty()) {
activity.runOnUiThread {
gotDirectories(activity.addTempFolderIfNeeded(it))
}
}
}
}
private fun showOtherFolder() {
val showHidden = activity.config.shouldShowHidden
FilePickerDialog(activity, sourcePath, false, showHidden, true) {
callback(it)
}
}
private fun gotDirectories(newDirs: ArrayList<Directory>) {
val dirs = activity.getSortedDirectories(newDirs)
if (dirs.hashCode() == shownDirectories.hashCode())
return
shownDirectories = dirs
val adapter = DirectoryAdapter(activity, dirs.clone() as ArrayList<Directory>, null, view.directories_grid, true) {
if ((it as Directory).path.trimEnd('/') == sourcePath) {
activity.toast(R.string.source_and_destination_same)
return@DirectoryAdapter
} else {
callback(it.path)
dialog.dismiss()
}
}
val scrollHorizontally = activity.config.scrollHorizontally && isGridViewType
val sorting = activity.config.directorySorting
view.apply {
directories_grid.adapter = adapter
directories_vertical_fastscroller.isHorizontal = false
directories_vertical_fastscroller.beGoneIf(scrollHorizontally)
directories_horizontal_fastscroller.isHorizontal = true
directories_horizontal_fastscroller.beVisibleIf(scrollHorizontally)
if (scrollHorizontally) {
directories_horizontal_fastscroller.allowBubbleDisplay = activity.config.showInfoBubble
directories_horizontal_fastscroller.setViews(directories_grid) {
directories_horizontal_fastscroller.updateBubbleText(dirs[it].getBubbleText(sorting))
}
} else {
directories_vertical_fastscroller.allowBubbleDisplay = activity.config.showInfoBubble
directories_vertical_fastscroller.setViews(directories_grid) {
directories_vertical_fastscroller.updateBubbleText(dirs[it].getBubbleText(sorting))
}
}
}
}
}

View file

@ -1,97 +0,0 @@
package com.simplemobiletools.gallery.dialogs
import android.graphics.Point
import android.support.v7.app.AlertDialog
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.showKeyboard
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.extensions.value
import com.simplemobiletools.gallery.R
import kotlinx.android.synthetic.main.resize_image.view.*
class ResizeDialog(val activity: BaseSimpleActivity, val size: Point, val callback: (newSize: Point) -> Unit) {
init {
val view = activity.layoutInflater.inflate(R.layout.resize_image, null)
val widthView = view.image_width
val heightView = view.image_height
widthView.setText(size.x.toString())
heightView.setText(size.y.toString())
val ratio = size.x / size.y.toFloat()
widthView.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (widthView.hasFocus()) {
var width = getViewValue(widthView)
if (width > size.x) {
widthView.setText(size.x.toString())
width = size.x
}
if (view.keep_aspect_ratio.isChecked) {
heightView.setText((width / ratio).toInt().toString())
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
heightView.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (heightView.hasFocus()) {
var height = getViewValue(heightView)
if (height > size.y) {
heightView.setText(size.y.toString())
height = size.y
}
if (view.keep_aspect_ratio.isChecked) {
widthView.setText((height * ratio).toInt().toString())
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.resize_and_save) {
showKeyboard(view.image_width)
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val width = getViewValue(widthView)
val height = getViewValue(heightView)
if (width <= 0 || height <= 0) {
activity.toast(R.string.invalid_values)
return@setOnClickListener
}
val newSize = Point(getViewValue(widthView), getViewValue(heightView))
callback(newSize)
dismiss()
}
}
}
}
fun getViewValue(view: EditText): Int {
val textValue = view.value
return if (textValue.isEmpty()) 0 else textValue.toInt()
}
}

View file

@ -1,278 +0,0 @@
package com.simplemobiletools.gallery.extensions
import android.app.Activity
import android.content.Intent
import android.provider.MediaStore
import android.support.v7.app.AppCompatActivity
import android.view.View
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.FAQItem
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.gallery.BuildConfig
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.SimpleActivity
import com.simplemobiletools.gallery.dialogs.PickDirectoryDialog
import com.simplemobiletools.gallery.helpers.NOMEDIA
import com.simplemobiletools.gallery.interfaces.MediumDao
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.util.*
fun Activity.sharePath(path: String) {
sharePathIntent(path, BuildConfig.APPLICATION_ID)
}
fun Activity.sharePaths(paths: ArrayList<String>) {
sharePathsIntent(paths, BuildConfig.APPLICATION_ID)
}
fun Activity.shareMediumPath(path: String) {
sharePath(path)
}
fun Activity.shareMediaPaths(paths: ArrayList<String>) {
sharePaths(paths)
}
fun Activity.setAs(path: String) {
setAsIntent(path, BuildConfig.APPLICATION_ID)
}
fun Activity.openPath(path: String, forceChooser: Boolean) {
openPathIntent(path, forceChooser, BuildConfig.APPLICATION_ID)
}
fun Activity.openEditor(path: String) {
openEditorIntent(path, BuildConfig.APPLICATION_ID)
}
fun Activity.launchCamera() {
val intent = Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
} else {
toast(R.string.no_app_found)
}
}
fun SimpleActivity.launchAbout() {
val licenses = LICENSE_GLIDE or LICENSE_CROPPER or LICENSE_MULTISELECT or LICENSE_RTL or LICENSE_SUBSAMPLING or LICENSE_PATTERN or
LICENSE_REPRINT or LICENSE_GIF_DRAWABLE or LICENSE_PHOTOVIEW or LICENSE_PICASSO or LICENSE_EXOPLAYER or LICENSE_PANORAMA_VIEW or LICENSE_SANSELAN or LICENSE_FILTERS
val faqItems = arrayListOf(
FAQItem(R.string.faq_5_title_commons, R.string.faq_5_text_commons),
FAQItem(R.string.faq_1_title, R.string.faq_1_text),
FAQItem(R.string.faq_2_title, R.string.faq_2_text),
FAQItem(R.string.faq_3_title, R.string.faq_3_text),
FAQItem(R.string.faq_4_title, R.string.faq_4_text),
FAQItem(R.string.faq_5_title, R.string.faq_5_text),
FAQItem(R.string.faq_6_title, R.string.faq_6_text),
FAQItem(R.string.faq_7_title, R.string.faq_7_text),
FAQItem(R.string.faq_8_title, R.string.faq_8_text),
FAQItem(R.string.faq_10_title, R.string.faq_10_text),
FAQItem(R.string.faq_11_title, R.string.faq_11_text),
FAQItem(R.string.faq_12_title, R.string.faq_12_text),
FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons))
startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
}
fun AppCompatActivity.showSystemUI(toggleActionBarVisibility: Boolean) {
if (toggleActionBarVisibility) {
supportActionBar?.show()
}
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
fun AppCompatActivity.hideSystemUI(toggleActionBarVisibility: Boolean) {
if (toggleActionBarVisibility) {
supportActionBar?.hide()
}
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LOW_PROFILE or
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_IMMERSIVE
}
fun BaseSimpleActivity.addNoMedia(path: String, callback: () -> Unit) {
val file = File(path, NOMEDIA)
if (file.exists()) {
callback()
return
}
if (needsStupidWritePermissions(path)) {
handleSAFDialog(file.absolutePath) {
val fileDocument = getDocumentFile(path)
if (fileDocument?.exists() == true && fileDocument.isDirectory) {
fileDocument.createFile("", NOMEDIA)
applicationContext.scanFileRecursively(file) {
callback()
}
} else {
toast(R.string.unknown_error_occurred)
callback()
}
}
} else {
try {
file.createNewFile()
applicationContext.scanFileRecursively(file) {
callback()
}
} catch (e: Exception) {
showErrorToast(e)
callback()
}
}
}
fun BaseSimpleActivity.removeNoMedia(path: String, callback: (() -> Unit)? = null) {
val file = File(path, NOMEDIA)
if (!file.exists()) {
callback?.invoke()
return
}
tryDeleteFileDirItem(file.toFileDirItem(applicationContext), false, false) {
callback?.invoke()
}
}
fun BaseSimpleActivity.toggleFileVisibility(oldPath: String, hide: Boolean, callback: ((newPath: String) -> Unit)? = null) {
val path = oldPath.getParentPath()
var filename = oldPath.getFilenameFromPath()
if ((hide && filename.startsWith('.')) || (!hide && !filename.startsWith('.'))) {
callback?.invoke(oldPath)
return
}
filename = if (hide) {
".${filename.trimStart('.')}"
} else {
filename.substring(1, filename.length)
}
val newPath = "$path/$filename"
renameFile(oldPath, newPath) {
callback?.invoke(newPath)
Thread {
updateDBMediaPath(oldPath, newPath)
}.start()
}
}
fun BaseSimpleActivity.tryCopyMoveFilesTo(fileDirItems: ArrayList<FileDirItem>, isCopyOperation: Boolean, callback: (destinationPath: String) -> Unit) {
if (fileDirItems.isEmpty()) {
toast(R.string.unknown_error_occurred)
return
}
val source = fileDirItems[0].getParentPath()
PickDirectoryDialog(this, source) {
copyMoveFilesTo(fileDirItems, source.trimEnd('/'), it, isCopyOperation, true, config.shouldShowHidden, callback)
}
}
fun BaseSimpleActivity.tryDeleteFileDirItem(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, deleteFromDatabase: Boolean,
callback: ((wasSuccess: Boolean) -> Unit)? = null) {
deleteFile(fileDirItem, allowDeleteFolder) {
if (deleteFromDatabase) {
Thread {
galleryDB.MediumDao().deleteMediumPath(fileDirItem.path)
runOnUiThread {
callback?.invoke(it)
}
}.start()
} else {
callback?.invoke(it)
}
}
}
fun BaseSimpleActivity.movePathsInRecycleBin(paths: ArrayList<String>, mediumDao: MediumDao = galleryDB.MediumDao(), callback: ((wasSuccess: Boolean) -> Unit)?) {
Thread {
var pathsCnt = paths.size
paths.forEach {
val file = File(it)
val internalFile = File(filesDir.absolutePath, it)
try {
if (file.copyRecursively(internalFile, true)) {
mediumDao.updateDeleted(it, System.currentTimeMillis())
pathsCnt--
}
} catch (ignored: Exception) {
}
}
callback?.invoke(pathsCnt == 0)
}.start()
}
fun BaseSimpleActivity.restoreRecycleBinPath(path: String, callback: () -> Unit) {
restoreRecycleBinPaths(arrayListOf(path), galleryDB.MediumDao(), callback)
}
fun BaseSimpleActivity.restoreRecycleBinPaths(paths: ArrayList<String>, mediumDao: MediumDao = galleryDB.MediumDao(), callback: () -> Unit) {
Thread {
paths.forEach {
val source = it
val destination = it.removePrefix(filesDir.absolutePath)
var inputStream: InputStream? = null
var out: OutputStream? = null
try {
out = getFileOutputStreamSync(destination, source.getMimeType())
inputStream = getFileInputStreamSync(source)!!
inputStream.copyTo(out!!)
if (File(source).length() == File(destination).length()) {
mediumDao.updateDeleted(destination, 0)
}
} catch (e: Exception) {
showErrorToast(e)
} finally {
inputStream?.close()
out?.close()
}
}
runOnUiThread {
callback()
}
}.start()
}
fun BaseSimpleActivity.emptyTheRecycleBin(callback: (() -> Unit)? = null) {
Thread {
filesDir.deleteRecursively()
galleryDB.MediumDao().clearRecycleBin()
galleryDB.DirectoryDao().deleteRecycleBin()
toast(R.string.recycle_bin_emptied)
callback?.invoke()
}.start()
}
fun BaseSimpleActivity.emptyAndDisableTheRecycleBin(callback: () -> Unit) {
Thread {
emptyTheRecycleBin {
config.useRecycleBin = false
callback()
}
}.start()
}
fun BaseSimpleActivity.showRecycleBinEmptyingDialog(callback: () -> Unit) {
ConfirmationDialog(this, "", R.string.empty_recycle_bin_confirmation, R.string.yes, R.string.no) {
callback()
}
}

View file

@ -1,530 +0,0 @@
package com.simplemobiletools.gallery.fragments
import android.annotation.SuppressLint
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.PictureDrawable
import android.media.ExifInterface.*
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.helpers.isJellyBean1Plus
import com.simplemobiletools.commons.helpers.isLollipopPlus
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.PanoramaActivity
import com.simplemobiletools.gallery.activities.PhotoActivity
import com.simplemobiletools.gallery.activities.ViewPagerActivity
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.svg.SvgSoftwareLayerSetter
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import it.sephiroth.android.library.exif2.ExifInterface
import kotlinx.android.synthetic.main.pager_photo_item.view.*
import org.apache.sanselan.common.byteSources.ByteSourceInputStream
import org.apache.sanselan.formats.jpeg.JpegImageParser
import pl.droidsonroids.gif.GifDrawable
import java.io.File
import java.io.FileOutputStream
class PhotoFragment : ViewPagerFragment() {
private val DEFAULT_DOUBLE_TAP_ZOOM = 2f
private val ZOOMABLE_VIEW_LOAD_DELAY = 300L
private var isFragmentVisible = false
private var isFullscreen = false
private var wasInit = false
private var isPanorama = false
private var imageOrientation = -1
private var gifDrawable: GifDrawable? = null
private var loadZoomableViewHandler = Handler()
private var storedShowExtendedDetails = false
private var storedHideExtendedDetails = false
private var storedExtendedDetails = 0
lateinit var view: ViewGroup
lateinit var medium: Medium
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
view = (inflater.inflate(R.layout.pager_photo_item, container, false) as ViewGroup).apply {
subsampling_view.setOnClickListener { photoClicked() }
photo_view.setOnClickListener { photoClicked() }
instant_prev_item.setOnClickListener { listener?.goToPrevItem() }
instant_next_item.setOnClickListener { listener?.goToNextItem() }
panorama_outline.setOnClickListener { openPanorama() }
instant_prev_item.parentView = container
instant_next_item.parentView = container
photo_brightness_controller.initialize(activity!!, slide_info, true, container) { x, y ->
view.apply {
if (subsampling_view.isVisible()) {
subsampling_view.sendFakeClick(x, y)
} else {
photo_view.sendFakeClick(x, y)
}
}
}
}
if (ViewPagerActivity.screenWidth == 0 || ViewPagerActivity.screenHeight == 0) {
measureScreen()
}
storeStateVariables()
if (!isFragmentVisible && activity is PhotoActivity) {
isFragmentVisible = true
}
medium = arguments!!.getSerializable(MEDIUM) as Medium
if (medium.path.startsWith("content://") && !medium.path.startsWith("content://mms/")) {
val originalPath = medium.path
medium.path = context!!.getRealPathFromURI(Uri.parse(originalPath)) ?: medium.path
if (medium.path.isEmpty()) {
var out: FileOutputStream? = null
try {
var inputStream = context!!.contentResolver.openInputStream(Uri.parse(originalPath))
val exif = ExifInterface()
exif.readExif(inputStream, ExifInterface.Options.OPTION_ALL)
val tag = exif.getTag(ExifInterface.TAG_ORIENTATION)
val orientation = tag?.getValueAsInt(-1) ?: -1
inputStream = context!!.contentResolver.openInputStream(Uri.parse(originalPath))
val original = BitmapFactory.decodeStream(inputStream)
val rotated = rotateViaMatrix(original, orientation)
exif.setTagValue(ExifInterface.TAG_ORIENTATION, 1)
exif.removeCompressedThumbnail()
val file = File(context!!.externalCacheDir, Uri.parse(originalPath).lastPathSegment)
out = FileOutputStream(file)
rotated.compress(Bitmap.CompressFormat.JPEG, 100, out)
medium.path = file.absolutePath
} catch (e: Exception) {
activity!!.toast(R.string.unknown_error_occurred)
return view
} finally {
out?.close()
}
}
}
isFullscreen = activity!!.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN == View.SYSTEM_UI_FLAG_FULLSCREEN
loadImage()
initExtendedDetails()
wasInit = true
checkIfPanorama()
return view
}
override fun onPause() {
super.onPause()
storeStateVariables()
}
override fun onResume() {
super.onResume()
if (wasInit && (context!!.config.showExtendedDetails != storedShowExtendedDetails || context!!.config.extendedDetails != storedExtendedDetails)) {
initExtendedDetails()
}
val allowPhotoGestures = context!!.config.allowPhotoGestures
val allowInstantChange = context!!.config.allowInstantChange
view.apply {
photo_brightness_controller.beVisibleIf(allowPhotoGestures)
instant_prev_item.beVisibleIf(allowInstantChange)
instant_next_item.beVisibleIf(allowInstantChange)
photo_view.setAllowFingerDragZoom(activity!!.config.oneFingerZoom)
}
storeStateVariables()
}
override fun setMenuVisibility(menuVisible: Boolean) {
super.setMenuVisibility(menuVisible)
isFragmentVisible = menuVisible
if (wasInit) {
if (medium.isGIF()) {
gifFragmentVisibilityChanged(menuVisible)
} else {
photoFragmentVisibilityChanged(menuVisible)
}
}
}
private fun storeStateVariables() {
context!!.config.apply {
storedShowExtendedDetails = showExtendedDetails
storedHideExtendedDetails = hideExtendedDetails
storedExtendedDetails = extendedDetails
}
}
@SuppressLint("NewApi")
private fun measureScreen() {
val metrics = DisplayMetrics()
if (isJellyBean1Plus()) {
activity!!.windowManager.defaultDisplay.getRealMetrics(metrics)
ViewPagerActivity.screenWidth = metrics.widthPixels
ViewPagerActivity.screenHeight = metrics.heightPixels
} else {
activity!!.windowManager.defaultDisplay.getMetrics(metrics)
ViewPagerActivity.screenWidth = metrics.widthPixels
ViewPagerActivity.screenHeight = metrics.heightPixels
}
}
private fun gifFragmentVisibilityChanged(isVisible: Boolean) {
if (isVisible) {
gifDrawable?.start()
} else {
gifDrawable?.stop()
}
}
private fun photoFragmentVisibilityChanged(isVisible: Boolean) {
if (isVisible) {
scheduleZoomableView()
} else {
view.subsampling_view.recycle()
view.subsampling_view.beGone()
loadZoomableViewHandler.removeCallbacksAndMessages(null)
}
}
private fun degreesForRotation(orientation: Int) = when (orientation) {
ORIENTATION_ROTATE_270 -> 270
ORIENTATION_ROTATE_180 -> 180
ORIENTATION_ROTATE_90 -> 90
else -> 0
}
private fun rotateViaMatrix(original: Bitmap, orientation: Int): Bitmap {
val degrees = degreesForRotation(orientation).toFloat()
return if (degrees == 0f) {
original
} else {
val matrix = Matrix()
matrix.setRotate(degrees)
Bitmap.createBitmap(original, 0, 0, original.width, original.height, matrix, true)
}
}
private fun loadImage() {
imageOrientation = getImageOrientation()
when {
medium.isGIF() -> loadGif()
medium.isSVG() -> loadSVG()
else -> loadBitmap()
}
}
private fun loadGif() {
try {
val pathToLoad = getPathToLoad(medium)
gifDrawable = if (pathToLoad.startsWith("content://") || pathToLoad.startsWith("file://")) {
GifDrawable(context!!.contentResolver, Uri.parse(pathToLoad))
} else {
GifDrawable(pathToLoad)
}
if (!isFragmentVisible) {
gifDrawable!!.stop()
}
view.photo_view.setImageDrawable(gifDrawable)
} catch (e: Exception) {
gifDrawable = null
loadBitmap()
} catch (e: OutOfMemoryError) {
gifDrawable = null
loadBitmap()
}
}
private fun loadSVG() {
Glide.with(this)
.`as`(PictureDrawable::class.java)
.listener(SvgSoftwareLayerSetter())
.load(medium.path)
.into(view.photo_view)
}
private fun loadBitmap(degrees: Int = 0) {
var pathToLoad = if (medium.path.startsWith("content://")) medium.path else "file://${medium.path}"
pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23")
try {
val picasso = Picasso.get()
.load(pathToLoad)
.centerInside()
.resize(ViewPagerActivity.screenWidth, ViewPagerActivity.screenHeight)
if (degrees != 0) {
picasso.rotate(degrees.toFloat())
}
picasso.into(view.photo_view, object : Callback {
override fun onSuccess() {
view.photo_view.isZoomable = degrees != 0 || context?.config?.allowZoomingImages == false
if (isFragmentVisible && degrees == 0) {
scheduleZoomableView()
}
}
override fun onError(e: Exception) {
if (context != null) {
tryLoadingWithGlide()
}
}
})
} catch (ignored: Exception) {
}
}
private fun tryLoadingWithGlide() {
var targetWidth = if (ViewPagerActivity.screenWidth == 0) com.bumptech.glide.request.target.Target.SIZE_ORIGINAL else ViewPagerActivity.screenWidth
var targetHeight = if (ViewPagerActivity.screenHeight == 0) com.bumptech.glide.request.target.Target.SIZE_ORIGINAL else ViewPagerActivity.screenHeight
if (imageOrientation == ORIENTATION_ROTATE_90) {
targetWidth = targetHeight
targetHeight = com.bumptech.glide.request.target.Target.SIZE_ORIGINAL
}
val options = RequestOptions()
.signature(medium.path.getFileSignature())
.format(DecodeFormat.PREFER_ARGB_8888)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.override(targetWidth, targetHeight)
Glide.with(context!!)
.asBitmap()
.load(getPathToLoad(medium))
.apply(options)
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: com.bumptech.glide.request.target.Target<Bitmap>?, isFirstResource: Boolean) = false
override fun onResourceReady(resource: Bitmap?, model: Any?, target: com.bumptech.glide.request.target.Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
if (isFragmentVisible) {
scheduleZoomableView()
}
return false
}
}).into(view.photo_view)
}
private fun openPanorama() {
Intent(context, PanoramaActivity::class.java).apply {
putExtra(PATH, medium.path)
startActivity(this)
}
}
private fun scheduleZoomableView() {
loadZoomableViewHandler.removeCallbacksAndMessages(null)
loadZoomableViewHandler.postDelayed({
if (isFragmentVisible && context?.config?.allowZoomingImages == true && medium.isImage() && view.subsampling_view.isGone()) {
addZoomableView()
}
}, ZOOMABLE_VIEW_LOAD_DELAY)
}
private fun addZoomableView() {
val rotation = degreesForRotation(imageOrientation)
view.subsampling_view.apply {
setMinimumTileDpi(getMinTileDpi())
background = ColorDrawable(Color.TRANSPARENT)
setBitmapDecoderFactory { PicassoDecoder(medium.path, Picasso.get(), rotation) }
setRegionDecoderFactory { PicassoRegionDecoder() }
maxScale = 10f
beVisible()
isQuickScaleEnabled = context.config.oneFingerZoom
setResetScaleOnSizeChange(context.config.screenRotation != ROTATE_BY_ASPECT_RATIO)
setImage(ImageSource.uri(getPathToLoad(medium)))
orientation = rotation
setEagerLoadingEnabled(false)
setOnImageEventListener(object : SubsamplingScaleImageView.OnImageEventListener {
override fun onImageLoaded() {
}
override fun onReady() {
background = ColorDrawable(if (context.config.blackBackground) Color.BLACK else context.config.backgroundColor)
val useWidth = if (imageOrientation == ORIENTATION_ROTATE_90 || imageOrientation == ORIENTATION_ROTATE_270) sHeight else sWidth
val useHeight = if (imageOrientation == ORIENTATION_ROTATE_90 || imageOrientation == ORIENTATION_ROTATE_270) sWidth else sHeight
setDoubleTapZoomScale(getDoubleTapZoomScale(useWidth, useHeight))
}
override fun onTileLoadError(e: Exception?) {
}
override fun onPreviewReleased() {
}
override fun onImageLoadError(e: Exception) {
view.photo_view.isZoomable = true
background = ColorDrawable(Color.TRANSPARENT)
beGone()
}
override fun onPreviewLoadError(e: Exception?) {
background = ColorDrawable(Color.TRANSPARENT)
beGone()
}
})
}
}
private fun getMinTileDpi(): Int {
val metrics = resources.displayMetrics
val averageDpi = (metrics.xdpi + metrics.ydpi) / 2
return when {
averageDpi > 400 -> 320
averageDpi > 300 -> 240
else -> 160
}
}
private fun checkIfPanorama() {
isPanorama = try {
val inputStream = if (medium.path.startsWith("content:/")) context!!.contentResolver.openInputStream(Uri.parse(medium.path)) else File(medium.path).inputStream()
val imageParser = JpegImageParser().getXmpXml(ByteSourceInputStream(inputStream, medium.name), HashMap<String, Any>())
imageParser.contains("GPano:UsePanoramaViewer=\"True\"", true) || imageParser.contains("<GPano:UsePanoramaViewer>True</GPano:UsePanoramaViewer>", true)
} catch (e: Exception) {
false
} catch (e: OutOfMemoryError) {
false
}
view.panorama_outline.beVisibleIf(isPanorama && isLollipopPlus())
}
private fun getImageOrientation(): Int {
val defaultOrientation = -1
var orient = defaultOrientation
try {
val pathToLoad = getPathToLoad(medium)
val exif = android.media.ExifInterface(pathToLoad)
orient = exif.getAttributeInt(android.media.ExifInterface.TAG_ORIENTATION, defaultOrientation)
if (orient == defaultOrientation || medium.path.startsWith(OTG_PATH)) {
val uri = if (pathToLoad.startsWith("content:/")) Uri.parse(pathToLoad) else Uri.fromFile(File(pathToLoad))
val inputStream = context!!.contentResolver.openInputStream(uri)
val exif2 = ExifInterface()
exif2.readExif(inputStream, ExifInterface.Options.OPTION_ALL)
orient = exif2.getTag(ExifInterface.TAG_ORIENTATION)?.getValueAsInt(defaultOrientation) ?: defaultOrientation
}
} catch (ignored: Exception) {
} catch (ignored: OutOfMemoryError) {
}
return orient
}
private fun getDoubleTapZoomScale(width: Int, height: Int): Float {
val bitmapAspectRatio = height / width.toFloat()
val screenAspectRatio = ViewPagerActivity.screenHeight / ViewPagerActivity.screenWidth.toFloat()
return if (context == null || bitmapAspectRatio == screenAspectRatio) {
DEFAULT_DOUBLE_TAP_ZOOM
} else if (context!!.portrait && bitmapAspectRatio <= screenAspectRatio) {
ViewPagerActivity.screenHeight / height.toFloat()
} else if (context!!.portrait && bitmapAspectRatio > screenAspectRatio) {
ViewPagerActivity.screenWidth / width.toFloat()
} else if (!context!!.portrait && bitmapAspectRatio >= screenAspectRatio) {
ViewPagerActivity.screenWidth / width.toFloat()
} else if (!context!!.portrait && bitmapAspectRatio < screenAspectRatio) {
ViewPagerActivity.screenHeight / height.toFloat()
} else {
DEFAULT_DOUBLE_TAP_ZOOM
}
}
fun rotateImageViewBy(degrees: Int) {
loadZoomableViewHandler.removeCallbacksAndMessages(null)
view.subsampling_view.beGone()
loadBitmap(degrees)
}
private fun initExtendedDetails() {
if (context!!.config.showExtendedDetails) {
view.photo_details.apply {
beInvisible() // make it invisible so we can measure it, but not show yet
text = getMediumExtendedDetails(medium)
onGlobalLayout {
if (isAdded) {
val realY = getExtendedDetailsY(height)
if (realY > 0) {
y = realY
beVisibleIf(text.isNotEmpty())
alpha = if (!context!!.config.hideExtendedDetails || !isFullscreen) 1f else 0f
}
}
}
}
} else {
view.photo_details.beGone()
}
}
override fun onDestroyView() {
super.onDestroyView()
if (activity?.isActivityDestroyed() == false) {
view.subsampling_view.recycle()
}
loadZoomableViewHandler.removeCallbacksAndMessages(null)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
loadImage()
initExtendedDetails()
}
private fun photoClicked() {
listener?.fragmentClicked()
}
override fun fullscreenToggled(isFullscreen: Boolean) {
this.isFullscreen = isFullscreen
view.photo_details.apply {
if (storedShowExtendedDetails && isVisible()) {
animate().y(getExtendedDetailsY(height))
if (storedHideExtendedDetails) {
animate().alpha(if (isFullscreen) 0f else 1f).start()
}
}
}
}
private fun getExtendedDetailsY(height: Int): Float {
val smallMargin = resources.getDimension(R.dimen.small_margin)
val fullscreenOffset = context!!.navigationBarHeight.toFloat() - smallMargin
val actionsHeight = if (context!!.config.bottomActions && !isFullscreen) resources.getDimension(R.dimen.bottom_actions_height) else 0f
return context!!.usableScreenSize.y - height - actionsHeight + if (isFullscreen) fullscreenOffset else -smallMargin
}
}

View file

@ -1,71 +0,0 @@
package com.simplemobiletools.gallery.fragments
import android.support.v4.app.Fragment
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.models.Medium
import java.io.File
abstract class ViewPagerFragment : Fragment() {
var listener: FragmentListener? = null
abstract fun fullscreenToggled(isFullscreen: Boolean)
interface FragmentListener {
fun fragmentClicked()
fun videoEnded(): Boolean
fun goToPrevItem()
fun goToNextItem()
}
fun getMediumExtendedDetails(medium: Medium): String {
val file = File(medium.path)
if (!file.exists()) {
return ""
}
val path = "${file.parent.trimEnd('/')}/"
val exif = android.media.ExifInterface(medium.path)
val details = StringBuilder()
val detailsFlag = context!!.config.extendedDetails
if (detailsFlag and EXT_NAME != 0) {
medium.name.let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_PATH != 0) {
path.let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_SIZE != 0) {
file.length().formatSize().let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_RESOLUTION != 0) {
file.absolutePath.getResolution()?.formatAsResolution().let { if (it?.isNotEmpty() == true) details.appendln(it) }
}
if (detailsFlag and EXT_LAST_MODIFIED != 0) {
file.lastModified().formatDate().let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_DATE_TAKEN != 0) {
path.getExifDateTaken(exif).let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_CAMERA_MODEL != 0) {
path.getExifCameraModel(exif).let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_EXIF_PROPERTIES != 0) {
path.getExifProperties(exif).let { if (it.isNotEmpty()) details.appendln(it) }
}
return details.toString().trim()
}
fun getPathToLoad(medium: Medium) = if (medium.path.startsWith(OTG_PATH)) medium.path.getOTGPublicPath(context!!) else medium.path
}

View file

@ -1,9 +0,0 @@
package com.simplemobiletools.gallery.interfaces
import com.simplemobiletools.gallery.models.FilterItem
interface FilterAdapterListener {
fun getCurrentFilter(): FilterItem
fun setCurrentFilter(position: Int)
}

View file

@ -1,3 +0,0 @@
package com.simplemobiletools.gallery.models
open class ThumbnailItem

View file

@ -1,6 +1,6 @@
package com.simplemobiletools.gallery package com.simplemobiletools.gallery.pro
import android.support.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import com.github.ajalt.reprint.core.Reprint import com.github.ajalt.reprint.core.Reprint
import com.simplemobiletools.commons.extensions.checkUseEnglish import com.simplemobiletools.commons.extensions.checkUseEnglish

View file

@ -1,38 +1,46 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.annotation.TargetApi
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat import android.graphics.Bitmap.CompressFormat
import android.graphics.Color import android.graphics.Color
import android.graphics.Point import android.graphics.Point
import android.media.ExifInterface
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.simplemobiletools.commons.dialogs.ColorPickerDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.commons.helpers.REAL_FILE_PATH import com.simplemobiletools.commons.helpers.REAL_FILE_PATH
import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.adapters.FiltersAdapter import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.dialogs.ResizeDialog import com.simplemobiletools.gallery.pro.adapters.FiltersAdapter
import com.simplemobiletools.gallery.dialogs.SaveAsDialog import com.simplemobiletools.gallery.pro.dialogs.OtherAspectRatioDialog
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.dialogs.ResizeDialog
import com.simplemobiletools.gallery.extensions.openEditor import com.simplemobiletools.gallery.pro.dialogs.SaveAsDialog
import com.simplemobiletools.gallery.helpers.FilterThumbnailsManager import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.models.FilterItem import com.simplemobiletools.gallery.pro.extensions.openEditor
import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.models.FilterItem
import com.theartofdev.edmodo.cropper.CropImageView import com.theartofdev.edmodo.cropper.CropImageView
import com.zomato.photofilters.FilterPack import com.zomato.photofilters.FilterPack
import com.zomato.photofilters.imageprocessors.Filter import com.zomato.photofilters.imageprocessors.Filter
@ -40,6 +48,7 @@ import kotlinx.android.synthetic.main.activity_edit.*
import kotlinx.android.synthetic.main.bottom_actions_aspect_ratio.* import kotlinx.android.synthetic.main.bottom_actions_aspect_ratio.*
import kotlinx.android.synthetic.main.bottom_editor_actions_filter.* import kotlinx.android.synthetic.main.bottom_editor_actions_filter.*
import kotlinx.android.synthetic.main.bottom_editor_crop_rotate_actions.* import kotlinx.android.synthetic.main.bottom_editor_crop_rotate_actions.*
import kotlinx.android.synthetic.main.bottom_editor_draw_actions.*
import kotlinx.android.synthetic.main.bottom_editor_primary_actions.* import kotlinx.android.synthetic.main.bottom_editor_primary_actions.*
import java.io.* import java.io.*
@ -50,19 +59,16 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} }
} }
private val TEMP_FOLDER_NAME = "images"
private val ASPECT_X = "aspectX" private val ASPECT_X = "aspectX"
private val ASPECT_Y = "aspectY" private val ASPECT_Y = "aspectY"
private val CROP = "crop" private val CROP = "crop"
private val ASPECT_RATIO_FREE = 0
private val ASPECT_RATIO_ONE_ONE = 1
private val ASPECT_RATIO_FOUR_THREE = 2
private val ASPECT_RATIO_SIXTEEN_NINE = 3
// constants for bottom primary action groups // constants for bottom primary action groups
private val PRIMARY_ACTION_NONE = 0 private val PRIMARY_ACTION_NONE = 0
private val PRIMARY_ACTION_FILTER = 1 private val PRIMARY_ACTION_FILTER = 1
private val PRIMARY_ACTION_CROP_ROTATE = 2 private val PRIMARY_ACTION_CROP_ROTATE = 2
private val PRIMARY_ACTION_DRAW = 3
private val CROP_ROTATE_NONE = 0 private val CROP_ROTATE_NONE = 0
private val CROP_ROTATE_ASPECT_RATIO = 1 private val CROP_ROTATE_ASPECT_RATIO = 1
@ -71,13 +77,17 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
private lateinit var saveUri: Uri private lateinit var saveUri: Uri
private var resizeWidth = 0 private var resizeWidth = 0
private var resizeHeight = 0 private var resizeHeight = 0
private var drawColor = 0
private var lastOtherAspectRatio: Pair<Int, Int>? = null
private var currPrimaryAction = PRIMARY_ACTION_NONE private var currPrimaryAction = PRIMARY_ACTION_NONE
private var currCropRotateAction = CROP_ROTATE_NONE private var currCropRotateAction = CROP_ROTATE_NONE
private var currAspectRatio = ASPECT_RATIO_FREE private var currAspectRatio = ASPECT_RATIO_FREE
private var isCropIntent = false private var isCropIntent = false
private var isEditingWithThirdParty = false private var isEditingWithThirdParty = false
private var isSharingBitmap = false
private var initialBitmap: Bitmap? = null private var wasDrawCanvasPositioned = false
private var oldExif: ExifInterface? = null
private var filterInitialBitmap: Bitmap? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -93,6 +103,34 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} }
} }
override fun onResume() {
super.onResume()
isEditingWithThirdParty = false
bottom_draw_width.setColors(config.textColor, getAdjustedPrimaryColor(), config.backgroundColor)
}
override fun onStop() {
super.onStop()
if (isEditingWithThirdParty) {
finish()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_editor, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save_as -> saveImage()
R.id.edit -> editWith()
R.id.share -> shareImage()
else -> return super.onOptionsItemSelected(item)
}
return true
}
private fun initEditActivity() { private fun initEditActivity() {
if (intent.data == null) { if (intent.data == null) {
toast(R.string.invalid_image_path) toast(R.string.invalid_image_path)
@ -133,37 +171,25 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
loadDefaultImageView() loadDefaultImageView()
setupBottomActions() setupBottomActions()
}
override fun onResume() { if (config.lastEditorCropAspectRatio == ASPECT_RATIO_OTHER) {
super.onResume() if (config.lastEditorCropOtherAspectRatioX == 0) {
isEditingWithThirdParty = false config.lastEditorCropOtherAspectRatioX = 1
} }
override fun onStop() { if (config.lastEditorCropOtherAspectRatioY == 0) {
super.onStop() config.lastEditorCropOtherAspectRatioY = 1
if (isEditingWithThirdParty) { }
finish()
lastOtherAspectRatio = Pair(config.lastEditorCropOtherAspectRatioX, config.lastEditorCropOtherAspectRatioY)
} }
} updateAspectRatio(config.lastEditorCropAspectRatio)
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_editor, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save_as -> saveImage()
R.id.edit -> editWith()
else -> return super.onOptionsItemSelected(item)
}
return true
} }
private fun loadDefaultImageView() { private fun loadDefaultImageView() {
default_image_view.beVisible() default_image_view.beVisible()
crop_image_view.beGone() crop_image_view.beGone()
editor_draw_canvas.beGone()
val options = RequestOptions() val options = RequestOptions()
.skipMemoryCache(true) .skipMemoryCache(true)
@ -178,21 +204,22 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
override fun onResourceReady(bitmap: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { override fun onResourceReady(bitmap: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
val currentFilter = getFiltersAdapter()?.getCurrentFilter() val currentFilter = getFiltersAdapter()?.getCurrentFilter()
if (initialBitmap == null) { if (filterInitialBitmap == null) {
loadCropImageView() loadCropImageView()
bottomCropRotateClicked() bottomCropRotateClicked()
} }
if (initialBitmap != null && currentFilter != null && currentFilter.filter.name != getString(R.string.none)) { if (filterInitialBitmap != null && currentFilter != null && currentFilter.filter.name != getString(R.string.none)) {
default_image_view.onGlobalLayout { default_image_view.onGlobalLayout {
applyFilter(currentFilter) applyFilter(currentFilter)
} }
} else { } else {
initialBitmap = bitmap filterInitialBitmap = bitmap
} }
if (isCropIntent) { if (isCropIntent) {
bottom_primary_filter.beGone() bottom_primary_filter.beGone()
bottom_primary_draw.beGone()
} }
return false return false
@ -202,6 +229,7 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
private fun loadCropImageView() { private fun loadCropImageView() {
default_image_view.beGone() default_image_view.beGone()
editor_draw_canvas.beGone()
crop_image_view.apply { crop_image_view.apply {
beVisible() beVisible()
setOnCropImageCompleteListener(this@EditActivity) setOnCropImageCompleteListener(this@EditActivity)
@ -216,9 +244,79 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} }
} }
private fun loadDrawCanvas() {
default_image_view.beGone()
crop_image_view.beGone()
editor_draw_canvas.beVisible()
if (!wasDrawCanvasPositioned) {
wasDrawCanvasPositioned = true
editor_draw_canvas.onGlobalLayout {
Thread {
fillCanvasBackground()
}.start()
}
}
}
private fun fillCanvasBackground() {
val size = Point()
windowManager.defaultDisplay.getSize(size)
val options = RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.fitCenter()
try {
val builder = Glide.with(applicationContext)
.asBitmap()
.load(uri)
.apply(options)
.into(editor_draw_canvas.width, editor_draw_canvas.height)
val bitmap = builder.get()
runOnUiThread {
editor_draw_canvas.apply {
updateBackgroundBitmap(bitmap)
layoutParams.width = bitmap.width
layoutParams.height = bitmap.height
y = (height - bitmap.height) / 2f
requestLayout()
}
}
} catch (e: Exception) {
showErrorToast(e)
}
}
@TargetApi(Build.VERSION_CODES.N)
private fun saveImage() { private fun saveImage() {
var inputStream: InputStream? = null
try {
if (isNougatPlus()) {
inputStream = contentResolver.openInputStream(uri)
oldExif = ExifInterface(inputStream)
}
} catch (e: Exception) {
} finally {
inputStream?.close()
}
if (crop_image_view.isVisible()) { if (crop_image_view.isVisible()) {
crop_image_view.getCroppedImageAsync() crop_image_view.getCroppedImageAsync()
} else if (editor_draw_canvas.isVisible()) {
val bitmap = editor_draw_canvas.getBitmap()
if (saveUri.scheme == "file") {
SaveAsDialog(this, saveUri.path, true) {
saveBitmapToFile(bitmap, it, true)
}
} else if (saveUri.scheme == "content") {
val filePathGetter = getNewFilePath()
SaveAsDialog(this, filePathGetter.first, filePathGetter.second) {
saveBitmapToFile(bitmap, it, true)
}
}
} else { } else {
val currentFilter = getFiltersAdapter()?.getCurrentFilter() ?: return val currentFilter = getFiltersAdapter()?.getCurrentFilter() ?: return
val filePathGetter = getNewFilePath() val filePathGetter = getNewFilePath()
@ -244,12 +342,78 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} }
} }
private fun shareImage() {
Thread {
when {
default_image_view.isVisible() -> {
val currentFilter = getFiltersAdapter()?.getCurrentFilter()
if (currentFilter == null) {
toast(R.string.unknown_error_occurred)
return@Thread
}
val originalBitmap = Glide.with(applicationContext).asBitmap().load(uri).submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get()
currentFilter!!.filter.processFilter(originalBitmap)
shareBitmap(originalBitmap)
}
crop_image_view.isVisible() -> {
isSharingBitmap = true
runOnUiThread {
crop_image_view.getCroppedImageAsync()
}
}
editor_draw_canvas.isVisible() -> shareBitmap(editor_draw_canvas.getBitmap())
}
}.start()
}
private fun getTempImagePath(bitmap: Bitmap, callback: (path: String?) -> Unit) {
val bytes = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bytes)
val folder = File(cacheDir, TEMP_FOLDER_NAME)
if (!folder.exists()) {
if (!folder.mkdir()) {
callback(null)
return
}
}
val filename = applicationContext.getFilenameFromContentUri(saveUri) ?: "tmp.jpg"
val newPath = "$folder/$filename"
val fileDirItem = FileDirItem(newPath, filename)
getFileOutputStream(fileDirItem, true) {
if (it != null) {
try {
it.write(bytes.toByteArray())
callback(newPath)
} catch (e: Exception) {
} finally {
it.close()
}
} else {
callback("")
}
}
}
private fun shareBitmap(bitmap: Bitmap) {
getTempImagePath(bitmap) {
if (it != null) {
sharePathIntent(it, BuildConfig.APPLICATION_ID)
} else {
toast(R.string.unknown_error_occurred)
}
}
}
private fun getFiltersAdapter() = bottom_actions_filter_list.adapter as? FiltersAdapter private fun getFiltersAdapter() = bottom_actions_filter_list.adapter as? FiltersAdapter
private fun setupBottomActions() { private fun setupBottomActions() {
setupPrimaryActionButtons() setupPrimaryActionButtons()
setupCropRotateActionButtons() setupCropRotateActionButtons()
setupAspectRatioButtons() setupAspectRatioButtons()
setupDrawButtons()
} }
private fun setupPrimaryActionButtons() { private fun setupPrimaryActionButtons() {
@ -260,6 +424,10 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
bottom_primary_crop_rotate.setOnClickListener { bottom_primary_crop_rotate.setOnClickListener {
bottomCropRotateClicked() bottomCropRotateClicked()
} }
bottom_primary_draw.setOnClickListener {
bottomDrawClicked()
}
} }
private fun bottomFilterClicked() { private fun bottomFilterClicked() {
@ -280,6 +448,15 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
updatePrimaryActionButtons() updatePrimaryActionButtons()
} }
private fun bottomDrawClicked() {
currPrimaryAction = if (currPrimaryAction == PRIMARY_ACTION_DRAW) {
PRIMARY_ACTION_NONE
} else {
PRIMARY_ACTION_DRAW
}
updatePrimaryActionButtons()
}
private fun setupCropRotateActionButtons() { private fun setupCropRotateActionButtons() {
bottom_rotate.setOnClickListener { bottom_rotate.setOnClickListener {
crop_image_view.rotateImage(90) crop_image_view.rotateImage(90)
@ -328,34 +505,83 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
bottom_aspect_ratio_sixteen_nine.setOnClickListener { bottom_aspect_ratio_sixteen_nine.setOnClickListener {
updateAspectRatio(ASPECT_RATIO_SIXTEEN_NINE) updateAspectRatio(ASPECT_RATIO_SIXTEEN_NINE)
} }
bottom_aspect_ratio_other.setOnClickListener {
OtherAspectRatioDialog(this, lastOtherAspectRatio) {
lastOtherAspectRatio = it
config.lastEditorCropOtherAspectRatioX = it.first
config.lastEditorCropOtherAspectRatioY = it.second
updateAspectRatio(ASPECT_RATIO_OTHER)
}
}
updateAspectRatioButtons() updateAspectRatioButtons()
} }
private fun setupDrawButtons() {
updateDrawColor(config.lastEditorDrawColor)
bottom_draw_width.progress = config.lastEditorBrushSize
updateBrushSize(config.lastEditorBrushSize)
bottom_draw_color_clickable.setOnClickListener {
ColorPickerDialog(this, drawColor) { wasPositivePressed, color ->
if (wasPositivePressed) {
updateDrawColor(color)
}
}
}
bottom_draw_width.onSeekBarChangeListener {
config.lastEditorBrushSize = it
updateBrushSize(it)
}
bottom_draw_undo.setOnClickListener {
editor_draw_canvas.undo()
}
}
private fun updateBrushSize(percent: Int) {
editor_draw_canvas.updateBrushSize(percent)
val scale = Math.max(0.03f, percent / 100f)
bottom_draw_color.scaleX = scale
bottom_draw_color.scaleY = scale
}
private fun updatePrimaryActionButtons() { private fun updatePrimaryActionButtons() {
if (crop_image_view.isGone() && currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE) { if (crop_image_view.isGone() && currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE) {
loadCropImageView() loadCropImageView()
} else if (default_image_view.isGone() && currPrimaryAction == PRIMARY_ACTION_FILTER) { } else if (default_image_view.isGone() && currPrimaryAction == PRIMARY_ACTION_FILTER) {
loadDefaultImageView() loadDefaultImageView()
} else if (editor_draw_canvas.isGone() && currPrimaryAction == PRIMARY_ACTION_DRAW) {
loadDrawCanvas()
} }
arrayOf(bottom_primary_filter, bottom_primary_crop_rotate).forEach { arrayOf(bottom_primary_filter, bottom_primary_crop_rotate, bottom_primary_draw).forEach {
it.applyColorFilter(Color.WHITE) it.applyColorFilter(Color.WHITE)
} }
val currentPrimaryActionButton = when (currPrimaryAction) { val currentPrimaryActionButton = when (currPrimaryAction) {
PRIMARY_ACTION_FILTER -> bottom_primary_filter PRIMARY_ACTION_FILTER -> bottom_primary_filter
PRIMARY_ACTION_CROP_ROTATE -> bottom_primary_crop_rotate PRIMARY_ACTION_CROP_ROTATE -> bottom_primary_crop_rotate
PRIMARY_ACTION_DRAW -> bottom_primary_draw
else -> null else -> null
} }
currentPrimaryActionButton?.applyColorFilter(config.primaryColor) currentPrimaryActionButton?.applyColorFilter(getAdjustedPrimaryColor())
bottom_editor_filter_actions.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_FILTER) bottom_editor_filter_actions.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_FILTER)
bottom_editor_crop_rotate_actions.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE) bottom_editor_crop_rotate_actions.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE)
bottom_editor_draw_actions.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_DRAW)
if (currPrimaryAction == PRIMARY_ACTION_FILTER && bottom_actions_filter_list.adapter == null) { if (currPrimaryAction == PRIMARY_ACTION_FILTER && bottom_actions_filter_list.adapter == null) {
Thread { Thread {
val thumbnailSize = resources.getDimension(R.dimen.bottom_filters_thumbnail_size).toInt() val thumbnailSize = resources.getDimension(R.dimen.bottom_filters_thumbnail_size).toInt()
val bitmap = Glide.with(this).asBitmap().load(uri).submit(thumbnailSize, thumbnailSize).get() val bitmap = Glide.with(this)
.asBitmap()
.load(uri)
.submit(thumbnailSize, thumbnailSize)
.get()
runOnUiThread { runOnUiThread {
val filterThumbnailsManager = FilterThumbnailsManager() val filterThumbnailsManager = FilterThumbnailsManager()
filterThumbnailsManager.clearThumbs() filterThumbnailsManager.clearThumbs()
@ -394,12 +620,13 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} }
private fun applyFilter(filterItem: FilterItem) { private fun applyFilter(filterItem: FilterItem) {
val newBitmap = Bitmap.createBitmap(initialBitmap) val newBitmap = Bitmap.createBitmap(filterInitialBitmap)
default_image_view.setImageBitmap(filterItem.filter.processFilter(newBitmap)) default_image_view.setImageBitmap(filterItem.filter.processFilter(newBitmap))
} }
private fun updateAspectRatio(aspectRatio: Int) { private fun updateAspectRatio(aspectRatio: Int) {
currAspectRatio = aspectRatio currAspectRatio = aspectRatio
config.lastEditorCropAspectRatio = aspectRatio
updateAspectRatioButtons() updateAspectRatioButtons()
crop_image_view.apply { crop_image_view.apply {
@ -409,7 +636,8 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
val newAspectRatio = when (aspectRatio) { val newAspectRatio = when (aspectRatio) {
ASPECT_RATIO_ONE_ONE -> Pair(1, 1) ASPECT_RATIO_ONE_ONE -> Pair(1, 1)
ASPECT_RATIO_FOUR_THREE -> Pair(4, 3) ASPECT_RATIO_FOUR_THREE -> Pair(4, 3)
else -> Pair(16, 9) ASPECT_RATIO_SIXTEEN_NINE -> Pair(16, 9)
else -> Pair(lastOtherAspectRatio!!.first, lastOtherAspectRatio!!.second)
} }
setAspectRatio(newAspectRatio.first, newAspectRatio.second) setAspectRatio(newAspectRatio.first, newAspectRatio.second)
@ -418,7 +646,7 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} }
private fun updateAspectRatioButtons() { private fun updateAspectRatioButtons() {
arrayOf(bottom_aspect_ratio_free, bottom_aspect_ratio_one_one, bottom_aspect_ratio_four_three, bottom_aspect_ratio_sixteen_nine).forEach { arrayOf(bottom_aspect_ratio_free, bottom_aspect_ratio_one_one, bottom_aspect_ratio_four_three, bottom_aspect_ratio_sixteen_nine, bottom_aspect_ratio_other).forEach {
it.setTextColor(Color.WHITE) it.setTextColor(Color.WHITE)
} }
@ -426,10 +654,11 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
ASPECT_RATIO_FREE -> bottom_aspect_ratio_free ASPECT_RATIO_FREE -> bottom_aspect_ratio_free
ASPECT_RATIO_ONE_ONE -> bottom_aspect_ratio_one_one ASPECT_RATIO_ONE_ONE -> bottom_aspect_ratio_one_one
ASPECT_RATIO_FOUR_THREE -> bottom_aspect_ratio_four_three ASPECT_RATIO_FOUR_THREE -> bottom_aspect_ratio_four_three
else -> bottom_aspect_ratio_sixteen_nine ASPECT_RATIO_SIXTEEN_NINE -> bottom_aspect_ratio_sixteen_nine
else -> bottom_aspect_ratio_other
} }
currentAspectRatioButton.setTextColor(config.primaryColor) currentAspectRatioButton.setTextColor(getAdjustedPrimaryColor())
} }
private fun updateCropRotateActionButtons() { private fun updateCropRotateActionButtons() {
@ -442,7 +671,14 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
else -> null else -> null
} }
primaryActionView?.applyColorFilter(config.primaryColor) primaryActionView?.applyColorFilter(getAdjustedPrimaryColor())
}
private fun updateDrawColor(color: Int) {
drawColor = color
bottom_draw_color.applyColorFilter(color)
config.lastEditorDrawColor = color
editor_draw_canvas.updateColor(color)
} }
private fun resizeImage() { private fun resizeImage() {
@ -480,15 +716,22 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
override fun onCropImageComplete(view: CropImageView, result: CropImageView.CropResult) { override fun onCropImageComplete(view: CropImageView, result: CropImageView.CropResult) {
if (result.error == null) { if (result.error == null) {
val bitmap = result.bitmap
if (isSharingBitmap) {
isSharingBitmap = false
shareBitmap(bitmap)
return
}
if (isCropIntent) { if (isCropIntent) {
if (saveUri.scheme == "file") { if (saveUri.scheme == "file") {
saveBitmapToFile(result.bitmap, saveUri.path, true) saveBitmapToFile(bitmap, saveUri.path, true)
} else { } else {
var inputStream: InputStream? = null var inputStream: InputStream? = null
var outputStream: OutputStream? = null var outputStream: OutputStream? = null
try { try {
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
result.bitmap.compress(CompressFormat.JPEG, 100, stream) bitmap.compress(CompressFormat.JPEG, 100, stream)
inputStream = ByteArrayInputStream(stream.toByteArray()) inputStream = ByteArrayInputStream(stream.toByteArray())
outputStream = contentResolver.openOutputStream(saveUri) outputStream = contentResolver.openOutputStream(saveUri)
inputStream.copyTo(outputStream) inputStream.copyTo(outputStream)
@ -506,12 +749,12 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} }
} else if (saveUri.scheme == "file") { } else if (saveUri.scheme == "file") {
SaveAsDialog(this, saveUri.path, true) { SaveAsDialog(this, saveUri.path, true) {
saveBitmapToFile(result.bitmap, it, true) saveBitmapToFile(bitmap, it, true)
} }
} else if (saveUri.scheme == "content") { } else if (saveUri.scheme == "content") {
val filePathGetter = getNewFilePath() val filePathGetter = getNewFilePath()
SaveAsDialog(this, filePathGetter.first, filePathGetter.second) { SaveAsDialog(this, filePathGetter.first, filePathGetter.second) {
saveBitmapToFile(result.bitmap, it, true) saveBitmapToFile(bitmap, it, true)
} }
} else { } else {
toast(R.string.unknown_file_location) toast(R.string.unknown_file_location)
@ -561,6 +804,7 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} }
} }
@TargetApi(Build.VERSION_CODES.N)
private fun saveBitmap(file: File, bitmap: Bitmap, out: OutputStream, showSavingToast: Boolean) { private fun saveBitmap(file: File, bitmap: Bitmap, out: OutputStream, showSavingToast: Boolean) {
if (showSavingToast) { if (showSavingToast) {
toast(R.string.saving) toast(R.string.saving)
@ -572,13 +816,22 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} else { } else {
bitmap.compress(file.absolutePath.getCompressionFormat(), 90, out) bitmap.compress(file.absolutePath.getCompressionFormat(), 90, out)
} }
try {
if (isNougatPlus()) {
val newExif = ExifInterface(file.absolutePath)
oldExif?.copyTo(newExif, false)
}
} catch (e: Exception) {
}
setResult(Activity.RESULT_OK, intent) setResult(Activity.RESULT_OK, intent)
scanFinalPath(file.absolutePath) scanFinalPath(file.absolutePath)
out.close() out.close()
} }
private fun editWith() { private fun editWith() {
openEditor(uri.toString()) openEditor(uri.toString(), true)
isEditingWithThirdParty = true isEditingWithThirdParty = true
} }

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
@ -6,9 +6,9 @@ import android.view.MenuItem
import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.adapters.ManageFoldersAdapter import com.simplemobiletools.gallery.pro.adapters.ManageFoldersAdapter
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import kotlinx.android.synthetic.main.activity_manage_folders.* import kotlinx.android.synthetic.main.activity_manage_folders.*
class ExcludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener { class ExcludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
@ -20,7 +20,7 @@ class ExcludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
private fun updateFolders() { private fun updateFolders() {
val folders = ArrayList<String>() val folders = ArrayList<String>()
config.excludedFolders.mapTo(folders, { it }) config.excludedFolders.mapTo(folders) { it }
manage_folders_placeholder.apply { manage_folders_placeholder.apply {
text = getString(R.string.excluded_activity_placeholder) text = getString(R.string.excluded_activity_placeholder)
beVisibleIf(folders.isEmpty()) beVisibleIf(folders.isEmpty())
@ -49,7 +49,7 @@ class ExcludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
} }
private fun addFolder() { private fun addFolder() {
FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden) { FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden, false, true) {
config.lastFilepickerPath = it config.lastFilepickerPath = it
config.addExcludedFolder(it) config.addExcludedFolder(it)
updateFolders() updateFolders()

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
@ -6,11 +6,11 @@ import android.view.MenuItem
import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.adapters.ManageHiddenFoldersAdapter import com.simplemobiletools.gallery.pro.adapters.ManageHiddenFoldersAdapter
import com.simplemobiletools.gallery.extensions.addNoMedia import com.simplemobiletools.gallery.pro.extensions.addNoMedia
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.extensions.getNoMediaFolders import com.simplemobiletools.gallery.pro.extensions.getNoMediaFolders
import kotlinx.android.synthetic.main.activity_manage_folders.* import kotlinx.android.synthetic.main.activity_manage_folders.*
class HiddenFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener { class HiddenFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
@ -53,7 +53,7 @@ class HiddenFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
} }
private fun addFolder() { private fun addFolder() {
FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden) { FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden, false, true) {
config.lastFilepickerPath = it config.lastFilepickerPath = it
Thread { Thread {
addNoMedia(it) { addNoMedia(it) {

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
@ -7,9 +7,9 @@ import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.scanPathRecursively import com.simplemobiletools.commons.extensions.scanPathRecursively
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.adapters.ManageFoldersAdapter import com.simplemobiletools.gallery.pro.adapters.ManageFoldersAdapter
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import kotlinx.android.synthetic.main.activity_manage_folders.* import kotlinx.android.synthetic.main.activity_manage_folders.*
class IncludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener { class IncludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
@ -21,7 +21,7 @@ class IncludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
private fun updateFolders() { private fun updateFolders() {
val folders = ArrayList<String>() val folders = ArrayList<String>()
config.includedFolders.mapTo(folders, { it }) config.includedFolders.mapTo(folders) { it }
manage_folders_placeholder.apply { manage_folders_placeholder.apply {
text = getString(R.string.included_activity_placeholder) text = getString(R.string.included_activity_placeholder)
beVisibleIf(folders.isEmpty()) beVisibleIf(folders.isEmpty())
@ -50,7 +50,7 @@ class IncludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
} }
private fun addFolder() { private fun addFolder() {
FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden) { FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden, false, true) {
config.lastFilepickerPath = it config.lastFilepickerPath = it
config.addIncludedFolder(it) config.addIncludedFolder(it)
updateFolders() updateFolders()

View file

@ -1,42 +1,46 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.app.Activity import android.app.Activity
import android.app.SearchManager
import android.content.ClipData import android.content.ClipData
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.provider.MediaStore import android.provider.MediaStore
import android.support.v7.widget.GridLayoutManager
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuItemCompat
import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.CreateNewFolderDialog import com.simplemobiletools.commons.dialogs.CreateNewFolderDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.models.Release import com.simplemobiletools.commons.models.Release
import com.simplemobiletools.commons.views.MyGridLayoutManager import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.BuildConfig import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.adapters.DirectoryAdapter import com.simplemobiletools.gallery.pro.adapters.DirectoryAdapter
import com.simplemobiletools.gallery.databases.GalleryDatabase import com.simplemobiletools.gallery.pro.databases.GalleryDatabase
import com.simplemobiletools.gallery.dialogs.ChangeSortingDialog import com.simplemobiletools.gallery.pro.dialogs.ChangeSortingDialog
import com.simplemobiletools.gallery.dialogs.FilterMediaDialog import com.simplemobiletools.gallery.pro.dialogs.ChangeViewTypeDialog
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.pro.dialogs.FilterMediaDialog
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.interfaces.DirectoryDao import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.interfaces.DirectoryOperationsListener import com.simplemobiletools.gallery.pro.interfaces.DirectoryDao
import com.simplemobiletools.gallery.interfaces.MediumDao import com.simplemobiletools.gallery.pro.interfaces.DirectoryOperationsListener
import com.simplemobiletools.gallery.models.AlbumCover import com.simplemobiletools.gallery.pro.interfaces.MediumDao
import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.pro.models.AlbumCover
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.models.Directory
import com.simplemobiletools.gallery.pro.models.Medium
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import java.io.* import java.io.*
import java.util.* import java.util.*
@ -60,11 +64,16 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
private var mIsPasswordProtectionPending = false private var mIsPasswordProtectionPending = false
private var mWasProtectionHandled = false private var mWasProtectionHandled = false
private var mShouldStopFetching = false private var mShouldStopFetching = false
private var mIsSearchOpen = false
private var mLatestMediaId = 0L private var mLatestMediaId = 0L
private var mLatestMediaDateId = 0L private var mLatestMediaDateId = 0L
private var mCurrentPathPrefix = "" // used at "Group direct subfolders" for navigation
private var mOpenedSubfolders = arrayListOf("") // used at "Group direct subfolders" for navigating Up with the back button
private var mLastMediaHandler = Handler() private var mLastMediaHandler = Handler()
private var mTempShowHiddenHandler = Handler() private var mTempShowHiddenHandler = Handler()
private var mZoomListener: MyRecyclerView.MyZoomListener? = null private var mZoomListener: MyRecyclerView.MyZoomListener? = null
private var mSearchMenuItem: MenuItem? = null
private var mDirs = ArrayList<Directory>()
private var mStoredAnimateGifs = true private var mStoredAnimateGifs = true
private var mStoredCropThumbnails = true private var mStoredCropThumbnails = true
@ -110,7 +119,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
showFilterMediaDialog() showFilterMediaDialog()
} }
mIsPasswordProtectionPending = config.appPasswordProtectionOn mIsPasswordProtectionPending = config.isAppPasswordProtectionOn
setupLatestMediaId() setupLatestMediaId()
// notify some users about the Clock app // notify some users about the Clock app
@ -119,10 +128,6 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
NewAppDialog(this, NEW_APP_PACKAGE, "Simple Clock") NewAppDialog(this, NEW_APP_PACKAGE, "Simple Clock")
}*/ }*/
if (!config.wasOTGHandled && hasPermission(PERMISSION_WRITE_STORAGE)) {
checkOTGInclusion()
}
if (!config.wereFavoritesPinned) { if (!config.wereFavoritesPinned) {
config.addPinnedFolders(hashSetOf(FAVORITES)) config.addPinnedFolders(hashSetOf(FAVORITES))
config.wereFavoritesPinned = true config.wereFavoritesPinned = true
@ -140,6 +145,8 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
config.filterMedia += TYPE_SVGS config.filterMedia += TYPE_SVGS
} }
} }
updateWidgets()
} }
override fun onStart() { override fun onStart() {
@ -213,6 +220,8 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
mSearchMenuItem?.collapseActionView()
if (config.temporarilyShowHidden || config.tempSkipDeleteConfirmation) { if (config.temporarilyShowHidden || config.tempSkipDeleteConfirmation) {
mTempShowHiddenHandler.postDelayed({ mTempShowHiddenHandler.postDelayed({
config.temporarilyShowHidden = false config.temporarilyShowHidden = false
@ -230,7 +239,24 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
config.tempSkipDeleteConfirmation = false config.tempSkipDeleteConfirmation = false
mTempShowHiddenHandler.removeCallbacksAndMessages(null) mTempShowHiddenHandler.removeCallbacksAndMessages(null)
removeTempFolder() removeTempFolder()
GalleryDatabase.destroyInstance()
if (!config.showAll) {
GalleryDatabase.destroyInstance()
}
}
}
override fun onBackPressed() {
if (config.groupDirectSubfolders) {
if (mCurrentPathPrefix.isEmpty()) {
super.onBackPressed()
} else {
mOpenedSubfolders.removeAt(mOpenedSubfolders.size - 1)
mCurrentPathPrefix = mOpenedSubfolders.last()
setupAdapter(mDirs)
}
} else {
super.onBackPressed()
} }
} }
@ -242,10 +268,13 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
menu.apply { menu.apply {
findItem(R.id.increase_column_count).isVisible = config.viewTypeFolders == VIEW_TYPE_GRID && config.dirColumnCnt < MAX_COLUMN_COUNT findItem(R.id.increase_column_count).isVisible = config.viewTypeFolders == VIEW_TYPE_GRID && config.dirColumnCnt < MAX_COLUMN_COUNT
findItem(R.id.reduce_column_count).isVisible = config.viewTypeFolders == VIEW_TYPE_GRID && config.dirColumnCnt > 1 findItem(R.id.reduce_column_count).isVisible = config.viewTypeFolders == VIEW_TYPE_GRID && config.dirColumnCnt > 1
setupSearch(this)
} }
} }
menu.findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden menu.findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden
menu.findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden menu.findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden
return true return true
} }
@ -292,6 +321,43 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} }
} }
private fun setupSearch(menu: Menu) {
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
mSearchMenuItem = menu.findItem(R.id.search)
(mSearchMenuItem?.actionView as? SearchView)?.apply {
setSearchableInfo(searchManager.getSearchableInfo(componentName))
isSubmitButtonEnabled = false
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String) = false
override fun onQueryTextChange(newText: String): Boolean {
if (mIsSearchOpen) {
setupAdapter(mDirs, newText)
}
return true
}
})
}
MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, object : MenuItemCompat.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
mIsSearchOpen = true
directories_refresh_layout.isEnabled = false
return true
}
// this triggers on device rotation too, avoid doing anything
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
if (mIsSearchOpen) {
mIsSearchOpen = false
directories_refresh_layout.isEnabled = config.enablePullToRefresh
setupAdapter(mDirs, "")
}
return true
}
})
}
private fun removeTempFolder() { private fun removeTempFolder() {
if (config.tempFolderPath.isNotEmpty()) { if (config.tempFolderPath.isNotEmpty()) {
val newFolder = File(config.tempFolderPath) val newFolder = File(config.tempFolderPath)
@ -309,8 +375,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
Thread { Thread {
if (hasOTGConnected()) { if (hasOTGConnected()) {
runOnUiThread { runOnUiThread {
handleOTGPermission { ConfirmationDialog(this, getString(R.string.usb_detected), positive = R.string.ok, negative = 0) {
config.addIncludedFolder(OTG_PATH) handleOTGPermission {
config.addIncludedFolder(OTG_PATH)
}
} }
} }
config.wasOTGHandled = true config.wasOTGHandled = true
@ -321,6 +389,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
private fun tryLoadGallery() { private fun tryLoadGallery() {
handlePermission(PERMISSION_WRITE_STORAGE) { handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) { if (it) {
if (!config.wasOTGHandled && hasPermission(PERMISSION_WRITE_STORAGE)) {
checkOTGInclusion()
}
if (config.showAll) { if (config.showAll) {
showAllMedia() showAllMedia()
} else { } else {
@ -387,17 +459,11 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} }
private fun changeViewType() { private fun changeViewType() {
val items = arrayListOf( ChangeViewTypeDialog(this, true) {
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() invalidateOptionsMenu()
setupLayoutManager() setupLayoutManager()
val dirs = getCurrentlyDisplayedDirs()
directories_grid.adapter = null directories_grid.adapter = null
setupAdapter(dirs) setupAdapter(mDirs)
} }
} }
@ -420,16 +486,31 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} }
override fun deleteFolders(folders: ArrayList<File>) { override fun deleteFolders(folders: ArrayList<File>) {
val fileDirItems = folders.map { FileDirItem(it.absolutePath, it.name, true) } as ArrayList<FileDirItem> val fileDirItems = folders.asSequence().filter { it.isDirectory }.map { FileDirItem(it.absolutePath, it.name, true) }.toMutableList() as ArrayList<FileDirItem>
fileDirItems.forEach { when {
toast(String.format(getString(R.string.deleting_folder), it.name), Toast.LENGTH_LONG) fileDirItems.isEmpty() -> return
fileDirItems.size == 1 -> toast(String.format(getString(R.string.deleting_folder), fileDirItems.first().name))
else -> {
val baseString = if (config.useRecycleBin) R.plurals.moving_items_into_bin else R.plurals.delete_items
val deletingItems = resources.getQuantityString(baseString, fileDirItems.size, fileDirItems.size)
toast(deletingItems)
}
} }
if (config.useRecycleBin) { if (config.useRecycleBin) {
val pathsToDelete = ArrayList<String>() val pathsToDelete = ArrayList<String>()
val filter = config.filterMedia
val showHidden = config.shouldShowHidden
fileDirItems.filter { it.isDirectory }.forEach { fileDirItems.filter { it.isDirectory }.forEach {
val files = File(it.path).listFiles() val files = File(it.path).listFiles()
files?.filter { it.absolutePath.isMediaFile() }?.mapTo(pathsToDelete) { it.absolutePath } files?.filter {
it.absolutePath.isMediaFile() && (showHidden || !it.name.startsWith('.')) &&
((it.isImageFast() && filter and TYPE_IMAGES != 0) ||
(it.isVideoFast() && filter and TYPE_VIDEOS != 0) ||
(it.isGif() && filter and TYPE_GIFS != 0) ||
(it.isRawFast() && filter and TYPE_RAWS != 0) ||
(it.isSvg() && filter and TYPE_SVGS != 0))
}?.mapTo(pathsToDelete) { it.absolutePath }
} }
movePathsInRecycleBin(pathsToDelete, mMediumDao) { movePathsInRecycleBin(pathsToDelete, mMediumDao) {
@ -469,10 +550,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
private fun setupGridLayoutManager() { private fun setupGridLayoutManager() {
val layoutManager = directories_grid.layoutManager as MyGridLayoutManager val layoutManager = directories_grid.layoutManager as MyGridLayoutManager
if (config.scrollHorizontally) { if (config.scrollHorizontally) {
layoutManager.orientation = GridLayoutManager.HORIZONTAL layoutManager.orientation = RecyclerView.HORIZONTAL
directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT) directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
} else { } else {
layoutManager.orientation = GridLayoutManager.VERTICAL layoutManager.orientation = RecyclerView.VERTICAL
directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
} }
@ -531,13 +612,13 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
private fun setupListLayoutManager() { private fun setupListLayoutManager() {
val layoutManager = directories_grid.layoutManager as MyGridLayoutManager val layoutManager = directories_grid.layoutManager as MyGridLayoutManager
layoutManager.spanCount = 1 layoutManager.spanCount = 1
layoutManager.orientation = GridLayoutManager.VERTICAL layoutManager.orientation = RecyclerView.VERTICAL
directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
mZoomListener = null mZoomListener = null
} }
private fun createNewFolder() { private fun createNewFolder() {
FilePickerDialog(this, internalStoragePath, false, config.shouldShowHidden) { FilePickerDialog(this, internalStoragePath, false, config.shouldShowHidden, false, true) {
CreateNewFolderDialog(this, it) { CreateNewFolderDialog(this, it) {
config.tempFolderPath = it config.tempFolderPath = it
Thread { Thread {
@ -597,14 +678,22 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
if (requestCode == PICK_MEDIA && resultData != null) { if (requestCode == PICK_MEDIA && resultData != null) {
val resultIntent = Intent() val resultIntent = Intent()
var resultUri: Uri? = null
if (mIsThirdPartyIntent) { if (mIsThirdPartyIntent) {
when { when {
intent.extras?.containsKey(MediaStore.EXTRA_OUTPUT) == true -> fillExtraOutput(resultData) intent.extras?.containsKey(MediaStore.EXTRA_OUTPUT) == true -> {
resultUri = fillExtraOutput(resultData)
}
resultData.extras?.containsKey(PICKED_PATHS) == true -> fillPickedPaths(resultData, resultIntent) resultData.extras?.containsKey(PICKED_PATHS) == true -> fillPickedPaths(resultData, resultIntent)
else -> fillIntentPath(resultData, resultIntent) else -> fillIntentPath(resultData, resultIntent)
} }
} }
if (resultUri != null) {
resultIntent.data = resultUri
resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
setResult(Activity.RESULT_OK, resultIntent) setResult(Activity.RESULT_OK, resultIntent)
finish() finish()
} else if (requestCode == PICK_WALLPAPER) { } else if (requestCode == PICK_WALLPAPER) {
@ -615,22 +704,25 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
super.onActivityResult(requestCode, resultCode, resultData) super.onActivityResult(requestCode, resultCode, resultData)
} }
private fun fillExtraOutput(resultData: Intent) { private fun fillExtraOutput(resultData: Intent): Uri? {
val path = resultData.data.path val file = File(resultData.data.path)
var inputStream: InputStream? = null var inputStream: InputStream? = null
var outputStream: OutputStream? = null var outputStream: OutputStream? = null
try { try {
val output = intent.extras.get(MediaStore.EXTRA_OUTPUT) as Uri val output = intent.extras.get(MediaStore.EXTRA_OUTPUT) as Uri
inputStream = FileInputStream(File(path)) inputStream = FileInputStream(file)
outputStream = contentResolver.openOutputStream(output) outputStream = contentResolver.openOutputStream(output)
inputStream.copyTo(outputStream) inputStream.copyTo(outputStream)
} catch (e: SecurityException) { } catch (e: SecurityException) {
showErrorToast(e) showErrorToast(e)
} catch (ignored: FileNotFoundException) { } catch (ignored: FileNotFoundException) {
return getFilePublicUri(file, BuildConfig.APPLICATION_ID)
} finally { } finally {
inputStream?.close() inputStream?.close()
outputStream?.close() outputStream?.close()
} }
return null
} }
private fun fillPickedPaths(resultData: Intent, resultIntent: Intent) { private fun fillPickedPaths(resultData: Intent, resultIntent: Intent) {
@ -678,9 +770,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} }
private fun gotDirectories(newDirs: ArrayList<Directory>) { private fun gotDirectories(newDirs: ArrayList<Directory>) {
// if hidden item showing is disabled but all Favorite items are hidden, hide the Favorites folder
mIsGettingDirs = false mIsGettingDirs = false
mShouldStopFetching = false mShouldStopFetching = false
// if hidden item showing is disabled but all Favorite items are hidden, hide the Favorites folder
if (!config.shouldShowHidden) { if (!config.shouldShowHidden) {
val favoritesFolder = newDirs.firstOrNull { it.areFavorites() } val favoritesFolder = newDirs.firstOrNull { it.areFavorites() }
if (favoritesFolder != null && favoritesFolder.tmb.getFilenameFromPath().startsWith('.')) { if (favoritesFolder != null && favoritesFolder.tmb.getFilenameFromPath().startsWith('.')) {
@ -694,10 +787,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
runOnUiThread { runOnUiThread {
checkPlaceholderVisibility(dirs) checkPlaceholderVisibility(dirs)
val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFolders == VIEW_TYPE_GRID
directories_vertical_fastscroller.beVisibleIf(directories_grid.isVisible() && !allowHorizontalScroll) directories_vertical_fastscroller.beVisibleIf(directories_grid.isVisible() && !allowHorizontalScroll)
directories_horizontal_fastscroller.beVisibleIf(directories_grid.isVisible() && allowHorizontalScroll) directories_horizontal_fastscroller.beVisibleIf(directories_grid.isVisible() && allowHorizontalScroll)
setupAdapter(dirs) setupAdapter(dirs.clone() as ArrayList<Directory>)
} }
// cached folders have been loaded, recheck folders one by one starting with the first displayed // cached folders have been loaded, recheck folders one by one starting with the first displayed
@ -707,9 +800,11 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
val hiddenString = getString(R.string.hidden) val hiddenString = getString(R.string.hidden)
val albumCovers = config.parseAlbumCovers() val albumCovers = config.parseAlbumCovers()
val includedFolders = config.includedFolders val includedFolders = config.includedFolders
val tempFolderPath = config.tempFolderPath
val isSortingAscending = config.directorySorting and SORT_DESCENDING == 0 val isSortingAscending = config.directorySorting and SORT_DESCENDING == 0
val getProperDateTaken = config.directorySorting and SORT_BY_DATE_TAKEN != 0 val getProperDateTaken = config.directorySorting and SORT_BY_DATE_TAKEN != 0
val favoritePaths = getFavoritePaths() val favoritePaths = getFavoritePaths()
val dirPathsToRemove = ArrayList<String>()
try { try {
for (directory in dirs) { for (directory in dirs) {
@ -717,15 +812,18 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
return return
} }
val curMedia = mediaFetcher.getFilesFrom(directory.path, getImagesOnly, getVideosOnly, getProperDateTaken, favoritePaths) val curMedia = mediaFetcher.getFilesFrom(directory.path, getImagesOnly, getVideosOnly, getProperDateTaken, favoritePaths, false)
val newDir = if (curMedia.isEmpty()) { val newDir = if (curMedia.isEmpty()) {
if (directory.path != tempFolderPath) {
dirPathsToRemove.add(directory.path)
}
directory directory
} else { } else {
createDirectoryFromMedia(directory.path, curMedia, albumCovers, hiddenString, includedFolders, isSortingAscending) createDirectoryFromMedia(directory.path, curMedia, albumCovers, hiddenString, includedFolders, isSortingAscending)
} }
// we are looping through the already displayed folders looking for changes, do not do anything if nothing changed // we are looping through the already displayed folders looking for changes, do not do anything if nothing changed
if (directory == newDir) { if (directory.copy(subfoldersCount = 0, subfoldersMediaCount = 0) == newDir) {
continue continue
} }
@ -739,7 +837,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
types = newDir.types types = newDir.types
} }
showSortedDirs(dirs) setupAdapter(dirs)
// update directories and media files in the local db, delete invalid items // update directories and media files in the local db, delete invalid items
updateDBDirectory(directory, mDirectoryDao) updateDBDirectory(directory, mDirectoryDao)
@ -751,7 +849,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
if (!curMedia.contains(it)) { if (!curMedia.contains(it)) {
val path = (it as? Medium)?.path val path = (it as? Medium)?.path
if (path != null) { if (path != null) {
mMediumDao.deleteMediumPath(path) deleteDBPath(mMediumDao, path)
} }
} }
} }
@ -760,9 +858,18 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} catch (ignored: Exception) { } catch (ignored: Exception) {
} }
if (dirPathsToRemove.isNotEmpty()) {
val dirsToRemove = dirs.filter { dirPathsToRemove.contains(it.path) }
dirsToRemove.forEach {
mDirectoryDao.deleteDirPath(it.path)
}
dirs.removeAll(dirsToRemove)
setupAdapter(dirs)
}
val foldersToScan = mediaFetcher.getFoldersToScan() val foldersToScan = mediaFetcher.getFoldersToScan()
foldersToScan.add(FAVORITES) foldersToScan.add(FAVORITES)
if (config.showRecycleBinAtFolders) { if (config.useRecycleBin && config.showRecycleBinAtFolders) {
foldersToScan.add(RECYCLE_BIN) foldersToScan.add(RECYCLE_BIN)
} else { } else {
foldersToScan.remove(RECYCLE_BIN) foldersToScan.remove(RECYCLE_BIN)
@ -778,7 +885,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
return return
} }
val newMedia = mediaFetcher.getFilesFrom(folder, getImagesOnly, getVideosOnly, getProperDateTaken, favoritePaths) val newMedia = mediaFetcher.getFilesFrom(folder, getImagesOnly, getVideosOnly, getProperDateTaken, favoritePaths, false)
if (newMedia.isEmpty()) { if (newMedia.isEmpty()) {
continue continue
} }
@ -794,7 +901,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
val newDir = createDirectoryFromMedia(folder, newMedia, albumCovers, hiddenString, includedFolders, isSortingAscending) val newDir = createDirectoryFromMedia(folder, newMedia, albumCovers, hiddenString, includedFolders, isSortingAscending)
dirs.add(newDir) dirs.add(newDir)
showSortedDirs(dirs) setupAdapter(dirs)
mDirectoryDao.insert(newDir) mDirectoryDao.insert(newDir)
if (folder != RECYCLE_BIN) { if (folder != RECYCLE_BIN) {
mMediumDao.insertAll(newMedia) mMediumDao.insertAll(newMedia)
@ -818,6 +925,11 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} catch (e: Exception) { } catch (e: Exception) {
config.everShownFolders = HashSet() config.everShownFolders = HashSet()
} }
mDirs = dirs.clone() as ArrayList<Directory>
if (mDirs.size > 55) {
excludeSpamFolders()
}
} }
private fun checkPlaceholderVisibility(dirs: ArrayList<Directory>) { private fun checkPlaceholderVisibility(dirs: ArrayList<Directory>) {
@ -826,15 +938,6 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
directories_grid.beVisibleIf(directories_empty_text_label.isGone()) directories_grid.beVisibleIf(directories_empty_text_label.isGone())
} }
private fun showSortedDirs(dirs: ArrayList<Directory>) {
var sortedDirs = getSortedDirectories(dirs)
sortedDirs = sortedDirs.distinctBy { it.path.getDistinctPath() } as ArrayList<Directory>
runOnUiThread {
(directories_grid.adapter as? DirectoryAdapter)?.updateDirs(sortedDirs)
}
}
private fun createDirectoryFromMedia(path: String, curMedia: ArrayList<Medium>, albumCovers: ArrayList<AlbumCover>, hiddenString: String, private fun createDirectoryFromMedia(path: String, curMedia: ArrayList<Medium>, albumCovers: ArrayList<AlbumCover>, hiddenString: String,
includedFolders: MutableSet<String>, isSortingAscending: Boolean): Directory { includedFolders: MutableSet<String>, isSortingAscending: Boolean): Directory {
var thumbnail = curMedia.firstOrNull { getDoesFilePathExist(it.path) }?.path ?: "" var thumbnail = curMedia.firstOrNull { getDoesFilePathExist(it.path) }?.path ?: ""
@ -848,43 +951,59 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} }
} }
val mediaTypes = curMedia.getDirMediaTypes()
val dirName = when (path) {
FAVORITES -> getString(R.string.favorites)
RECYCLE_BIN -> getString(R.string.recycle_bin)
else -> checkAppendingHidden(path, hiddenString, includedFolders)
}
val firstItem = curMedia.first() val firstItem = curMedia.first()
val lastItem = curMedia.last() val lastItem = curMedia.last()
val dirName = checkAppendingHidden(path, hiddenString, includedFolders)
val lastModified = if (isSortingAscending) Math.min(firstItem.modified, lastItem.modified) else Math.max(firstItem.modified, lastItem.modified) val lastModified = if (isSortingAscending) Math.min(firstItem.modified, lastItem.modified) else Math.max(firstItem.modified, lastItem.modified)
val dateTaken = if (isSortingAscending) Math.min(firstItem.taken, lastItem.taken) else Math.max(firstItem.taken, lastItem.taken) val dateTaken = if (isSortingAscending) Math.min(firstItem.taken, lastItem.taken) else Math.max(firstItem.taken, lastItem.taken)
val size = curMedia.sumByLong { it.size } val size = curMedia.sumByLong { it.size }
val mediaTypes = curMedia.getDirMediaTypes()
return Directory(null, path, thumbnail, dirName, curMedia.size, lastModified, dateTaken, size, getPathLocation(path), mediaTypes) return Directory(null, path, thumbnail, dirName, curMedia.size, lastModified, dateTaken, size, getPathLocation(path), mediaTypes)
} }
private fun setupAdapter(dirs: ArrayList<Directory>) { private fun setupAdapter(dirs: ArrayList<Directory>, textToSearch: String = "") {
val currAdapter = directories_grid.adapter val currAdapter = directories_grid.adapter
val distinctDirs = dirs.distinctBy { it.path.getDistinctPath() }.toMutableList() as ArrayList<Directory>
val sortedDirs = getSortedDirectories(distinctDirs)
var dirsToShow = getDirsToShow(sortedDirs, mDirs, mCurrentPathPrefix).clone() as ArrayList<Directory>
if (currAdapter == null) { if (currAdapter == null) {
initZoomListener() initZoomListener()
val fastscroller = if (config.scrollHorizontally) directories_horizontal_fastscroller else directories_vertical_fastscroller val fastscroller = if (config.scrollHorizontally) directories_horizontal_fastscroller else directories_vertical_fastscroller
DirectoryAdapter(this, dirs.clone() as ArrayList<Directory>, this, directories_grid, isPickIntent(intent) || isGetAnyContentIntent(intent), fastscroller) { DirectoryAdapter(this, dirsToShow, this, directories_grid, isPickIntent(intent) || isGetAnyContentIntent(intent), fastscroller) {
val path = (it as Directory).path val clickedDir = it as Directory
if (path != config.tempFolderPath) { val path = clickedDir.path
itemClicked(path) if (clickedDir.subfoldersCount == 1 || !config.groupDirectSubfolders) {
if (path != config.tempFolderPath) {
itemClicked(path)
}
} else {
mCurrentPathPrefix = path
mOpenedSubfolders.add(path)
setupAdapter(mDirs, "")
} }
}.apply { }.apply {
setupZoomListener(mZoomListener) setupZoomListener(mZoomListener)
directories_grid.adapter = this runOnUiThread {
directories_grid.adapter = this
setupScrollDirection()
}
} }
measureRecyclerViewContent(dirsToShow)
} else { } else {
(currAdapter as DirectoryAdapter).updateDirs(dirs) if (textToSearch.isNotEmpty()) {
dirsToShow = dirsToShow.filter { it.name.contains(textToSearch, true) }.sortedBy { !it.name.startsWith(textToSearch, true) }.toMutableList() as ArrayList
}
runOnUiThread {
(directories_grid.adapter as? DirectoryAdapter)?.updateDirs(dirsToShow)
measureRecyclerViewContent(dirsToShow)
}
} }
getRecyclerAdapter()?.dirs?.apply { // recyclerview sometimes becomes empty at init/update, triggering an invisible refresh like this seems to work fine
measureRecyclerViewContent(this) directories_grid.postDelayed({
} directories_grid.scrollBy(0, 0)
setupScrollDirection() }, 500)
} }
private fun setupScrollDirection() { private fun setupScrollDirection() {
@ -915,7 +1034,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
invalidDirs.add(it) invalidDirs.add(it)
} else if (it.path != config.tempFolderPath) { } else if (it.path != config.tempFolderPath) {
val children = if (it.path.startsWith(OTG_PATH)) getOTGFolderChildrenNames(it.path) else File(it.path).list()?.asList() val children = if (it.path.startsWith(OTG_PATH)) getOTGFolderChildrenNames(it.path) else File(it.path).list()?.asList()
val hasMediaFile = children?.any { it.isMediaFile() } ?: false val hasMediaFile = children?.any { it?.isMediaFile() == true } ?: false
if (!hasMediaFile) { if (!hasMediaFile) {
invalidDirs.add(it) invalidDirs.add(it)
} }
@ -938,7 +1057,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
if (invalidDirs.isNotEmpty()) { if (invalidDirs.isNotEmpty()) {
dirs.removeAll(invalidDirs) dirs.removeAll(invalidDirs)
showSortedDirs(dirs) setupAdapter(dirs)
invalidDirs.forEach { invalidDirs.forEach {
mDirectoryDao.deleteDirPath(it.path) mDirectoryDao.deleteDirPath(it.path)
} }
@ -959,7 +1078,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} }
private fun checkLastMediaChanged() { private fun checkLastMediaChanged() {
if (isActivityDestroyed()) { if (isDestroyed) {
return return
} }
@ -981,18 +1100,64 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
}, LAST_MEDIA_CHECK_PERIOD) }, LAST_MEDIA_CHECK_PERIOD)
} }
private fun checkRecycleBinItems() { private fun checkRecycleBinItems() {
if (config.useRecycleBin && config.lastBinCheck < System.currentTimeMillis() - DAY_SECONDS * 1000) { if (config.useRecycleBin && config.lastBinCheck < System.currentTimeMillis() - DAY_SECONDS * 1000) {
config.lastBinCheck = System.currentTimeMillis() config.lastBinCheck = System.currentTimeMillis()
Handler().postDelayed({ Handler().postDelayed({
Thread { Thread {
mMediumDao.deleteOldRecycleBinItems(System.currentTimeMillis() - MONTH_MILLISECONDS) try {
mMediumDao.deleteOldRecycleBinItems(System.currentTimeMillis() - MONTH_MILLISECONDS)
} catch (e: Exception) {
}
}.start() }.start()
}, 3000L) }, 3000L)
} }
} }
// exclude probably unwanted folders, for example facebook stickers are split between hundreds of separate folders like
// /storage/emulated/0/Android/data/com.facebook.orca/files/stickers/175139712676531/209575122566323
// /storage/emulated/0/Android/data/com.facebook.orca/files/stickers/497837993632037/499671223448714
private fun excludeSpamFolders() {
Thread {
try {
val internalPath = internalStoragePath
val checkedPaths = ArrayList<String>()
val oftenRepeatedPaths = ArrayList<String>()
val paths = mDirs.map { it.path.removePrefix(internalPath) }.toMutableList() as ArrayList<String>
paths.forEach {
val parts = it.split("/")
var currentString = ""
for (i in 0 until parts.size) {
currentString += "${parts[i]}/"
if (!checkedPaths.contains(currentString)) {
val cnt = paths.count { it.startsWith(currentString) }
if (cnt > 50 && currentString.startsWith("/Android/data", true)) {
oftenRepeatedPaths.add(currentString)
}
}
checkedPaths.add(currentString)
}
}
val substringToRemove = oftenRepeatedPaths.filter {
val path = it
it == "/" || oftenRepeatedPaths.any { it != path && it.startsWith(path) }
}
oftenRepeatedPaths.removeAll(substringToRemove)
oftenRepeatedPaths.forEach {
val file = File("$internalPath/$it")
if (file.exists()) {
config.addExcludedFolder(file.absolutePath)
}
}
} catch (e: Exception) {
}
}.start()
}
override fun refreshItems() { override fun refreshItems() {
getDirectories() getDirectories()
} }
@ -1012,55 +1177,11 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
private fun checkWhatsNewDialog() { private fun checkWhatsNewDialog() {
arrayListOf<Release>().apply { arrayListOf<Release>().apply {
add(Release(46, R.string.release_46)) add(Release(213, R.string.release_213))
add(Release(47, R.string.release_47)) add(Release(217, R.string.release_217))
add(Release(49, R.string.release_49)) add(Release(220, R.string.release_220))
add(Release(50, R.string.release_50)) add(Release(221, R.string.release_221))
add(Release(51, R.string.release_51)) add(Release(225, R.string.release_225))
add(Release(52, R.string.release_52))
add(Release(54, R.string.release_54))
add(Release(58, R.string.release_58))
add(Release(62, R.string.release_62))
add(Release(65, R.string.release_65))
add(Release(66, R.string.release_66))
add(Release(69, R.string.release_69))
add(Release(70, R.string.release_70))
add(Release(72, R.string.release_72))
add(Release(74, R.string.release_74))
add(Release(76, R.string.release_76))
add(Release(77, R.string.release_77))
add(Release(83, R.string.release_83))
add(Release(84, R.string.release_84))
add(Release(88, R.string.release_88))
add(Release(89, R.string.release_89))
add(Release(93, R.string.release_93))
add(Release(94, R.string.release_94))
add(Release(97, R.string.release_97))
add(Release(98, R.string.release_98))
add(Release(108, R.string.release_108))
add(Release(112, R.string.release_112))
add(Release(114, R.string.release_114))
add(Release(115, R.string.release_115))
add(Release(118, R.string.release_118))
add(Release(119, R.string.release_119))
add(Release(122, R.string.release_122))
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))
add(Release(136, R.string.release_136))
add(Release(137, R.string.release_137))
add(Release(138, R.string.release_138))
add(Release(143, R.string.release_143))
add(Release(158, R.string.release_158))
add(Release(159, R.string.release_159))
add(Release(163, R.string.release_163))
add(Release(177, R.string.release_177))
add(Release(178, R.string.release_178))
add(Release(180, R.string.release_180))
add(Release(181, R.string.release_181))
add(Release(182, R.string.release_182))
add(Release(184, R.string.release_184))
checkWhatsNew(this, BuildConfig.VERSION_CODE) checkWhatsNew(this, BuildConfig.VERSION_CODE)
} }
} }

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.app.Activity import android.app.Activity
import android.app.SearchManager import android.app.SearchManager
@ -9,42 +9,39 @@ import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.support.v4.view.MenuItemCompat
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.SearchView
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuItemCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.commons.helpers.REQUEST_EDIT_IMAGE import com.simplemobiletools.commons.helpers.REQUEST_EDIT_IMAGE
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.views.MyGridLayoutManager import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.adapters.MediaAdapter import com.simplemobiletools.gallery.pro.adapters.MediaAdapter
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask import com.simplemobiletools.gallery.pro.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.dialogs.ChangeGroupingDialog import com.simplemobiletools.gallery.pro.databases.GalleryDatabase
import com.simplemobiletools.gallery.dialogs.ChangeSortingDialog import com.simplemobiletools.gallery.pro.dialogs.*
import com.simplemobiletools.gallery.dialogs.ExcludeFolderDialog import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.dialogs.FilterMediaDialog import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.pro.interfaces.DirectoryDao
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.interfaces.MediaOperationsListener
import com.simplemobiletools.gallery.interfaces.DirectoryDao import com.simplemobiletools.gallery.pro.interfaces.MediumDao
import com.simplemobiletools.gallery.interfaces.MediaOperationsListener import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.interfaces.MediumDao import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.models.ThumbnailSection
import com.simplemobiletools.gallery.models.ThumbnailItem
import com.simplemobiletools.gallery.models.ThumbnailSection
import kotlinx.android.synthetic.main.activity_media.* import kotlinx.android.synthetic.main.activity_media.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -116,6 +113,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
media_empty_text.setOnClickListener { media_empty_text.setOnClickListener {
showFilterMediaDialog() showFilterMediaDialog()
} }
updateWidgets()
} }
override fun onStart() { override fun onStart() {
@ -154,7 +153,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
media_horizontal_fastscroller.allowBubbleDisplay = config.showInfoBubble media_horizontal_fastscroller.allowBubbleDisplay = config.showInfoBubble
media_vertical_fastscroller.allowBubbleDisplay = config.showInfoBubble media_vertical_fastscroller.allowBubbleDisplay = config.showInfoBubble
media_refresh_layout.isEnabled = config.enablePullToRefresh media_refresh_layout.isEnabled = config.enablePullToRefresh
tryloadGallery() tryLoadGallery()
invalidateOptionsMenu() invalidateOptionsMenu()
media_empty_text_label.setTextColor(config.textColor) media_empty_text_label.setTextColor(config.textColor)
media_empty_text.setTextColor(getAdjustedPrimaryColor()) media_empty_text.setTextColor(getAdjustedPrimaryColor())
@ -191,6 +190,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
if (config.showAll && !isChangingConfigurations) { if (config.showAll && !isChangingConfigurations) {
config.temporarilyShowHidden = false config.temporarilyShowHidden = false
config.tempSkipDeleteConfirmation = false config.tempSkipDeleteConfirmation = false
GalleryDatabase.destroyInstance()
} }
mTempShowHiddenHandler.removeCallbacksAndMessages(null) mTempShowHiddenHandler.removeCallbacksAndMessages(null)
@ -219,10 +219,10 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden
findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden
findItem(R.id.increase_column_count).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID && config.mediaColumnCnt < MAX_COLUMN_COUNT val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath)
findItem(R.id.reduce_column_count).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID && config.mediaColumnCnt > 1 findItem(R.id.increase_column_count).isVisible = viewType == VIEW_TYPE_GRID && config.mediaColumnCnt < MAX_COLUMN_COUNT
findItem(R.id.reduce_column_count).isVisible = viewType == VIEW_TYPE_GRID && config.mediaColumnCnt > 1
findItem(R.id.toggle_filename).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID findItem(R.id.toggle_filename).isVisible = viewType == VIEW_TYPE_GRID
} }
setupSearch(menu) setupSearch(menu)
@ -248,6 +248,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
R.id.stop_showing_hidden -> tryToggleTemporarilyShowHidden() R.id.stop_showing_hidden -> tryToggleTemporarilyShowHidden()
R.id.increase_column_count -> increaseColumnCount() R.id.increase_column_count -> increaseColumnCount()
R.id.reduce_column_count -> reduceColumnCount() R.id.reduce_column_count -> reduceColumnCount()
R.id.slideshow -> startSlideshow()
R.id.settings -> launchSettings() R.id.settings -> launchSettings()
R.id.about -> launchAbout() R.id.about -> launchAbout()
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
@ -255,6 +256,19 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
return true return true
} }
private fun startSlideshow() {
if (mMedia.isNotEmpty()) {
Intent(this, ViewPagerActivity::class.java).apply {
val item = mMedia.firstOrNull { it is Medium } as? Medium
?: return
putExtra(PATH, item.path)
putExtra(SHOW_ALL, mShowAll)
putExtra(SLIDESHOW_START_ON_ENTER, true)
startActivity(this)
}
}
}
private fun storeStateVariables() { private fun storeStateVariables() {
config.apply { config.apply {
mStoredAnimateGifs = animateGifs mStoredAnimateGifs = animateGifs
@ -306,23 +320,26 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
private fun searchQueryChanged(text: String) { private fun searchQueryChanged(text: String) {
Thread { Thread {
val filtered = mMedia.filter { it is Medium && it.name.contains(text, true) } as ArrayList try {
filtered.sortBy { it is Medium && !it.name.startsWith(text, true) } val filtered = mMedia.filter { it is Medium && it.name.contains(text, true) } as ArrayList
val grouped = MediaFetcher(applicationContext).groupMedia(filtered as ArrayList<Medium>, mPath) filtered.sortBy { it is Medium && !it.name.startsWith(text, true) }
runOnUiThread { val grouped = MediaFetcher(applicationContext).groupMedia(filtered as ArrayList<Medium>, mPath)
getMediaAdapter()?.updateMedia(grouped) runOnUiThread {
measureRecyclerViewContent(grouped) getMediaAdapter()?.updateMedia(grouped)
measureRecyclerViewContent(grouped)
}
} catch (ignored: Exception) {
} }
}.start() }.start()
} }
private fun tryloadGallery() { private fun tryLoadGallery() {
handlePermission(PERMISSION_WRITE_STORAGE) { handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) { if (it) {
val dirName = when { val dirName = when {
mPath == FAVORITES -> getString(R.string.favorites) mPath == FAVORITES -> getString(R.string.favorites)
mPath == RECYCLE_BIN -> getString(R.string.recycle_bin) mPath == RECYCLE_BIN -> getString(R.string.recycle_bin)
mPath == OTG_PATH -> getString(R.string.otg) mPath == OTG_PATH -> getString(R.string.usb)
mPath.startsWith(OTG_PATH) -> mPath.trimEnd('/').substringAfterLast('/') mPath.startsWith(OTG_PATH) -> mPath.trimEnd('/').substringAfterLast('/')
else -> getHumanizedFilename(mPath) else -> getHumanizedFilename(mPath)
} }
@ -348,7 +365,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
initZoomListener() initZoomListener()
val fastscroller = if (config.scrollHorizontally) media_horizontal_fastscroller else media_vertical_fastscroller val fastscroller = if (config.scrollHorizontally) media_horizontal_fastscroller else media_vertical_fastscroller
MediaAdapter(this, mMedia.clone() as ArrayList<ThumbnailItem>, this, mIsGetImageIntent || mIsGetVideoIntent || mIsGetAnyIntent, MediaAdapter(this, mMedia.clone() as ArrayList<ThumbnailItem>, this, mIsGetImageIntent || mIsGetVideoIntent || mIsGetAnyIntent,
mAllowPickingMultiple, media_grid, fastscroller) { mAllowPickingMultiple, mPath, media_grid, fastscroller) {
if (it is Medium) { if (it is Medium) {
itemClicked(it.path) itemClicked(it.path)
} }
@ -366,7 +383,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
private fun setupScrollDirection() { private fun setupScrollDirection() {
val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath)
val allowHorizontalScroll = config.scrollHorizontally && viewType == VIEW_TYPE_GRID
media_vertical_fastscroller.isHorizontal = false media_vertical_fastscroller.isHorizontal = false
media_vertical_fastscroller.beGoneIf(allowHorizontalScroll) media_vertical_fastscroller.beGoneIf(allowHorizontalScroll)
@ -397,7 +415,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
private fun checkLastMediaChanged() { private fun checkLastMediaChanged() {
if (isActivityDestroyed()) { if (isDestroyed) {
return return
} }
@ -474,12 +492,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
private fun changeViewType() { private fun changeViewType() {
val items = arrayListOf( ChangeViewTypeDialog(this, false, mPath) {
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() invalidateOptionsMenu()
setupLayoutManager() setupLayoutManager()
media_grid.adapter = null media_grid.adapter = null
@ -545,7 +558,9 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
mIsGettingMedia = true mIsGettingMedia = true
if (!mLoadedInitialPhotos) { if (mLoadedInitialPhotos) {
startAsyncTask()
} else {
getCachedMedia(mPath, mIsGetVideoIntent, mIsGetImageIntent, mMediumDao) { getCachedMedia(mPath, mIsGetVideoIntent, mIsGetImageIntent, mMediumDao) {
if (it.isEmpty()) { if (it.isEmpty()) {
runOnUiThread { runOnUiThread {
@ -556,9 +571,6 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
startAsyncTask() startAsyncTask()
} }
} else {
media_refresh_layout.isRefreshing = true
startAsyncTask()
} }
mLoadedInitialPhotos = true mLoadedInitialPhotos = true
@ -568,7 +580,15 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
mCurrAsyncTask?.stopFetching() mCurrAsyncTask?.stopFetching()
mCurrAsyncTask = GetMediaAsynctask(applicationContext, mPath, mIsGetImageIntent, mIsGetVideoIntent, mShowAll) { mCurrAsyncTask = GetMediaAsynctask(applicationContext, mPath, mIsGetImageIntent, mIsGetVideoIntent, mShowAll) {
Thread { Thread {
gotMedia(it) val oldMedia = mMedia.clone() as ArrayList<ThumbnailItem>
val newMedia = it
gotMedia(newMedia, false)
try {
oldMedia.filter { !newMedia.contains(it) }.mapNotNull { it as? Medium }.filter { !File(it.path).exists() }.forEach {
mMediumDao.deleteMediumPath(it.path)
}
} catch (e: Exception) {
}
}.start() }.start()
} }
@ -619,7 +639,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
private fun setupLayoutManager() { private fun setupLayoutManager() {
if (config.viewTypeFiles == VIEW_TYPE_GRID) { val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath)
if (viewType == VIEW_TYPE_GRID) {
setupGridLayoutManager() setupGridLayoutManager()
} else { } else {
setupListLayoutManager() setupListLayoutManager()
@ -629,10 +650,10 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
private fun setupGridLayoutManager() { private fun setupGridLayoutManager() {
val layoutManager = media_grid.layoutManager as MyGridLayoutManager val layoutManager = media_grid.layoutManager as MyGridLayoutManager
if (config.scrollHorizontally) { if (config.scrollHorizontally) {
layoutManager.orientation = GridLayoutManager.HORIZONTAL layoutManager.orientation = RecyclerView.HORIZONTAL
media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT) media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
} else { } else {
layoutManager.orientation = GridLayoutManager.VERTICAL layoutManager.orientation = RecyclerView.VERTICAL
media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
} }
@ -672,8 +693,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
val pathToCheck = if (mPath.isEmpty()) SHOW_ALL else mPath val pathToCheck = if (mPath.isEmpty()) SHOW_ALL else mPath
val hasSections = config.getFolderGrouping(pathToCheck) and GROUP_BY_NONE == 0 && !config.scrollHorizontally val hasSections = config.getFolderGrouping(pathToCheck) and GROUP_BY_NONE == 0 && !config.scrollHorizontally
val sectionTitleHeight = if (hasSections) layoutManager.getChildAt(0)?.height ?: 0 else 0 val sectionTitleHeight = if (hasSections) layoutManager.getChildAt(0)?.height ?: 0 else 0
val thumbnailHeight = if (hasSections) layoutManager.getChildAt(1)?.height ?: 0 else layoutManager.getChildAt(0)?.height val thumbnailHeight = if (hasSections) layoutManager.getChildAt(1)?.height ?: 0 else layoutManager.getChildAt(0)?.height ?: 0
?: 0
var fullHeight = 0 var fullHeight = 0
var curSectionItems = 0 var curSectionItems = 0
@ -696,7 +716,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
private fun initZoomListener() { private fun initZoomListener() {
if (config.viewTypeFiles == VIEW_TYPE_GRID) { val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath)
if (viewType == VIEW_TYPE_GRID) {
val layoutManager = media_grid.layoutManager as MyGridLayoutManager val layoutManager = media_grid.layoutManager as MyGridLayoutManager
mZoomListener = object : MyRecyclerView.MyZoomListener { mZoomListener = object : MyRecyclerView.MyZoomListener {
override fun zoomIn() { override fun zoomIn() {
@ -721,7 +742,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
private fun setupListLayoutManager() { private fun setupListLayoutManager() {
val layoutManager = media_grid.layoutManager as MyGridLayoutManager val layoutManager = media_grid.layoutManager as MyGridLayoutManager
layoutManager.spanCount = 1 layoutManager.spanCount = 1
layoutManager.orientation = GridLayoutManager.VERTICAL layoutManager.orientation = RecyclerView.VERTICAL
media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
mZoomListener = null mZoomListener = null
} }
@ -803,7 +824,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
} }
private fun gotMedia(media: ArrayList<ThumbnailItem>, isFromCache: Boolean = false) { private fun gotMedia(media: ArrayList<ThumbnailItem>, isFromCache: Boolean) {
mIsGettingMedia = false mIsGettingMedia = false
checkLastMediaChanged() checkLastMediaChanged()
mMedia = media mMedia = media
@ -814,7 +835,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
media_empty_text.beVisibleIf(media.isEmpty() && !isFromCache) media_empty_text.beVisibleIf(media.isEmpty() && !isFromCache)
media_grid.beVisibleIf(media_empty_text_label.isGone()) media_grid.beVisibleIf(media_empty_text_label.isGone())
val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath)
val allowHorizontalScroll = config.scrollHorizontally && viewType == VIEW_TYPE_GRID
media_vertical_fastscroller.beVisibleIf(media_grid.isVisible() && !allowHorizontalScroll) media_vertical_fastscroller.beVisibleIf(media_grid.isVisible() && !allowHorizontalScroll)
media_horizontal_fastscroller.beVisibleIf(media_grid.isVisible() && allowHorizontalScroll) media_horizontal_fastscroller.beVisibleIf(media_grid.isVisible() && allowHorizontalScroll)
setupAdapter() setupAdapter()
@ -832,11 +854,15 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
override fun tryDeleteFiles(fileDirItems: ArrayList<FileDirItem>) { override fun tryDeleteFiles(fileDirItems: ArrayList<FileDirItem>) {
val filtered = fileDirItems.filter { it.path.isMediaFile() } as ArrayList val filtered = fileDirItems.filter { File(it.path).isFile && it.path.isMediaFile() } as ArrayList
val deletingItems = resources.getQuantityString(R.plurals.deleting_items, filtered.size, filtered.size) if (filtered.isEmpty()) {
toast(deletingItems) return
}
if (config.useRecycleBin && !filtered.first().path.startsWith(recycleBinPath)) {
val movingItems = resources.getQuantityString(R.plurals.moving_items_into_bin, filtered.size, filtered.size)
toast(movingItems)
if (config.useRecycleBin && !filtered.first().path.startsWith(filesDir.absolutePath)) {
movePathsInRecycleBin(filtered.map { it.path } as ArrayList<String>, mMediumDao) { movePathsInRecycleBin(filtered.map { it.path } as ArrayList<String>, mMediumDao) {
if (it) { if (it) {
deleteFilteredFiles(filtered) deleteFilteredFiles(filtered)
@ -845,6 +871,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
} }
} }
} else { } else {
val deletingItems = resources.getQuantityString(R.plurals.deleting_items, filtered.size, filtered.size)
toast(deletingItems)
deleteFilteredFiles(filtered) deleteFilteredFiles(filtered)
} }
} }
@ -861,8 +889,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
Thread { Thread {
val useRecycleBin = config.useRecycleBin val useRecycleBin = config.useRecycleBin
filtered.forEach { filtered.forEach {
if (!useRecycleBin) { if (it.path.startsWith(recycleBinPath) || !useRecycleBin) {
mMediumDao.deleteMediumPath(it.path) deleteDBPath(mMediumDao, it.path)
} }
} }
}.start() }.start()

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap import android.graphics.Bitmap
@ -11,18 +11,19 @@ import android.widget.RelativeLayout
import com.google.vr.sdk.widgets.pano.VrPanoramaEventListener import com.google.vr.sdk.widgets.pano.VrPanoramaEventListener
import com.google.vr.sdk.widgets.pano.VrPanoramaView import com.google.vr.sdk.widgets.pano.VrPanoramaView
import com.simplemobiletools.commons.extensions.beVisible import com.simplemobiletools.commons.extensions.beVisible
import com.simplemobiletools.commons.extensions.onGlobalLayout
import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.helpers.PATH import com.simplemobiletools.gallery.pro.helpers.PATH
import kotlinx.android.synthetic.main.activity_panorama.* import kotlinx.android.synthetic.main.activity_panorama_photo.*
open class PanoramaActivity : SimpleActivity() { open class PanoramaPhotoActivity : SimpleActivity() {
private val CARDBOARD_DISPLAY_MODE = 3 private val CARDBOARD_DISPLAY_MODE = 3
private var isFullScreen = false private var isFullscreen = false
private var isExploreEnabled = true private var isExploreEnabled = true
private var isRendering = false private var isRendering = false
@ -30,8 +31,10 @@ open class PanoramaActivity : SimpleActivity() {
useDynamicTheme = false useDynamicTheme = false
requestWindowFeature(Window.FEATURE_NO_TITLE) requestWindowFeature(Window.FEATURE_NO_TITLE)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_panorama) setContentView(R.layout.activity_panorama_photo)
supportActionBar?.hide() supportActionBar?.hide()
checkNotchSupport()
setupButtonMargins() setupButtonMargins()
cardboard.setOnClickListener { cardboard.setOnClickListener {
@ -61,6 +64,8 @@ open class PanoramaActivity : SimpleActivity() {
if (config.blackBackground) { if (config.blackBackground) {
updateStatusbarColor(Color.BLACK) updateStatusbarColor(Color.BLACK)
} }
window.statusBarColor = resources.getColor(R.color.circle_black_background)
} }
override fun onPause() { override fun onPause() {
@ -97,11 +102,6 @@ open class PanoramaActivity : SimpleActivity() {
loadImageFromBitmap(bitmap, options) loadImageFromBitmap(bitmap, options)
setFlingingEnabled(true) setFlingingEnabled(true)
setPureTouchTracking(true) setPureTouchTracking(true)
setEventListener(object : VrPanoramaEventListener() {
override fun onClick() {
handleClick()
}
})
// add custom buttons so we can position them and toggle visibility as desired // add custom buttons so we can position them and toggle visibility as desired
setFullscreenButtonEnabled(false) setFullscreenButtonEnabled(false)
@ -112,6 +112,12 @@ open class PanoramaActivity : SimpleActivity() {
setOnClickListener { setOnClickListener {
handleClick() handleClick()
} }
setEventListener(object : VrPanoramaEventListener() {
override fun onClick() {
handleClick()
}
})
} }
} }
}.start() }.start()
@ -120,7 +126,7 @@ open class PanoramaActivity : SimpleActivity() {
} }
window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> window.decorView.setOnSystemUiVisibilityChangeListener { visibility ->
isFullScreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0 isFullscreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0
toggleButtonVisibility() toggleButtonVisibility()
} }
} }
@ -148,26 +154,30 @@ open class PanoramaActivity : SimpleActivity() {
} }
private fun setupButtonMargins() { private fun setupButtonMargins() {
val navBarHeight = navigationBarHeight
(cardboard.layoutParams as RelativeLayout.LayoutParams).apply { (cardboard.layoutParams as RelativeLayout.LayoutParams).apply {
bottomMargin = navigationBarHeight bottomMargin = navBarHeight
rightMargin = navigationBarWidth rightMargin = navigationBarWidth
} }
(explore.layoutParams as RelativeLayout.LayoutParams).bottomMargin = navigationBarHeight (explore.layoutParams as RelativeLayout.LayoutParams).bottomMargin = navigationBarHeight
cardboard.onGlobalLayout {
panorama_gradient_background.layoutParams.height = navBarHeight + cardboard.height
}
} }
private fun toggleButtonVisibility() { private fun toggleButtonVisibility() {
cardboard.animate().alpha(if (isFullScreen) 0f else 1f) arrayOf(cardboard, explore, panorama_gradient_background).forEach {
cardboard.isClickable = !isFullScreen it.animate().alpha(if (isFullscreen) 0f else 1f)
it.isClickable = !isFullscreen
explore.animate().alpha(if (isFullScreen) 0f else 1f) }
explore.isClickable = !isFullScreen
} }
private fun handleClick() { private fun handleClick() {
isFullScreen = !isFullScreen isFullscreen = !isFullscreen
toggleButtonVisibility() toggleButtonVisibility()
if (isFullScreen) { if (isFullscreen) {
hideSystemUI(false) hideSystemUI(false)
} else { } else {
showSystemUI(false) showSystemUI(false)

View file

@ -0,0 +1,322 @@
package com.simplemobiletools.gallery.pro.activities
import android.content.res.Configuration
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.widget.RelativeLayout
import android.widget.SeekBar
import com.google.vr.sdk.widgets.video.VrVideoEventListener
import com.google.vr.sdk.widgets.video.VrVideoView
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.MIN_SKIP_LENGTH
import com.simplemobiletools.gallery.pro.helpers.PATH
import kotlinx.android.synthetic.main.activity_panorama_video.*
import kotlinx.android.synthetic.main.bottom_video_time_holder.*
import java.io.File
open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListener {
private val CARDBOARD_DISPLAY_MODE = 3
private var mIsFullscreen = false
private var mIsExploreEnabled = true
private var mIsRendering = false
private var mIsPlaying = false
private var mIsDragged = false
private var mPlayOnReady = false
private var mDuration = 0
private var mCurrTime = 0
private var mTimerHandler = Handler()
public override fun onCreate(savedInstanceState: Bundle?) {
useDynamicTheme = false
requestWindowFeature(Window.FEATURE_NO_TITLE)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_panorama_video)
supportActionBar?.hide()
checkNotchSupport()
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
checkIntent()
} else {
toast(R.string.no_storage_permissions)
finish()
}
}
}
override fun onResume() {
super.onResume()
vr_video_view.resumeRendering()
mIsRendering = true
if (config.blackBackground) {
updateStatusbarColor(Color.BLACK)
}
window.statusBarColor = resources.getColor(R.color.circle_black_background)
}
override fun onPause() {
super.onPause()
vr_video_view.pauseRendering()
mIsRendering = false
}
override fun onDestroy() {
super.onDestroy()
if (mIsRendering) {
vr_video_view.shutdown()
}
if (!isChangingConfigurations) {
mTimerHandler.removeCallbacksAndMessages(null)
}
}
private fun checkIntent() {
val path = intent.getStringExtra(PATH)
if (path == null) {
toast(R.string.invalid_image_path)
finish()
return
}
setupButtons()
intent.removeExtra(PATH)
video_curr_time.setOnClickListener { skip(false) }
video_duration.setOnClickListener { skip(true) }
try {
val options = VrVideoView.Options()
options.inputType = VrVideoView.Options.TYPE_MONO
vr_video_view.apply {
loadVideo(Uri.fromFile(File(path)), options)
pauseVideo()
setFlingingEnabled(true)
setPureTouchTracking(true)
// add custom buttons so we can position them and toggle visibility as desired
setFullscreenButtonEnabled(false)
setInfoButtonEnabled(false)
setTransitionViewEnabled(false)
setStereoModeButtonEnabled(false)
setOnClickListener {
handleClick()
}
setEventListener(object : VrVideoEventListener() {
override fun onClick() {
handleClick()
}
override fun onLoadSuccess() {
if (mDuration == 0) {
setupDuration(duration)
setupTimer()
}
if (mPlayOnReady || config.autoplayVideos) {
mPlayOnReady = false
mIsPlaying = true
resumeVideo()
} else {
video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline)
}
video_toggle_play_pause.beVisible()
}
override fun onCompletion() {
videoCompleted()
}
})
}
video_toggle_play_pause.setOnClickListener {
togglePlayPause()
}
} catch (e: Exception) {
showErrorToast(e)
}
window.decorView.setOnSystemUiVisibilityChangeListener { visibility ->
mIsFullscreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0
toggleButtonVisibility()
}
}
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
setupButtons()
}
private fun setupDuration(duration: Long) {
mDuration = (duration / 1000).toInt()
video_seekbar.max = mDuration
video_duration.text = mDuration.getFormattedDuration()
setVideoProgress(0)
}
private fun setupTimer() {
runOnUiThread(object : Runnable {
override fun run() {
if (mIsPlaying && !mIsDragged) {
mCurrTime = (vr_video_view!!.currentPosition / 1000).toInt()
video_seekbar.progress = mCurrTime
video_curr_time.text = mCurrTime.getFormattedDuration()
}
mTimerHandler.postDelayed(this, 1000)
}
})
}
private fun togglePlayPause() {
mIsPlaying = !mIsPlaying
if (mIsPlaying) {
resumeVideo()
} else {
pauseVideo()
}
}
private fun resumeVideo() {
video_toggle_play_pause.setImageResource(R.drawable.ic_pause_outline)
if (mCurrTime == mDuration) {
setVideoProgress(0)
mPlayOnReady = true
return
}
vr_video_view.playVideo()
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
private fun pauseVideo() {
vr_video_view.pauseVideo()
video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline)
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
private fun setVideoProgress(seconds: Int) {
vr_video_view.seekTo(seconds * 1000L)
video_seekbar.progress = seconds
mCurrTime = seconds
video_curr_time.text = seconds.getFormattedDuration()
}
private fun videoCompleted() {
mIsPlaying = false
mCurrTime = (vr_video_view.duration / 1000).toInt()
video_seekbar.progress = video_seekbar.max
video_curr_time.text = mDuration.getFormattedDuration()
pauseVideo()
}
private fun setupButtons() {
var right = 0
var bottom = 0
if (hasNavBar()) {
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
bottom += navigationBarHeight
} else {
right += navigationBarWidth
bottom += navigationBarHeight
}
}
video_time_holder.setPadding(0, 0, right, bottom)
video_time_holder.background = resources.getDrawable(R.drawable.gradient_background)
video_time_holder.onGlobalLayout {
val newBottomMargin = video_time_holder.height - resources.getDimension(R.dimen.video_player_play_pause_size).toInt() - resources.getDimension(R.dimen.activity_margin).toInt()
(explore.layoutParams as RelativeLayout.LayoutParams).bottomMargin = newBottomMargin
(cardboard.layoutParams as RelativeLayout.LayoutParams).apply {
bottomMargin = newBottomMargin
rightMargin = navigationBarWidth
}
explore.requestLayout()
}
video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline)
cardboard.setOnClickListener {
vr_video_view.displayMode = CARDBOARD_DISPLAY_MODE
}
explore.setOnClickListener {
mIsExploreEnabled = !mIsExploreEnabled
vr_video_view.setPureTouchTracking(mIsExploreEnabled)
explore.setImageResource(if (mIsExploreEnabled) R.drawable.ic_explore else R.drawable.ic_explore_off)
}
}
private fun toggleButtonVisibility() {
val newAlpha = if (mIsFullscreen) 0f else 1f
arrayOf(cardboard, explore).forEach {
it.animate().alpha(newAlpha)
}
arrayOf(cardboard, explore, video_toggle_play_pause, video_curr_time, video_duration).forEach {
it.isClickable = !mIsFullscreen
}
video_seekbar.setOnSeekBarChangeListener(if (mIsFullscreen) null else this)
video_time_holder.animate().alpha(newAlpha).start()
}
private fun handleClick() {
mIsFullscreen = !mIsFullscreen
toggleButtonVisibility()
if (mIsFullscreen) {
hideSystemUI(false)
} else {
showSystemUI(false)
}
}
private fun skip(forward: Boolean) {
if (forward && mCurrTime == mDuration) {
return
}
val curr = vr_video_view.currentPosition
val twoPercents = Math.max((vr_video_view.duration / 50).toInt(), MIN_SKIP_LENGTH)
val newProgress = if (forward) curr + twoPercents else curr - twoPercents
val roundProgress = Math.round(newProgress / 1000f)
val limitedProgress = Math.max(Math.min(vr_video_view.duration.toInt(), roundProgress), 0)
setVideoProgress(limitedProgress)
if (!mIsPlaying) {
togglePlayPause()
}
}
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
setVideoProgress(progress)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
vr_video_view.pauseVideo()
mIsDragged = true
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
mIsPlaying = true
resumeVideo()
mIsDragged = false
}
}

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.os.Bundle import android.os.Bundle

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
@ -14,19 +14,20 @@ import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.IS_FROM_GALLERY import com.simplemobiletools.commons.helpers.IS_FROM_GALLERY
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.commons.helpers.REAL_FILE_PATH import com.simplemobiletools.commons.helpers.REAL_FILE_PATH
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.fragments.PhotoFragment import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.fragments.VideoFragment import com.simplemobiletools.gallery.pro.fragments.PhotoFragment
import com.simplemobiletools.gallery.fragments.ViewPagerFragment import com.simplemobiletools.gallery.pro.fragments.VideoFragment
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.fragments.ViewPagerFragment
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.models.Medium
import kotlinx.android.synthetic.main.bottom_actions.* import kotlinx.android.synthetic.main.bottom_actions.*
import kotlinx.android.synthetic.main.fragment_holder.* import kotlinx.android.synthetic.main.fragment_holder.*
import java.io.File import java.io.File
import java.io.FileInputStream
open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentListener { open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentListener {
private var mMedium: Medium? = null private var mMedium: Medium? = null
private var mIsFullScreen = false private var mIsFullScreen = false
private var mIsFromGallery = false private var mIsFromGallery = false
@ -38,7 +39,6 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_holder) setContentView(R.layout.fragment_holder)
setTranslucentNavigation()
handlePermission(PERMISSION_WRITE_STORAGE) { handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) { if (it) {
@ -52,7 +52,15 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
supportActionBar?.setBackgroundDrawable(resources.getDrawable(R.drawable.actionbar_gradient_background)) supportActionBar?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
window.statusBarColor = Color.TRANSPARENT
if (config.bottomActions) {
window.navigationBarColor = Color.TRANSPARENT
} else {
setTranslucentNavigation()
}
if (config.blackBackground) { if (config.blackBackground) {
updateStatusbarColor(Color.BLACK) updateStatusbarColor(Color.BLACK)
} }
@ -60,22 +68,36 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
private fun checkIntent(savedInstanceState: Bundle? = null) { private fun checkIntent(savedInstanceState: Bundle? = null) {
mUri = intent.data ?: return mUri = intent.data ?: return
if (intent.extras?.containsKey(REAL_FILE_PATH) == true) { var filename = getFilenameFromUri(mUri!!)
val realPath = intent.extras.getString(REAL_FILE_PATH) mIsFromGallery = intent.getBooleanExtra(IS_FROM_GALLERY, false)
sendViewPagerIntent(realPath) if (mIsFromGallery && filename.isVideoFast() && config.openVideosOnSeparateScreen) {
finish() launchVideoPlayer()
return return
} }
mIsFromGallery = intent.getBooleanExtra(IS_FROM_GALLERY, false) if (intent.extras?.containsKey(REAL_FILE_PATH) == true) {
val realPath = intent.extras.getString(REAL_FILE_PATH)
if (realPath != null) {
if (realPath.getFilenameFromPath().contains('.') || filename.contains('.')) {
sendViewPagerIntent(realPath)
finish()
return
} else {
filename = realPath.getFilenameFromPath()
}
}
}
if (mUri!!.scheme == "file") { if (mUri!!.scheme == "file") {
scanPathRecursively(mUri!!.path) if (filename.contains('.')) {
sendViewPagerIntent(mUri!!.path) scanPathRecursively(mUri!!.path)
finish() sendViewPagerIntent(mUri!!.path)
return finish()
return
}
} else { } else {
val path = applicationContext.getRealPathFromURI(mUri!!) ?: "" val path = applicationContext.getRealPathFromURI(mUri!!) ?: ""
if (path != mUri.toString() && path.isNotEmpty() && mUri!!.authority != "mms") { if (path != mUri.toString() && path.isNotEmpty() && mUri!!.authority != "mms" && filename.contains('.')) {
scanPathRecursively(mUri!!.path) scanPathRecursively(mUri!!.path)
sendViewPagerIntent(path) sendViewPagerIntent(path)
finish() finish()
@ -83,10 +105,10 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
} }
} }
checkNotchSupport()
showSystemUI(true) showSystemUI(true)
val bundle = Bundle() val bundle = Bundle()
val file = File(mUri.toString()) val file = File(mUri.toString())
val filename = getFilenameFromUri(mUri!!)
val type = when { val type = when {
filename.isVideoFast() -> TYPE_VIDEOS filename.isVideoFast() -> TYPE_VIDEOS
filename.isGif() -> TYPE_GIFS filename.isGif() -> TYPE_GIFS
@ -95,7 +117,8 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
else -> TYPE_IMAGES else -> TYPE_IMAGES
} }
mMedium = Medium(null, filename, mUri.toString(), mUri!!.path.getParentPath(), 0, 0, file.length(), type, false, 0L) mIsVideo = type == TYPE_VIDEOS
mMedium = Medium(null, filename, mUri.toString(), mUri!!.path.getParentPath(), 0, 0, file.length(), type, 0, false, 0L)
supportActionBar?.title = mMedium!!.name supportActionBar?.title = mMedium!!.name
bundle.putSerializable(MEDIUM, mMedium) bundle.putSerializable(MEDIUM, mMedium)
@ -103,7 +126,7 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
mFragment = if (mIsVideo) VideoFragment() else PhotoFragment() mFragment = if (mIsVideo) VideoFragment() else PhotoFragment()
mFragment!!.listener = this mFragment!!.listener = this
mFragment!!.arguments = bundle mFragment!!.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.fragment_placeholder, mFragment).commit() supportFragmentManager.beginTransaction().replace(R.id.fragment_placeholder, mFragment!!).commit()
} }
if (config.blackBackground) { if (config.blackBackground) {
@ -118,6 +141,46 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
initBottomActions() initBottomActions()
} }
private fun launchVideoPlayer() {
val newUri = getFinalUriFromPath(mUri.toString(), BuildConfig.APPLICATION_ID)
if (newUri == null) {
toast(R.string.unknown_error_occurred)
return
}
var isPanorama = false
val realPath = intent?.extras?.getString(REAL_FILE_PATH) ?: ""
try {
if (realPath.isNotEmpty()) {
val fis = FileInputStream(File(realPath))
parseFileChannel(realPath, fis.channel, 0, 0, 0) {
isPanorama = true
}
}
} catch (ignored: Exception) {
} catch (ignored: OutOfMemoryError) {
}
if (isPanorama) {
Intent(applicationContext, PanoramaVideoActivity::class.java).apply {
putExtra(PATH, realPath)
startActivity(this)
}
} else {
val mimeType = getUriMimeType(mUri.toString(), newUri)
Intent(applicationContext, VideoPlayerActivity::class.java).apply {
setDataAndType(newUri, mimeType)
addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
if (intent.extras != null) {
putExtras(intent.extras!!)
}
startActivity(this)
}
}
finish()
}
override fun onConfigurationChanged(newConfig: Configuration?) { override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
initBottomActionsLayout() initBottomActionsLayout()
@ -181,7 +244,8 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
} }
private fun initBottomActionButtons() { private fun initBottomActionButtons() {
arrayListOf(bottom_favorite, bottom_delete, bottom_rotate, bottom_properties, bottom_change_orientation, bottom_slideshow, bottom_show_on_map, bottom_toggle_file_visibility).forEach { arrayListOf(bottom_favorite, bottom_delete, bottom_rotate, bottom_properties, bottom_change_orientation, bottom_slideshow, bottom_show_on_map,
bottom_toggle_file_visibility, bottom_rename, bottom_copy).forEach {
it.beGone() it.beGone()
} }
@ -214,8 +278,10 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
showSystemUI(true) showSystemUI(true)
} }
val newAlpha = if (mIsFullScreen) 0f else 1f
top_shadow.animate().alpha(newAlpha).start()
if (!bottom_actions.isGone()) { if (!bottom_actions.isGone()) {
bottom_actions.animate().alpha(if (mIsFullScreen) 0f else 1f).start() bottom_actions.animate().alpha(newAlpha).start()
} }
} }
@ -224,4 +290,6 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
override fun goToPrevItem() {} override fun goToPrevItem() {}
override fun goToNextItem() {} override fun goToNextItem() {}
override fun launchViewVideoIntent(path: String) {}
} }

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
@ -10,11 +10,10 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.isActivityDestroyed
import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.isNougatPlus import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.theartofdev.edmodo.cropper.CropImageView import com.theartofdev.edmodo.cropper.CropImageView
import kotlinx.android.synthetic.main.activity_set_wallpaper.* import kotlinx.android.synthetic.main.activity_set_wallpaper.*
import kotlinx.android.synthetic.main.bottom_set_wallpaper_actions.* import kotlinx.android.synthetic.main.bottom_set_wallpaper_actions.*
@ -113,7 +112,7 @@ class SetWallpaperActivity : SimpleActivity(), CropImageView.OnCropImageComplete
@SuppressLint("NewApi") @SuppressLint("NewApi")
override fun onCropImageComplete(view: CropImageView?, result: CropImageView.CropResult) { override fun onCropImageComplete(view: CropImageView?, result: CropImageView.CropResult) {
if (isActivityDestroyed()) if (isDestroyed)
return return
if (result.error == null) { if (result.error == null) {

View file

@ -0,0 +1,773 @@
package com.simplemobiletools.gallery.pro.activities
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.dialogs.SecurityDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.dialogs.ManageBottomActionsDialog
import com.simplemobiletools.gallery.pro.dialogs.ManageExtendedDetailsDialog
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.extensions.emptyTheRecycleBin
import com.simplemobiletools.gallery.pro.extensions.galleryDB
import com.simplemobiletools.gallery.pro.extensions.showRecycleBinEmptyingDialog
import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.activity_settings.*
import java.io.File
import java.util.*
class SettingsActivity : SimpleActivity() {
private var mRecycleBinContentSize = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
}
override fun onResume() {
super.onResume()
setupSettingItems()
}
private fun setupSettingItems() {
setupCustomizeColors()
setupUseEnglish()
setupManageIncludedFolders()
setupManageExcludedFolders()
setupManageHiddenFolders()
setupShowHiddenItems()
setupDoExtraCheck()
setupAutoplayVideos()
setupRememberLastVideo()
setupLoopVideos()
setupOpenVideosOnSeparateScreen()
setupAnimateGifs()
setupMaxBrightness()
setupCropThumbnails()
setupDarkBackground()
setupScrollHorizontally()
setupScreenRotation()
setupHideSystemUI()
setupHiddenItemPasswordProtection()
setupAppPasswordProtection()
setupFileDeletionPasswordProtection()
setupDeleteEmptyFolders()
setupAllowPhotoGestures()
setupAllowVideoGestures()
setupAllowDownGesture()
setupShowNotch()
setupBottomActions()
setupThumbnailVideoDuration()
setupShowMediaCount()
setupKeepLastModified()
setupShowInfoBubble()
setupEnablePullToRefresh()
setupAllowZoomingImages()
setupShowHighestQuality()
setupAllowOneToOneZoom()
setupAllowInstantChange()
setupShowExtendedDetails()
setupHideExtendedDetails()
setupManageExtendedDetails()
setupSkipDeleteConfirmation()
setupManageBottomActions()
setupUseRecycleBin()
setupShowRecycleBin()
setupShowRecycleBinLast()
setupEmptyRecycleBin()
updateTextColors(settings_holder)
setupSectionColors()
setupExportSettings()
setupImportSettings()
}
private fun setupSectionColors() {
val adjustedPrimaryColor = getAdjustedPrimaryColor()
arrayListOf(visibility_label, videos_label, thumbnails_label, scrolling_label, fullscreen_media_label, security_label,
file_operations_label, deep_zoomable_images_label, extended_details_label, bottom_actions_label, recycle_bin_label,
migrating_label).forEach {
it.setTextColor(adjustedPrimaryColor)
}
}
private fun setupCustomizeColors() {
settings_customize_colors_holder.setOnClickListener {
startCustomizationActivity()
}
}
private fun setupUseEnglish() {
settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en")
settings_use_english.isChecked = config.useEnglish
settings_use_english_holder.setOnClickListener {
settings_use_english.toggle()
config.useEnglish = settings_use_english.isChecked
System.exit(0)
}
}
private fun setupManageIncludedFolders() {
settings_manage_included_folders_holder.setOnClickListener {
startActivity(Intent(this, IncludedFoldersActivity::class.java))
}
}
private fun setupManageExcludedFolders() {
settings_manage_excluded_folders_holder.setOnClickListener {
startActivity(Intent(this, ExcludedFoldersActivity::class.java))
}
}
private fun setupManageHiddenFolders() {
settings_manage_hidden_folders_holder.setOnClickListener {
handleHiddenFolderPasswordProtection {
startActivity(Intent(this, HiddenFoldersActivity::class.java))
}
}
}
private fun setupShowHiddenItems() {
settings_show_hidden_items.isChecked = config.showHiddenMedia
settings_show_hidden_items_holder.setOnClickListener {
if (config.showHiddenMedia) {
toggleHiddenItems()
} else {
handleHiddenFolderPasswordProtection {
toggleHiddenItems()
}
}
}
}
private fun toggleHiddenItems() {
settings_show_hidden_items.toggle()
config.showHiddenMedia = settings_show_hidden_items.isChecked
}
private fun setupDoExtraCheck() {
settings_do_extra_check.isChecked = config.doExtraCheck
settings_do_extra_check_holder.setOnClickListener {
settings_do_extra_check.toggle()
config.doExtraCheck = settings_do_extra_check.isChecked
}
}
private fun setupAutoplayVideos() {
settings_autoplay_videos.isChecked = config.autoplayVideos
settings_autoplay_videos_holder.setOnClickListener {
settings_autoplay_videos.toggle()
config.autoplayVideos = settings_autoplay_videos.isChecked
}
}
private fun setupRememberLastVideo() {
settings_remember_last_video_position.isChecked = config.rememberLastVideoPosition
settings_remember_last_video_position_holder.setOnClickListener {
settings_remember_last_video_position.toggle()
config.rememberLastVideoPosition = settings_remember_last_video_position.isChecked
}
}
private fun setupLoopVideos() {
settings_loop_videos.isChecked = config.loopVideos
settings_loop_videos_holder.setOnClickListener {
settings_loop_videos.toggle()
config.loopVideos = settings_loop_videos.isChecked
}
}
private fun setupOpenVideosOnSeparateScreen() {
settings_open_videos_on_separate_screen.isChecked = config.openVideosOnSeparateScreen
settings_open_videos_on_separate_screen_holder.setOnClickListener {
settings_open_videos_on_separate_screen.toggle()
config.openVideosOnSeparateScreen = settings_open_videos_on_separate_screen.isChecked
}
}
private fun setupAnimateGifs() {
settings_animate_gifs.isChecked = config.animateGifs
settings_animate_gifs_holder.setOnClickListener {
settings_animate_gifs.toggle()
config.animateGifs = settings_animate_gifs.isChecked
}
}
private fun setupMaxBrightness() {
settings_max_brightness.isChecked = config.maxBrightness
settings_max_brightness_holder.setOnClickListener {
settings_max_brightness.toggle()
config.maxBrightness = settings_max_brightness.isChecked
}
}
private fun setupCropThumbnails() {
settings_crop_thumbnails.isChecked = config.cropThumbnails
settings_crop_thumbnails_holder.setOnClickListener {
settings_crop_thumbnails.toggle()
config.cropThumbnails = settings_crop_thumbnails.isChecked
}
}
private fun setupThumbnailVideoDuration() {
settings_show_thumbnail_video_duration.isChecked = config.showThumbnailVideoDuration
settings_show_thumbnail_video_duration_holder.setOnClickListener {
settings_show_thumbnail_video_duration.toggle()
config.showThumbnailVideoDuration = settings_show_thumbnail_video_duration.isChecked
}
}
private fun setupDarkBackground() {
settings_black_background.isChecked = config.blackBackground
settings_black_background_holder.setOnClickListener {
settings_black_background.toggle()
config.blackBackground = settings_black_background.isChecked
}
}
private fun setupScrollHorizontally() {
settings_scroll_horizontally.isChecked = config.scrollHorizontally
settings_scroll_horizontally_holder.setOnClickListener {
settings_scroll_horizontally.toggle()
config.scrollHorizontally = settings_scroll_horizontally.isChecked
if (config.scrollHorizontally) {
config.enablePullToRefresh = false
settings_enable_pull_to_refresh.isChecked = false
}
}
}
private fun setupHideSystemUI() {
settings_hide_system_ui.isChecked = config.hideSystemUI
settings_hide_system_ui_holder.setOnClickListener {
settings_hide_system_ui.toggle()
config.hideSystemUI = settings_hide_system_ui.isChecked
}
}
private fun setupHiddenItemPasswordProtection() {
settings_hidden_item_password_protection.isChecked = config.isHiddenPasswordProtectionOn
settings_hidden_item_password_protection_holder.setOnClickListener {
val tabToShow = if (config.isHiddenPasswordProtectionOn) config.hiddenProtectionType else SHOW_ALL_TABS
SecurityDialog(this, config.hiddenPasswordHash, tabToShow) { hash, type, success ->
if (success) {
val hasPasswordProtection = config.isHiddenPasswordProtectionOn
settings_hidden_item_password_protection.isChecked = !hasPasswordProtection
config.isHiddenPasswordProtectionOn = !hasPasswordProtection
config.hiddenPasswordHash = if (hasPasswordProtection) "" else hash
config.hiddenProtectionType = type
if (config.isHiddenPasswordProtectionOn) {
val confirmationTextId = if (config.hiddenProtectionType == PROTECTION_FINGERPRINT)
R.string.fingerprint_setup_successfully else R.string.protection_setup_successfully
ConfirmationDialog(this, "", confirmationTextId, R.string.ok, 0) { }
}
}
}
}
}
private fun setupAppPasswordProtection() {
settings_app_password_protection.isChecked = config.isAppPasswordProtectionOn
settings_app_password_protection_holder.setOnClickListener {
val tabToShow = if (config.isAppPasswordProtectionOn) config.appProtectionType else SHOW_ALL_TABS
SecurityDialog(this, config.appPasswordHash, tabToShow) { hash, type, success ->
if (success) {
val hasPasswordProtection = config.isAppPasswordProtectionOn
settings_app_password_protection.isChecked = !hasPasswordProtection
config.isAppPasswordProtectionOn = !hasPasswordProtection
config.appPasswordHash = if (hasPasswordProtection) "" else hash
config.appProtectionType = type
if (config.isAppPasswordProtectionOn) {
val confirmationTextId = if (config.appProtectionType == PROTECTION_FINGERPRINT)
R.string.fingerprint_setup_successfully else R.string.protection_setup_successfully
ConfirmationDialog(this, "", confirmationTextId, R.string.ok, 0) { }
}
}
}
}
}
private fun setupFileDeletionPasswordProtection() {
settings_file_deletion_password_protection.isChecked = config.isDeletePasswordProtectionOn
settings_file_deletion_password_protection_holder.setOnClickListener {
val tabToShow = if (config.isDeletePasswordProtectionOn) config.deleteProtectionType else SHOW_ALL_TABS
SecurityDialog(this, config.deletePasswordHash, tabToShow) { hash, type, success ->
if (success) {
val hasPasswordProtection = config.isDeletePasswordProtectionOn
settings_file_deletion_password_protection.isChecked = !hasPasswordProtection
config.isDeletePasswordProtectionOn = !hasPasswordProtection
config.deletePasswordHash = if (hasPasswordProtection) "" else hash
config.deleteProtectionType = type
if (config.isDeletePasswordProtectionOn) {
val confirmationTextId = if (config.deleteProtectionType == PROTECTION_FINGERPRINT)
R.string.fingerprint_setup_successfully else R.string.protection_setup_successfully
ConfirmationDialog(this, "", confirmationTextId, R.string.ok, 0) { }
}
}
}
}
}
private fun setupDeleteEmptyFolders() {
settings_delete_empty_folders.isChecked = config.deleteEmptyFolders
settings_delete_empty_folders_holder.setOnClickListener {
settings_delete_empty_folders.toggle()
config.deleteEmptyFolders = settings_delete_empty_folders.isChecked
}
}
private fun setupAllowPhotoGestures() {
settings_allow_photo_gestures.isChecked = config.allowPhotoGestures
settings_allow_photo_gestures_holder.setOnClickListener {
settings_allow_photo_gestures.toggle()
config.allowPhotoGestures = settings_allow_photo_gestures.isChecked
}
}
private fun setupAllowVideoGestures() {
settings_allow_video_gestures.isChecked = config.allowVideoGestures
settings_allow_video_gestures_holder.setOnClickListener {
settings_allow_video_gestures.toggle()
config.allowVideoGestures = settings_allow_video_gestures.isChecked
}
}
private fun setupAllowDownGesture() {
settings_allow_down_gesture.isChecked = config.allowDownGesture
settings_allow_down_gesture_holder.setOnClickListener {
settings_allow_down_gesture.toggle()
config.allowDownGesture = settings_allow_down_gesture.isChecked
}
}
private fun setupShowNotch() {
settings_show_notch_holder.beVisibleIf(isPiePlus())
settings_show_notch.isChecked = config.showNotch
settings_show_notch_holder.setOnClickListener {
settings_show_notch.toggle()
config.showNotch = settings_show_notch.isChecked
}
}
private fun setupShowMediaCount() {
settings_show_media_count.isChecked = config.showMediaCount
settings_show_media_count_holder.setOnClickListener {
settings_show_media_count.toggle()
config.showMediaCount = settings_show_media_count.isChecked
}
}
private fun setupKeepLastModified() {
settings_keep_last_modified.isChecked = config.keepLastModified
settings_keep_last_modified_holder.setOnClickListener {
settings_keep_last_modified.toggle()
config.keepLastModified = settings_keep_last_modified.isChecked
}
}
private fun setupShowInfoBubble() {
settings_show_info_bubble.isChecked = config.showInfoBubble
settings_show_info_bubble_holder.setOnClickListener {
settings_show_info_bubble.toggle()
config.showInfoBubble = settings_show_info_bubble.isChecked
}
}
private fun setupEnablePullToRefresh() {
settings_enable_pull_to_refresh.isChecked = config.enablePullToRefresh
settings_enable_pull_to_refresh_holder.setOnClickListener {
settings_enable_pull_to_refresh.toggle()
config.enablePullToRefresh = settings_enable_pull_to_refresh.isChecked
}
}
private fun setupAllowZoomingImages() {
settings_allow_zooming_images.isChecked = config.allowZoomingImages
updateDeepZoomToggleButtons()
settings_allow_zooming_images_holder.setOnClickListener {
settings_allow_zooming_images.toggle()
config.allowZoomingImages = settings_allow_zooming_images.isChecked
updateDeepZoomToggleButtons()
}
}
private fun updateDeepZoomToggleButtons() {
settings_show_highest_quality_holder.beVisibleIf(config.allowZoomingImages)
settings_allow_one_to_one_zoom_holder.beVisibleIf(config.allowZoomingImages)
}
private fun setupShowHighestQuality() {
settings_show_highest_quality.isChecked = config.showHighestQuality
settings_show_highest_quality_holder.setOnClickListener {
settings_show_highest_quality.toggle()
config.showHighestQuality = settings_show_highest_quality.isChecked
}
}
private fun setupAllowOneToOneZoom() {
settings_allow_one_to_one_zoom.isChecked = config.allowOneToOneZoom
settings_allow_one_to_one_zoom_holder.setOnClickListener {
settings_allow_one_to_one_zoom.toggle()
config.allowOneToOneZoom = settings_allow_one_to_one_zoom.isChecked
}
}
private fun setupAllowInstantChange() {
settings_allow_instant_change.isChecked = config.allowInstantChange
settings_allow_instant_change_holder.setOnClickListener {
settings_allow_instant_change.toggle()
config.allowInstantChange = settings_allow_instant_change.isChecked
}
}
private fun setupShowExtendedDetails() {
settings_show_extended_details.isChecked = config.showExtendedDetails
settings_show_extended_details_holder.setOnClickListener {
settings_show_extended_details.toggle()
config.showExtendedDetails = settings_show_extended_details.isChecked
settings_manage_extended_details_holder.beVisibleIf(config.showExtendedDetails)
settings_hide_extended_details_holder.beVisibleIf(config.showExtendedDetails)
}
}
private fun setupHideExtendedDetails() {
settings_hide_extended_details_holder.beVisibleIf(config.showExtendedDetails)
settings_hide_extended_details.isChecked = config.hideExtendedDetails
settings_hide_extended_details_holder.setOnClickListener {
settings_hide_extended_details.toggle()
config.hideExtendedDetails = settings_hide_extended_details.isChecked
}
}
private fun setupManageExtendedDetails() {
settings_manage_extended_details_holder.beVisibleIf(config.showExtendedDetails)
settings_manage_extended_details_holder.setOnClickListener {
ManageExtendedDetailsDialog(this) {
if (config.extendedDetails == 0) {
settings_show_extended_details_holder.callOnClick()
}
}
}
}
private fun setupSkipDeleteConfirmation() {
settings_skip_delete_confirmation.isChecked = config.skipDeleteConfirmation
settings_skip_delete_confirmation_holder.setOnClickListener {
settings_skip_delete_confirmation.toggle()
config.skipDeleteConfirmation = settings_skip_delete_confirmation.isChecked
}
}
private fun setupScreenRotation() {
settings_screen_rotation.text = getScreenRotationText()
settings_screen_rotation_holder.setOnClickListener {
val items = arrayListOf(
RadioItem(ROTATE_BY_SYSTEM_SETTING, getString(R.string.screen_rotation_system_setting)),
RadioItem(ROTATE_BY_DEVICE_ROTATION, getString(R.string.screen_rotation_device_rotation)),
RadioItem(ROTATE_BY_ASPECT_RATIO, getString(R.string.screen_rotation_aspect_ratio)))
RadioGroupDialog(this@SettingsActivity, items, config.screenRotation) {
config.screenRotation = it as Int
settings_screen_rotation.text = getScreenRotationText()
}
}
}
private fun getScreenRotationText() = getString(when (config.screenRotation) {
ROTATE_BY_SYSTEM_SETTING -> R.string.screen_rotation_system_setting
ROTATE_BY_DEVICE_ROTATION -> R.string.screen_rotation_device_rotation
else -> R.string.screen_rotation_aspect_ratio
})
private fun setupBottomActions() {
settings_bottom_actions.isChecked = config.bottomActions
settings_bottom_actions_holder.setOnClickListener {
settings_bottom_actions.toggle()
config.bottomActions = settings_bottom_actions.isChecked
settings_manage_bottom_actions_holder.beVisibleIf(config.bottomActions)
}
}
private fun setupManageBottomActions() {
settings_manage_bottom_actions_holder.beVisibleIf(config.bottomActions)
settings_manage_bottom_actions_holder.setOnClickListener {
ManageBottomActionsDialog(this) {
if (config.visibleBottomActions == 0) {
settings_bottom_actions_holder.callOnClick()
config.bottomActions = false
config.visibleBottomActions = DEFAULT_BOTTOM_ACTIONS
}
}
}
}
private fun setupUseRecycleBin() {
settings_empty_recycle_bin_holder.beVisibleIf(config.useRecycleBin)
settings_show_recycle_bin_holder.beVisibleIf(config.useRecycleBin)
settings_show_recycle_bin_last_holder.beVisibleIf(config.useRecycleBin && config.showRecycleBinAtFolders)
settings_use_recycle_bin.isChecked = config.useRecycleBin
settings_use_recycle_bin_holder.setOnClickListener {
settings_use_recycle_bin.toggle()
config.useRecycleBin = settings_use_recycle_bin.isChecked
settings_empty_recycle_bin_holder.beVisibleIf(config.useRecycleBin)
settings_show_recycle_bin_holder.beVisibleIf(config.useRecycleBin)
settings_show_recycle_bin_last_holder.beVisibleIf(config.useRecycleBin && config.showRecycleBinAtFolders)
}
}
private fun setupShowRecycleBin() {
settings_show_recycle_bin.isChecked = config.showRecycleBinAtFolders
settings_show_recycle_bin_holder.setOnClickListener {
settings_show_recycle_bin.toggle()
config.showRecycleBinAtFolders = settings_show_recycle_bin.isChecked
settings_show_recycle_bin_last_holder.beVisibleIf(config.useRecycleBin && config.showRecycleBinAtFolders)
}
}
private fun setupShowRecycleBinLast() {
settings_show_recycle_bin_last.isChecked = config.showRecycleBinLast
settings_show_recycle_bin_last_holder.setOnClickListener {
settings_show_recycle_bin_last.toggle()
config.showRecycleBinLast = settings_show_recycle_bin_last.isChecked
if (config.showRecycleBinLast) {
config.removePinnedFolders(setOf(RECYCLE_BIN))
}
}
}
private fun setupEmptyRecycleBin() {
Thread {
mRecycleBinContentSize = galleryDB.MediumDao().getDeletedMedia().sumByLong { it.size }
runOnUiThread {
settings_empty_recycle_bin_size.text = mRecycleBinContentSize.formatSize()
}
}.start()
settings_empty_recycle_bin_holder.setOnClickListener {
if (mRecycleBinContentSize == 0L) {
toast(R.string.recycle_bin_empty)
} else {
showRecycleBinEmptyingDialog {
emptyTheRecycleBin()
mRecycleBinContentSize = 0L
settings_empty_recycle_bin_size.text = 0L.formatSize()
}
}
}
}
private fun setupExportSettings() {
settings_export_holder.setOnClickListener {
val configItems = LinkedHashMap<String, Any>().apply {
put(IS_USING_SHARED_THEME, config.isUsingSharedTheme)
put(TEXT_COLOR, config.textColor)
put(BACKGROUND_COLOR, config.backgroundColor)
put(PRIMARY_COLOR, config.primaryColor)
put(APP_ICON_COLOR, config.appIconColor)
put(USE_ENGLISH, config.useEnglish)
put(WAS_USE_ENGLISH_TOGGLED, config.wasUseEnglishToggled)
put(INCLUDED_FOLDERS, TextUtils.join(",", config.includedFolders))
put(EXCLUDED_FOLDERS, TextUtils.join(",", config.excludedFolders))
put(SHOW_HIDDEN_MEDIA, config.showHiddenMedia)
put(DO_EXTRA_CHECK, config.doExtraCheck)
put(AUTOPLAY_VIDEOS, config.autoplayVideos)
put(REMEMBER_LAST_VIDEO_POSITION, config.rememberLastVideoPosition)
put(LAST_VIDEO_PATH, config.lastVideoPath)
put(LOOP_VIDEOS, config.loopVideos)
put(OPEN_VIDEOS_ON_SEPARATE_SCREEN, config.openVideosOnSeparateScreen)
put(ALLOW_VIDEO_GESTURES, config.allowVideoGestures)
put(ANIMATE_GIFS, config.animateGifs)
put(CROP_THUMBNAILS, config.cropThumbnails)
put(SHOW_THUMBNAIL_VIDEO_DURATION, config.showThumbnailVideoDuration)
put(SHOW_MEDIA_COUNT, config.showMediaCount)
put(SHOW_INFO_BUBBLE, config.showInfoBubble)
put(SCROLL_HORIZONTALLY, config.scrollHorizontally)
put(ENABLE_PULL_TO_REFRESH, config.enablePullToRefresh)
put(MAX_BRIGHTNESS, config.maxBrightness)
put(BLACK_BACKGROUND, config.blackBackground)
put(HIDE_SYSTEM_UI, config.hideSystemUI)
put(ALLOW_INSTANT_CHANGE, config.allowInstantChange)
put(ALLOW_PHOTO_GESTURES, config.allowPhotoGestures)
put(ALLOW_DOWN_GESTURE, config.allowDownGesture)
put(SHOW_NOTCH, config.showNotch)
put(SCREEN_ROTATION, config.screenRotation)
put(ALLOW_ZOOMING_IMAGES, config.allowZoomingImages)
put(SHOW_HIGHEST_QUALITY, config.showHighestQuality)
put(ALLOW_ONE_TO_ONE_ZOOM, config.allowOneToOneZoom)
put(SHOW_EXTENDED_DETAILS, config.showExtendedDetails)
put(HIDE_EXTENDED_DETAILS, config.hideExtendedDetails)
put(EXTENDED_DETAILS, config.extendedDetails)
put(DELETE_EMPTY_FOLDERS, config.deleteEmptyFolders)
put(KEEP_LAST_MODIFIED, config.keepLastModified)
put(SKIP_DELETE_CONFIRMATION, config.skipDeleteConfirmation)
put(BOTTOM_ACTIONS, config.bottomActions)
put(VISIBLE_BOTTOM_ACTIONS, config.visibleBottomActions)
put(USE_RECYCLE_BIN, config.useRecycleBin)
put(SHOW_RECYCLE_BIN_AT_FOLDERS, config.showRecycleBinAtFolders)
put(SHOW_RECYCLE_BIN_LAST, config.showRecycleBinLast)
put(SORT_ORDER, config.sorting)
put(DIRECTORY_SORT_ORDER, config.directorySorting)
put(GROUP_BY, config.groupBy)
put(GROUP_DIRECT_SUBFOLDERS, config.groupDirectSubfolders)
put(PINNED_FOLDERS, TextUtils.join(",", config.pinnedFolders))
put(DISPLAY_FILE_NAMES, config.displayFileNames)
put(FILTER_MEDIA, config.filterMedia)
put(DIR_COLUMN_CNT, config.dirColumnCnt)
put(MEDIA_COLUMN_CNT, config.mediaColumnCnt)
put(SHOW_ALL, config.showAll)
put(SHOW_WIDGET_FOLDER_NAME, config.showWidgetFolderName)
put(WIDGET_BG_COLOR, config.widgetBgColor)
put(WIDGET_TEXT_COLOR, config.widgetTextColor)
put(VIEW_TYPE_FILES, config.viewTypeFiles)
put(VIEW_TYPE_FOLDERS, config.viewTypeFolders)
put(SLIDESHOW_INTERVAL, config.slideshowInterval)
put(SLIDESHOW_INCLUDE_VIDEOS, config.slideshowIncludeVideos)
put(SLIDESHOW_INCLUDE_GIFS, config.slideshowIncludeGIFs)
put(SLIDESHOW_RANDOM_ORDER, config.slideshowRandomOrder)
put(SLIDESHOW_MOVE_BACKWARDS, config.slideshowMoveBackwards)
put(SLIDESHOW_LOOP, config.loopSlideshow)
put(LAST_EDITOR_CROP_ASPECT_RATIO, config.lastEditorCropAspectRatio)
put(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X, config.lastEditorCropOtherAspectRatioX)
put(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y, config.lastEditorCropOtherAspectRatioY)
put(LAST_EDITOR_DRAW_COLOR, config.lastEditorDrawColor)
put(LAST_EDITOR_BRUSH_SIZE, config.lastEditorBrushSize)
put(LAST_CONFLICT_RESOLUTION, config.lastConflictResolution)
put(LAST_CONFLICT_APPLY_TO_ALL, config.lastConflictApplyToAll)
}
exportSettings(configItems, "gallery-settings.txt")
}
}
private fun setupImportSettings() {
settings_import_holder.setOnClickListener {
FilePickerDialog(this) {
Thread {
try {
parseFile(it)
} catch (e: Exception) {
showErrorToast(e)
}
}.start()
}
}
}
private fun parseFile(path: String) {
val inputStream = File(path).inputStream()
var importedItems = 0
val configValues = LinkedHashMap<String, Any>()
inputStream.bufferedReader().use {
while (true) {
try {
val line = it.readLine() ?: break
val split = line.split("=".toRegex(), 2)
if (split.size == 2) {
configValues[split[0]] = split[1]
}
importedItems++
} catch (e: Exception) {
showErrorToast(e)
}
}
}
for ((key, value) in configValues) {
when (key) {
IS_USING_SHARED_THEME -> config.isUsingSharedTheme = value.toBoolean()
TEXT_COLOR -> config.textColor = value.toInt()
BACKGROUND_COLOR -> config.backgroundColor = value.toInt()
PRIMARY_COLOR -> config.primaryColor = value.toInt()
APP_ICON_COLOR -> {
if (getAppIconColors().contains(value.toInt())) {
config.appIconColor = value.toInt()
checkAppIconColor()
}
}
USE_ENGLISH -> config.useEnglish = value.toBoolean()
WAS_USE_ENGLISH_TOGGLED -> config.wasUseEnglishToggled = value.toBoolean()
INCLUDED_FOLDERS -> config.addIncludedFolders(value.toStringSet())
EXCLUDED_FOLDERS -> config.addExcludedFolders(value.toStringSet())
SHOW_HIDDEN_MEDIA -> config.showHiddenMedia = value.toBoolean()
DO_EXTRA_CHECK -> config.doExtraCheck = value.toBoolean()
AUTOPLAY_VIDEOS -> config.autoplayVideos = value.toBoolean()
REMEMBER_LAST_VIDEO_POSITION -> config.rememberLastVideoPosition = value.toBoolean()
LAST_VIDEO_PATH -> config.lastVideoPath = value.toString()
LOOP_VIDEOS -> config.loopVideos = value.toBoolean()
OPEN_VIDEOS_ON_SEPARATE_SCREEN -> config.openVideosOnSeparateScreen = value.toBoolean()
ALLOW_VIDEO_GESTURES -> config.allowVideoGestures = value.toBoolean()
ANIMATE_GIFS -> config.animateGifs = value.toBoolean()
CROP_THUMBNAILS -> config.cropThumbnails = value.toBoolean()
SHOW_THUMBNAIL_VIDEO_DURATION -> config.showThumbnailVideoDuration = value.toBoolean()
SHOW_MEDIA_COUNT -> config.showMediaCount = value.toBoolean()
SHOW_INFO_BUBBLE -> config.showInfoBubble = value.toBoolean()
SCROLL_HORIZONTALLY -> config.scrollHorizontally = value.toBoolean()
ENABLE_PULL_TO_REFRESH -> config.enablePullToRefresh = value.toBoolean()
MAX_BRIGHTNESS -> config.maxBrightness = value.toBoolean()
BLACK_BACKGROUND -> config.blackBackground = value.toBoolean()
HIDE_SYSTEM_UI -> config.hideSystemUI = value.toBoolean()
ALLOW_INSTANT_CHANGE -> config.allowInstantChange = value.toBoolean()
ALLOW_PHOTO_GESTURES -> config.allowPhotoGestures = value.toBoolean()
ALLOW_DOWN_GESTURE -> config.allowDownGesture = value.toBoolean()
SHOW_NOTCH -> config.showNotch = value.toBoolean()
SCREEN_ROTATION -> config.screenRotation = value.toInt()
ALLOW_ZOOMING_IMAGES -> config.allowZoomingImages = value.toBoolean()
SHOW_HIGHEST_QUALITY -> config.showHighestQuality = value.toBoolean()
ALLOW_ONE_TO_ONE_ZOOM -> config.allowOneToOneZoom = value.toBoolean()
SHOW_EXTENDED_DETAILS -> config.showExtendedDetails = value.toBoolean()
HIDE_EXTENDED_DETAILS -> config.hideExtendedDetails = value.toBoolean()
EXTENDED_DETAILS -> config.extendedDetails = value.toInt()
DELETE_EMPTY_FOLDERS -> config.deleteEmptyFolders = value.toBoolean()
KEEP_LAST_MODIFIED -> config.keepLastModified = value.toBoolean()
SKIP_DELETE_CONFIRMATION -> config.skipDeleteConfirmation = value.toBoolean()
BOTTOM_ACTIONS -> config.bottomActions = value.toBoolean()
VISIBLE_BOTTOM_ACTIONS -> config.visibleBottomActions = value.toInt()
USE_RECYCLE_BIN -> config.useRecycleBin = value.toBoolean()
SHOW_RECYCLE_BIN_AT_FOLDERS -> config.showRecycleBinAtFolders = value.toBoolean()
SHOW_RECYCLE_BIN_LAST -> config.showRecycleBinLast = value.toBoolean()
SORT_ORDER -> config.sorting = value.toInt()
DIRECTORY_SORT_ORDER -> config.directorySorting = value.toInt()
GROUP_BY -> config.groupBy = value.toInt()
GROUP_DIRECT_SUBFOLDERS -> config.groupDirectSubfolders = value.toBoolean()
PINNED_FOLDERS -> config.addPinnedFolders(value.toStringSet())
DISPLAY_FILE_NAMES -> config.displayFileNames = value.toBoolean()
FILTER_MEDIA -> config.filterMedia = value.toInt()
DIR_COLUMN_CNT -> config.dirColumnCnt = value.toInt()
MEDIA_COLUMN_CNT -> config.mediaColumnCnt = value.toInt()
SHOW_ALL -> config.showAll = value.toBoolean()
SHOW_WIDGET_FOLDER_NAME -> config.showWidgetFolderName = value.toBoolean()
WIDGET_BG_COLOR -> config.widgetBgColor = value.toInt()
WIDGET_TEXT_COLOR -> config.widgetTextColor = value.toInt()
VIEW_TYPE_FILES -> config.viewTypeFiles = value.toInt()
VIEW_TYPE_FOLDERS -> config.viewTypeFolders = value.toInt()
SLIDESHOW_INTERVAL -> config.slideshowInterval = value.toInt()
SLIDESHOW_INCLUDE_VIDEOS -> config.slideshowIncludeVideos = value.toBoolean()
SLIDESHOW_INCLUDE_GIFS -> config.slideshowIncludeGIFs = value.toBoolean()
SLIDESHOW_RANDOM_ORDER -> config.slideshowRandomOrder = value.toBoolean()
SLIDESHOW_MOVE_BACKWARDS -> config.slideshowMoveBackwards = value.toBoolean()
SLIDESHOW_LOOP -> config.loopSlideshow = value.toBoolean()
LAST_EDITOR_CROP_ASPECT_RATIO -> config.lastEditorCropAspectRatio = value.toInt()
LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X -> config.lastEditorCropOtherAspectRatioX = value.toInt()
LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y -> config.lastEditorCropOtherAspectRatioY = value.toInt()
LAST_EDITOR_DRAW_COLOR -> config.lastEditorDrawColor = value.toInt()
LAST_EDITOR_BRUSH_SIZE -> config.lastEditorBrushSize = value.toInt()
LAST_CONFLICT_RESOLUTION -> config.lastConflictResolution = value.toInt()
LAST_CONFLICT_APPLY_TO_ALL -> config.lastConflictApplyToAll = value.toBoolean()
}
}
toast(if (configValues.size > 0) R.string.settings_imported_successfully else R.string.no_entries_for_importing)
runOnUiThread {
setupSettingItems()
}
}
}

View file

@ -1,7 +1,11 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.annotation.SuppressLint
import android.view.WindowManager
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.gallery.R import com.simplemobiletools.commons.helpers.isPiePlus
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
open class SimpleActivity : BaseSimpleActivity() { open class SimpleActivity : BaseSimpleActivity() {
override fun getAppIconIDs() = arrayListOf( override fun getAppIconIDs() = arrayListOf(
@ -27,4 +31,19 @@ open class SimpleActivity : BaseSimpleActivity() {
) )
override fun getAppLauncherName() = getString(R.string.app_launcher_name) override fun getAppLauncherName() = getString(R.string.app_launcher_name)
@SuppressLint("InlinedApi")
protected fun checkNotchSupport() {
if (isPiePlus()) {
val cutoutMode = when {
config.showNotch -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
else -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
}
window.attributes.layoutInDisplayCutoutMode = cutoutMode
if (config.showNotch) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
}
}
}
} }

View file

@ -1,9 +1,11 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.content.Intent import android.content.Intent
import com.simplemobiletools.commons.activities.BaseSplashActivity import com.simplemobiletools.commons.activities.BaseSplashActivity
class SplashActivity : BaseSplashActivity() { class SplashActivity : BaseSplashActivity() {
override fun getAppPackageName() = "-1"
override fun initActivity() { override fun initActivity() {
startActivity(Intent(this, MainActivity::class.java)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.os.Bundle import android.os.Bundle

View file

@ -0,0 +1,616 @@
package com.simplemobiletools.gallery.pro.activities
import android.app.Activity
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.Point
import android.graphics.SurfaceTexture
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.util.DisplayMetrics
import android.view.*
import android.widget.SeekBar
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.ExtractorMediaSource
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.google.android.exoplayer2.upstream.ContentDataSource
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DataSpec
import com.google.android.exoplayer2.video.VideoListener
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.activity_video_player.*
import kotlinx.android.synthetic.main.bottom_video_time_holder.*
open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListener, TextureView.SurfaceTextureListener {
private val PLAY_WHEN_READY_DRAG_DELAY = 100L
private var mIsFullscreen = false
private var mIsPlaying = false
private var mWasVideoStarted = false
private var mIsDragged = false
private var mIsOrientationLocked = false
private var mScreenWidth = 0
private var mCurrTime = 0
private var mDuration = 0
private var mDragThreshold = 0f
private var mTouchDownX = 0f
private var mTouchDownY = 0f
private var mTouchDownTime = 0L
private var mProgressAtDown = 0L
private var mCloseDownThreshold = 100f
private var mUri: Uri? = null
private var mExoPlayer: SimpleExoPlayer? = null
private var mVideoSize = Point(0, 0)
private var mTimerHandler = Handler()
private var mPlayWhenReadyHandler = Handler()
private var mIgnoreCloseDown = false
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_video_player)
setupOrientation()
checkNotchSupport()
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
initPlayer()
} else {
toast(R.string.no_storage_permissions)
finish()
}
}
}
override fun onResume() {
super.onResume()
top_shadow.layoutParams.height = statusBarHeight + actionBarHeight
supportActionBar?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
if (config.blackBackground) {
video_player_holder.background = ColorDrawable(Color.BLACK)
}
if (config.maxBrightness) {
val attributes = window.attributes
attributes.screenBrightness = 1f
window.attributes = attributes
}
updateTextColors(video_player_holder)
}
override fun onPause() {
super.onPause()
pauseVideo()
if (config.rememberLastVideoPosition && mWasVideoStarted) {
saveVideoProgress()
}
}
override fun onDestroy() {
super.onDestroy()
if (!isChangingConfigurations) {
pauseVideo()
video_curr_time.text = 0.getFormattedDuration()
releaseExoPlayer()
video_seekbar.progress = 0
mTimerHandler.removeCallbacksAndMessages(null)
mPlayWhenReadyHandler.removeCallbacksAndMessages(null)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_video_player, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_change_orientation -> changeOrientation()
R.id.menu_open_with -> openPath(mUri!!.toString(), true)
R.id.menu_share -> shareMediumPath(mUri!!.toString())
else -> return super.onOptionsItemSelected(item)
}
return true
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
setVideoSize()
initTimeHolder()
video_surface_frame.onGlobalLayout {
video_surface_frame.controller.resetState()
}
}
private fun setupOrientation() {
if (!mIsOrientationLocked) {
if (config.screenRotation == ROTATE_BY_DEVICE_ROTATION) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
} else if (config.screenRotation == ROTATE_BY_SYSTEM_SETTING) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}
}
private fun initPlayer() {
mUri = intent.data ?: return
supportActionBar?.title = getFilenameFromUri(mUri!!)
initTimeHolder()
showSystemUI(true)
window.decorView.setOnSystemUiVisibilityChangeListener { visibility ->
val isFullscreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0
fullscreenToggled(isFullscreen)
}
video_curr_time.setOnClickListener { skip(false) }
video_duration.setOnClickListener { skip(true) }
video_toggle_play_pause.setOnClickListener { togglePlayPause() }
video_surface_frame.setOnClickListener { toggleFullscreen() }
video_next_file.beVisibleIf(intent.getBooleanExtra(SHOW_NEXT_ITEM, false))
video_next_file.setOnClickListener { handleNextFile() }
video_prev_file.beVisibleIf(intent.getBooleanExtra(SHOW_PREV_ITEM, false))
video_prev_file.setOnClickListener { handlePrevFile() }
video_surface_frame.setOnTouchListener { view, event ->
handleEvent(event)
false
}
initExoPlayer()
video_surface.surfaceTextureListener = this
if (config.allowVideoGestures) {
video_brightness_controller.initialize(this, slide_info, true, video_player_holder) { x, y ->
toggleFullscreen()
}
video_volume_controller.initialize(this, slide_info, false, video_player_holder) { x, y ->
toggleFullscreen()
}
} else {
video_brightness_controller.beGone()
video_volume_controller.beGone()
}
if (config.hideSystemUI) {
Handler().postDelayed({
fullscreenToggled(true)
}, HIDE_SYSTEM_UI_DELAY)
}
mDragThreshold = DRAG_THRESHOLD * resources.displayMetrics.density
}
private fun initExoPlayer() {
val dataSpec = DataSpec(mUri)
val fileDataSource = ContentDataSource(applicationContext)
try {
fileDataSource.open(dataSpec)
} catch (e: Exception) {
showErrorToast(e)
}
val factory = DataSource.Factory { fileDataSource }
val audioSource = ExtractorMediaSource(fileDataSource.uri, factory, DefaultExtractorsFactory(), null, null)
mExoPlayer = ExoPlayerFactory.newSimpleInstance(applicationContext).apply {
seekParameters = SeekParameters.CLOSEST_SYNC
audioStreamType = C.STREAM_TYPE_MUSIC
prepare(audioSource)
}
initExoPlayerListeners()
}
private fun initExoPlayerListeners() {
mExoPlayer!!.addListener(object : Player.EventListener {
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {}
override fun onSeekProcessed() {}
override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {}
override fun onPlayerError(error: ExoPlaybackException?) {}
override fun onLoadingChanged(isLoading: Boolean) {}
override fun onPositionDiscontinuity(reason: Int) {}
override fun onRepeatModeChanged(repeatMode: Int) {}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {}
override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
when (playbackState) {
Player.STATE_READY -> videoPrepared()
Player.STATE_ENDED -> videoCompleted()
}
}
})
mExoPlayer!!.addVideoListener(object : VideoListener {
override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
mVideoSize.x = width
mVideoSize.y = height
setVideoSize()
}
override fun onRenderedFirstFrame() {}
})
}
private fun videoPrepared() {
if (!mWasVideoStarted) {
video_toggle_play_pause.beVisible()
mDuration = (mExoPlayer!!.duration / 1000).toInt()
video_seekbar.max = mDuration
video_duration.text = mDuration.getFormattedDuration()
setPosition(mCurrTime)
if (config.rememberLastVideoPosition) {
setLastVideoSavedPosition()
}
if (config.autoplayVideos) {
resumeVideo()
} else {
video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline)
}
}
}
private fun resumeVideo() {
video_toggle_play_pause.setImageResource(R.drawable.ic_pause_outline)
if (mExoPlayer == null) {
return
}
val wasEnded = didVideoEnd()
if (wasEnded) {
setPosition(0)
}
mWasVideoStarted = true
mIsPlaying = true
mExoPlayer?.playWhenReady = true
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
private fun pauseVideo() {
video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline)
if (mExoPlayer == null) {
return
}
mIsPlaying = false
if (!didVideoEnd()) {
mExoPlayer?.playWhenReady = false
}
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
private fun togglePlayPause() {
mIsPlaying = !mIsPlaying
if (mIsPlaying) {
resumeVideo()
} else {
pauseVideo()
}
}
private fun setPosition(seconds: Int) {
mExoPlayer?.seekTo(seconds * 1000L)
video_seekbar.progress = seconds
video_curr_time.text = seconds.getFormattedDuration()
}
private fun setLastVideoSavedPosition() {
if (config.lastVideoPath == mUri.toString() && config.lastVideoPosition > 0) {
setPosition(config.lastVideoPosition)
}
}
private fun videoCompleted() {
if (mExoPlayer == null) {
return
}
clearLastVideoSavedProgress()
mCurrTime = (mExoPlayer!!.duration / 1000).toInt()
if (config.loopVideos) {
resumeVideo()
} else {
video_seekbar.progress = video_seekbar.max
video_curr_time.text = mDuration.getFormattedDuration()
pauseVideo()
}
}
private fun didVideoEnd(): Boolean {
val currentPos = mExoPlayer?.currentPosition ?: 0
val duration = mExoPlayer?.duration ?: 0
return currentPos != 0L && currentPos >= duration
}
private fun saveVideoProgress() {
if (!didVideoEnd()) {
config.apply {
lastVideoPosition = mExoPlayer!!.currentPosition.toInt() / 1000
lastVideoPath = mUri.toString()
}
}
}
private fun clearLastVideoSavedProgress() {
config.apply {
lastVideoPosition = 0
lastVideoPath = ""
}
}
private fun setVideoSize() {
val videoProportion = mVideoSize.x.toFloat() / mVideoSize.y.toFloat()
val display = windowManager.defaultDisplay
val screenWidth: Int
val screenHeight: Int
val realMetrics = DisplayMetrics()
display.getRealMetrics(realMetrics)
screenWidth = realMetrics.widthPixels
screenHeight = realMetrics.heightPixels
val screenProportion = screenWidth.toFloat() / screenHeight.toFloat()
video_surface.layoutParams.apply {
if (videoProportion > screenProportion) {
width = screenWidth
height = (screenWidth.toFloat() / videoProportion).toInt()
} else {
width = (videoProportion * screenHeight.toFloat()).toInt()
height = screenHeight
}
video_surface.layoutParams = this
}
val multiplier = if (screenWidth > screenHeight) 0.5 else 0.8
mScreenWidth = (screenWidth * multiplier).toInt()
if (config.screenRotation == ROTATE_BY_ASPECT_RATIO) {
if (mVideoSize.x > mVideoSize.y) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} else if (mVideoSize.x < mVideoSize.y) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}
}
private fun changeOrientation() {
mIsOrientationLocked = true
requestedOrientation = if (resources.configuration.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} else {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}
private fun toggleFullscreen() {
fullscreenToggled(!mIsFullscreen)
}
private fun fullscreenToggled(isFullScreen: Boolean) {
mIsFullscreen = isFullScreen
if (isFullScreen) {
hideSystemUI(true)
} else {
showSystemUI(true)
}
val newAlpha = if (isFullScreen) 0f else 1f
arrayOf(video_prev_file, video_toggle_play_pause, video_next_file, video_curr_time, video_seekbar, video_duration, top_shadow, video_bottom_gradient).forEach {
it.animate().alpha(newAlpha).start()
}
video_seekbar.setOnSeekBarChangeListener(if (mIsFullscreen) null else this)
arrayOf(video_prev_file, video_next_file, video_curr_time, video_duration).forEach {
it.isClickable = !mIsFullscreen
}
}
private fun initTimeHolder() {
var right = 0
var bottom = 0
if (hasNavBar()) {
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
bottom += navigationBarHeight
} else {
right += navigationBarWidth
bottom += navigationBarHeight
}
}
video_time_holder.setPadding(0, 0, right, bottom)
video_seekbar.setOnSeekBarChangeListener(this)
video_seekbar.max = mDuration
video_duration.text = mDuration.getFormattedDuration()
video_curr_time.text = mCurrTime.getFormattedDuration()
setupTimer()
}
private fun setupTimer() {
runOnUiThread(object : Runnable {
override fun run() {
if (mExoPlayer != null && !mIsDragged && mIsPlaying) {
mCurrTime = (mExoPlayer!!.currentPosition / 1000).toInt()
video_seekbar.progress = mCurrTime
video_curr_time.text = mCurrTime.getFormattedDuration()
}
mTimerHandler.postDelayed(this, 1000)
}
})
}
private fun skip(forward: Boolean) {
if (mExoPlayer == null) {
return
}
val curr = mExoPlayer!!.currentPosition
val twoPercents = Math.max((mExoPlayer!!.duration / 50).toInt(), MIN_SKIP_LENGTH)
val newProgress = if (forward) curr + twoPercents else curr - twoPercents
val roundProgress = Math.round(newProgress / 1000f)
val limitedProgress = Math.max(Math.min(mExoPlayer!!.duration.toInt(), roundProgress), 0)
setPosition(limitedProgress)
if (!mIsPlaying) {
togglePlayPause()
}
}
private fun handleEvent(event: MotionEvent) {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mTouchDownX = event.x
mTouchDownY = event.y
mTouchDownTime = System.currentTimeMillis()
mProgressAtDown = mExoPlayer!!.currentPosition
}
MotionEvent.ACTION_POINTER_DOWN -> mIgnoreCloseDown = true
MotionEvent.ACTION_MOVE -> {
val diffX = event.x - mTouchDownX
val diffY = event.y - mTouchDownY
if (mIsDragged || (Math.abs(diffX) > mDragThreshold && Math.abs(diffX) > Math.abs(diffY)) && video_surface_frame.controller.state.zoom == 1f) {
if (!mIsDragged) {
arrayOf(video_curr_time, video_seekbar, video_duration).forEach {
it.animate().alpha(1f).start()
}
}
mIgnoreCloseDown = true
mIsDragged = true
var percent = ((diffX / mScreenWidth) * 100).toInt()
percent = Math.min(100, Math.max(-100, percent))
val skipLength = (mDuration * 1000f) * (percent / 100f)
var newProgress = mProgressAtDown + skipLength
newProgress = Math.max(Math.min(mExoPlayer!!.duration.toFloat(), newProgress), 0f)
val newSeconds = (newProgress / 1000).toInt()
setPosition(newSeconds)
resetPlayWhenReady()
}
}
MotionEvent.ACTION_UP -> {
val diffX = mTouchDownX - event.x
val diffY = mTouchDownY - event.y
val downGestureDuration = System.currentTimeMillis() - mTouchDownTime
if (config.allowDownGesture && !mIgnoreCloseDown && Math.abs(diffY) > Math.abs(diffX) && diffY < -mCloseDownThreshold &&
downGestureDuration < MAX_CLOSE_DOWN_GESTURE_DURATION &&
video_surface_frame.controller.state.zoom == 1f) {
supportFinishAfterTransition()
}
mIgnoreCloseDown = false
if (mIsDragged) {
if (mIsFullscreen) {
arrayOf(video_curr_time, video_seekbar, video_duration).forEach {
it.animate().alpha(0f).start()
}
}
if (!mIsPlaying) {
togglePlayPause()
}
}
mIsDragged = false
}
}
}
private fun handleNextFile() {
Intent().apply {
putExtra(GO_TO_NEXT_ITEM, true)
setResult(Activity.RESULT_OK, this)
}
finish()
}
private fun handlePrevFile() {
Intent().apply {
putExtra(GO_TO_PREV_ITEM, true)
setResult(Activity.RESULT_OK, this)
}
finish()
}
private fun resetPlayWhenReady() {
mExoPlayer?.playWhenReady = false
mPlayWhenReadyHandler.removeCallbacksAndMessages(null)
mPlayWhenReadyHandler.postDelayed({
mExoPlayer?.playWhenReady = true
}, PLAY_WHEN_READY_DRAG_DELAY)
}
private fun releaseExoPlayer() {
mExoPlayer?.stop()
Thread {
mExoPlayer?.release()
mExoPlayer = null
}.start()
}
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (mExoPlayer != null && fromUser) {
setPosition(progress)
resetPlayWhenReady()
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
mIsDragged = true
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
if (mExoPlayer == null)
return
if (mIsPlaying) {
mExoPlayer!!.playWhenReady = true
} else {
togglePlayPause()
}
mIsDragged = false
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?) = false
override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
Thread {
mExoPlayer?.setVideoSurface(Surface(video_surface!!.surfaceTexture))
}.start()
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {}
}

View file

@ -1,67 +1,59 @@
package com.simplemobiletools.gallery.activities package com.simplemobiletools.gallery.pro.activities
import android.animation.Animator import android.animation.Animator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.database.Cursor import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color import android.graphics.Color
import android.graphics.Matrix
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.media.ExifInterface import android.media.ExifInterface
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.provider.MediaStore import android.provider.MediaStore
import android.support.v4.view.ViewPager
import android.util.DisplayMetrics
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import com.bumptech.glide.Glide import android.widget.Toast
import androidx.viewpager.widget.ViewPager
import com.simplemobiletools.commons.dialogs.PropertiesDialog import com.simplemobiletools.commons.dialogs.PropertiesDialog
import com.simplemobiletools.commons.dialogs.RenameItemDialog import com.simplemobiletools.commons.dialogs.RenameItemDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.adapters.MyPagerAdapter import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask import com.simplemobiletools.gallery.pro.adapters.MyPagerAdapter
import com.simplemobiletools.gallery.dialogs.DeleteWithRememberDialog import com.simplemobiletools.gallery.pro.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.dialogs.SaveAsDialog import com.simplemobiletools.gallery.pro.dialogs.DeleteWithRememberDialog
import com.simplemobiletools.gallery.dialogs.SlideshowDialog import com.simplemobiletools.gallery.pro.dialogs.SaveAsDialog
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.pro.dialogs.SlideshowDialog
import com.simplemobiletools.gallery.fragments.PhotoFragment import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.fragments.VideoFragment import com.simplemobiletools.gallery.pro.fragments.PhotoFragment
import com.simplemobiletools.gallery.fragments.ViewPagerFragment import com.simplemobiletools.gallery.pro.fragments.VideoFragment
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.fragments.ViewPagerFragment
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.models.ThumbnailItem import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import kotlinx.android.synthetic.main.activity_medium.* import kotlinx.android.synthetic.main.activity_medium.*
import kotlinx.android.synthetic.main.bottom_actions.* import kotlinx.android.synthetic.main.bottom_actions.*
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.util.* import java.util.*
class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, ViewPagerFragment.FragmentListener { class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, ViewPagerFragment.FragmentListener {
private val REQUEST_VIEW_VIDEO = 1
private var mPath = "" private var mPath = ""
private var mDirectory = "" private var mDirectory = ""
private var mIsFullScreen = false private var mIsFullScreen = false
private var mPos = -1 private var mPos = -1
private var mShowAll = false private var mShowAll = false
private var mIsSlideshowActive = false private var mIsSlideshowActive = false
private var mRotationDegrees = 0
private var mPrevHashcode = 0 private var mPrevHashcode = 0
private var mSlideshowHandler = Handler() private var mSlideshowHandler = Handler()
@ -69,19 +61,18 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private var mSlideshowMoveBackwards = false private var mSlideshowMoveBackwards = false
private var mSlideshowMedia = mutableListOf<Medium>() private var mSlideshowMedia = mutableListOf<Medium>()
private var mAreSlideShowMediaVisible = false private var mAreSlideShowMediaVisible = false
private var mIsOrientationLocked = false private var mIsOrientationLocked = false
private var mMediaFiles = ArrayList<Medium>() private var mMediaFiles = ArrayList<Medium>()
private var mFavoritePaths = ArrayList<String>() private var mFavoritePaths = ArrayList<String>()
companion object {
var screenWidth = 0
var screenHeight = 0
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_medium) setContentView(R.layout.activity_medium)
top_shadow.layoutParams.height = statusBarHeight + actionBarHeight
checkNotchSupport()
(MediaActivity.mMedia.clone() as ArrayList<ThumbnailItem>).filter { it is Medium }.mapTo(mMediaFiles) { it as Medium } (MediaActivity.mMedia.clone() as ArrayList<ThumbnailItem>).filter { it is Medium }.mapTo(mMediaFiles) { it as Medium }
handlePermission(PERMISSION_WRITE_STORAGE) { handlePermission(PERMISSION_WRITE_STORAGE) {
@ -96,7 +87,6 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
initFavorites() initFavorites()
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (!hasPermission(PERMISSION_WRITE_STORAGE)) { if (!hasPermission(PERMISSION_WRITE_STORAGE)) {
@ -105,15 +95,12 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
if (config.bottomActions) { if (config.bottomActions) {
if (isLollipopPlus()) { window.navigationBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
}
} else { } else {
setTranslucentNavigation() setTranslucentNavigation()
} }
initBottomActions() initBottomActions()
supportActionBar?.setBackgroundDrawable(resources.getDrawable(R.drawable.actionbar_gradient_background))
if (config.maxBrightness) { if (config.maxBrightness) {
val attributes = window.attributes val attributes = window.attributes
@ -121,12 +108,11 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
window.attributes = attributes window.attributes = attributes
} }
setupRotation() setupOrientation()
invalidateOptionsMenu() invalidateOptionsMenu()
if (config.blackBackground) { supportActionBar?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
updateStatusbarColor(Color.BLACK) window.statusBarColor = Color.TRANSPARENT
}
} }
override fun onPause() { override fun onPause() {
@ -149,8 +135,81 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_viewpager, menu)
val currentMedium = getCurrentMedium() ?: return true
currentMedium.isFavorite = mFavoritePaths.contains(currentMedium.path)
val visibleBottomActions = if (config.bottomActions) config.visibleBottomActions else 0
val rotationDegrees = getCurrentPhotoFragment()?.mCurrentRotationDegrees ?: 0
menu.apply {
findItem(R.id.menu_show_on_map).isVisible = visibleBottomActions and BOTTOM_ACTION_SHOW_ON_MAP == 0
findItem(R.id.menu_slideshow).isVisible = visibleBottomActions and BOTTOM_ACTION_SLIDESHOW == 0
findItem(R.id.menu_properties).isVisible = visibleBottomActions and BOTTOM_ACTION_PROPERTIES == 0
findItem(R.id.menu_delete).isVisible = visibleBottomActions and BOTTOM_ACTION_DELETE == 0
findItem(R.id.menu_share).isVisible = visibleBottomActions and BOTTOM_ACTION_SHARE == 0
findItem(R.id.menu_edit).isVisible = visibleBottomActions and BOTTOM_ACTION_EDIT == 0 && !currentMedium.isSVG()
findItem(R.id.menu_rename).isVisible = visibleBottomActions and BOTTOM_ACTION_RENAME == 0 && !currentMedium.getIsInRecycleBin()
findItem(R.id.menu_rotate).isVisible = currentMedium.isImage() && visibleBottomActions and BOTTOM_ACTION_ROTATE == 0
findItem(R.id.menu_set_as).isVisible = visibleBottomActions and BOTTOM_ACTION_SET_AS == 0
findItem(R.id.menu_copy_to).isVisible = visibleBottomActions and BOTTOM_ACTION_COPY == 0
findItem(R.id.menu_save_as).isVisible = rotationDegrees != 0
findItem(R.id.menu_hide).isVisible = !currentMedium.isHidden() && visibleBottomActions and BOTTOM_ACTION_TOGGLE_VISIBILITY == 0 && !currentMedium.getIsInRecycleBin()
findItem(R.id.menu_unhide).isVisible = currentMedium.isHidden() && visibleBottomActions and BOTTOM_ACTION_TOGGLE_VISIBILITY == 0 && !currentMedium.getIsInRecycleBin()
findItem(R.id.menu_add_to_favorites).isVisible = !currentMedium.isFavorite && visibleBottomActions and BOTTOM_ACTION_TOGGLE_FAVORITE == 0
findItem(R.id.menu_remove_from_favorites).isVisible = currentMedium.isFavorite && visibleBottomActions and BOTTOM_ACTION_TOGGLE_FAVORITE == 0
findItem(R.id.menu_restore_file).isVisible = currentMedium.path.startsWith(recycleBinPath)
findItem(R.id.menu_change_orientation).isVisible = rotationDegrees == 0 && visibleBottomActions and BOTTOM_ACTION_CHANGE_ORIENTATION == 0
findItem(R.id.menu_change_orientation).icon = resources.getDrawable(getChangeOrientationIcon())
findItem(R.id.menu_rotate).setShowAsAction(
if (rotationDegrees != 0) {
MenuItem.SHOW_AS_ACTION_ALWAYS
} else {
MenuItem.SHOW_AS_ACTION_IF_ROOM
})
}
if (visibleBottomActions != 0) {
updateBottomActionIcons(currentMedium)
}
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (getCurrentMedium() == null)
return true
when (item.itemId) {
R.id.menu_set_as -> setAs(getCurrentPath())
R.id.menu_slideshow -> initSlideshow()
R.id.menu_copy_to -> copyMoveTo(true)
R.id.menu_move_to -> moveFileTo()
R.id.menu_open_with -> openPath(getCurrentPath(), true)
R.id.menu_hide -> toggleFileVisibility(true)
R.id.menu_unhide -> toggleFileVisibility(false)
R.id.menu_share -> shareMediumPath(getCurrentPath())
R.id.menu_delete -> checkDeleteConfirmation()
R.id.menu_rename -> renameFile()
R.id.menu_edit -> openEditor(getCurrentPath())
R.id.menu_properties -> showProperties()
R.id.menu_show_on_map -> showOnMap()
R.id.menu_rotate_right -> rotateImage(90)
R.id.menu_rotate_left -> rotateImage(-90)
R.id.menu_rotate_one_eighty -> rotateImage(180)
R.id.menu_add_to_favorites -> toggleFavorite()
R.id.menu_remove_from_favorites -> toggleFavorite()
R.id.menu_restore_file -> restoreFile()
R.id.menu_force_portrait -> toggleOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
R.id.menu_force_landscape -> toggleOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
R.id.menu_default_orientation -> toggleOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
R.id.menu_save_as -> saveImageAs()
R.id.menu_settings -> launchSettings()
else -> return super.onOptionsItemSelected(item)
}
return true
}
private fun initViewPager() { private fun initViewPager() {
measureScreen()
val uri = intent.data val uri = intent.data
if (uri != null) { if (uri != null) {
var cursor: Cursor? = null var cursor: Cursor? = null
@ -191,7 +250,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
if (intent.extras?.containsKey(IS_VIEW_INTENT) == true) { if (intent.extras?.containsKey(IS_VIEW_INTENT) == true) {
if (isShowHiddenFlagNeeded()) { if (isShowHiddenFlagNeeded()) {
if (!config.isPasswordProtectionOn) { if (!config.isHiddenPasswordProtectionOn) {
config.temporarilyShowHidden = true config.temporarilyShowHidden = true
} }
} }
@ -214,9 +273,10 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
supportActionBar?.title = mPath.getFilenameFromPath() supportActionBar?.title = mPath.getFilenameFromPath()
view_pager.onGlobalLayout { view_pager.onGlobalLayout {
if (!isActivityDestroyed()) { if (!isDestroyed) {
if (mMediaFiles.isNotEmpty()) { if (mMediaFiles.isNotEmpty()) {
gotMedia(mMediaFiles as ArrayList<ThumbnailItem>) gotMedia(mMediaFiles as ArrayList<ThumbnailItem>)
checkSlideshowOnEnter()
} }
} }
} }
@ -232,7 +292,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
view_pager.onGlobalLayout { view_pager.onGlobalLayout {
Handler().postDelayed({ Handler().postDelayed({
fragmentClicked() fragmentClicked()
}, 500) }, HIDE_SYSTEM_UI_DELAY)
} }
} }
@ -246,8 +306,10 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
view_pager.adapter?.let { view_pager.adapter?.let {
(it as MyPagerAdapter).toggleFullscreen(mIsFullScreen) (it as MyPagerAdapter).toggleFullscreen(mIsFullScreen)
checkSystemUI() checkSystemUI()
if (!bottom_actions.isGone()) { val newAlpha = if (mIsFullScreen) 0f else 1f
bottom_actions.animate().alpha(if (mIsFullScreen) 0f else 1f).start() top_shadow.animate().alpha(newAlpha).start()
if (bottom_actions.isVisible()) {
bottom_actions.animate().alpha(newAlpha).start()
arrayOf(bottom_favorite, bottom_edit, bottom_share, bottom_delete, bottom_rotate, bottom_properties, bottom_change_orientation, arrayOf(bottom_favorite, bottom_edit, bottom_share, bottom_delete, bottom_rotate, bottom_properties, bottom_change_orientation,
bottom_slideshow, bottom_show_on_map, bottom_toggle_file_visibility, bottom_rename).forEach { bottom_slideshow, bottom_show_on_map, bottom_toggle_file_visibility, bottom_rename).forEach {
it.isClickable = !mIsFullScreen it.isClickable = !mIsFullScreen
@ -255,6 +317,24 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
} }
} }
if (intent.action == "com.android.camera.action.REVIEW") {
Thread {
if (galleryDB.MediumDao().getMediaFromPath(mPath).isEmpty()) {
val type = when {
mPath.isVideoFast() -> TYPE_VIDEOS
mPath.isGif() -> TYPE_GIFS
mPath.isSvg() -> TYPE_SVGS
mPath.isRawFast() -> TYPE_RAWS
else -> TYPE_IMAGES
}
val duration = if (type == TYPE_VIDEOS) mPath.getVideoDuration() else 0
val medium = Medium(null, mPath.getFilenameFromPath(), mPath, mPath.getParentPath(), System.currentTimeMillis(), System.currentTimeMillis(), File(mPath).length(), type, duration, false, 0)
galleryDB.MediumDao().insert(medium)
}
}.start()
}
} }
private fun initBottomActions() { private fun initBottomActions() {
@ -268,7 +348,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}.start() }.start()
} }
private fun setupRotation() { private fun setupOrientation() {
if (!mIsOrientationLocked) { if (!mIsOrientationLocked) {
if (config.screenRotation == ROTATE_BY_DEVICE_ROTATION) { if (config.screenRotation == ROTATE_BY_DEVICE_ROTATION) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
@ -278,81 +358,9 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_viewpager, menu)
val currentMedium = getCurrentMedium() ?: return true
currentMedium.isFavorite = mFavoritePaths.contains(currentMedium.path)
val visibleBottomActions = if (config.bottomActions) config.visibleBottomActions else 0
menu.apply {
findItem(R.id.menu_show_on_map).isVisible = visibleBottomActions and BOTTOM_ACTION_SHOW_ON_MAP == 0
findItem(R.id.menu_slideshow).isVisible = visibleBottomActions and BOTTOM_ACTION_SLIDESHOW == 0
findItem(R.id.menu_properties).isVisible = visibleBottomActions and BOTTOM_ACTION_PROPERTIES == 0
findItem(R.id.menu_delete).isVisible = visibleBottomActions and BOTTOM_ACTION_DELETE == 0
findItem(R.id.menu_share).isVisible = visibleBottomActions and BOTTOM_ACTION_SHARE == 0
findItem(R.id.menu_edit).isVisible = visibleBottomActions and BOTTOM_ACTION_EDIT == 0 && !currentMedium.isSVG()
findItem(R.id.menu_rename).isVisible = visibleBottomActions and BOTTOM_ACTION_RENAME == 0 && !currentMedium.getIsInRecycleBin()
findItem(R.id.menu_rotate).isVisible = currentMedium.isImage() && visibleBottomActions and BOTTOM_ACTION_ROTATE == 0
findItem(R.id.menu_set_as).isVisible = visibleBottomActions and BOTTOM_ACTION_SET_AS == 0
findItem(R.id.menu_save_as).isVisible = mRotationDegrees != 0
findItem(R.id.menu_hide).isVisible = !currentMedium.isHidden() && visibleBottomActions and BOTTOM_ACTION_TOGGLE_VISIBILITY == 0 && !currentMedium.getIsInRecycleBin()
findItem(R.id.menu_unhide).isVisible = currentMedium.isHidden() && visibleBottomActions and BOTTOM_ACTION_TOGGLE_VISIBILITY == 0 && !currentMedium.getIsInRecycleBin()
findItem(R.id.menu_add_to_favorites).isVisible = !currentMedium.isFavorite && visibleBottomActions and BOTTOM_ACTION_TOGGLE_FAVORITE == 0
findItem(R.id.menu_remove_from_favorites).isVisible = currentMedium.isFavorite && visibleBottomActions and BOTTOM_ACTION_TOGGLE_FAVORITE == 0
findItem(R.id.menu_restore_file).isVisible = currentMedium.path.startsWith(filesDir.absolutePath)
findItem(R.id.menu_change_orientation).isVisible = mRotationDegrees == 0 && visibleBottomActions and BOTTOM_ACTION_CHANGE_ORIENTATION == 0
findItem(R.id.menu_change_orientation).icon = resources.getDrawable(getChangeOrientationIcon())
findItem(R.id.menu_rotate).setShowAsAction(
if (mRotationDegrees != 0) {
MenuItem.SHOW_AS_ACTION_ALWAYS
} else {
MenuItem.SHOW_AS_ACTION_IF_ROOM
})
}
if (visibleBottomActions != 0) {
updateBottomActionIcons(currentMedium)
}
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (getCurrentMedium() == null)
return true
when (item.itemId) {
R.id.menu_set_as -> setAs(getCurrentPath())
R.id.menu_slideshow -> initSlideshow()
R.id.menu_copy_to -> copyMoveTo(true)
R.id.menu_move_to -> copyMoveTo(false)
R.id.menu_open_with -> openPath(getCurrentPath(), true)
R.id.menu_hide -> toggleFileVisibility(true)
R.id.menu_unhide -> toggleFileVisibility(false)
R.id.menu_share -> shareMediumPath(getCurrentPath())
R.id.menu_delete -> checkDeleteConfirmation()
R.id.menu_rename -> renameFile()
R.id.menu_edit -> openEditor(getCurrentPath())
R.id.menu_properties -> showProperties()
R.id.menu_show_on_map -> showOnMap()
R.id.menu_rotate_right -> rotateImage(90)
R.id.menu_rotate_left -> rotateImage(270)
R.id.menu_rotate_one_eighty -> rotateImage(180)
R.id.menu_add_to_favorites -> toggleFavorite()
R.id.menu_remove_from_favorites -> toggleFavorite()
R.id.menu_restore_file -> restoreFile()
R.id.menu_force_portrait -> toggleOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
R.id.menu_force_landscape -> toggleOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
R.id.menu_default_orientation -> toggleOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
R.id.menu_save_as -> saveImageAs()
R.id.menu_settings -> launchSettings()
else -> return super.onOptionsItemSelected(item)
}
return true
}
private fun updatePagerItems(media: MutableList<Medium>) { private fun updatePagerItems(media: MutableList<Medium>) {
val pagerAdapter = MyPagerAdapter(this, supportFragmentManager, media) val pagerAdapter = MyPagerAdapter(this, supportFragmentManager, media)
if (!isActivityDestroyed()) { if (!isDestroyed) {
view_pager.apply { view_pager.apply {
adapter = pagerAdapter adapter = pagerAdapter
currentItem = mPos currentItem = mPos
@ -362,6 +370,12 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
} }
private fun checkSlideshowOnEnter() {
if (intent.getBooleanExtra(SLIDESHOW_START_ON_ENTER, false)) {
initSlideshow()
}
}
private fun initSlideshow() { private fun initSlideshow() {
SlideshowDialog(this) { SlideshowDialog(this) {
startSlideshow() startSlideshow()
@ -371,7 +385,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private fun startSlideshow() { private fun startSlideshow() {
if (getMediaForSlideshow()) { if (getMediaForSlideshow()) {
view_pager.onGlobalLayout { view_pager.onGlobalLayout {
if (!isActivityDestroyed()) { if (!isDestroyed) {
hideSystemUI(true) hideSystemUI(true)
mSlideshowInterval = config.slideshowInterval mSlideshowInterval = config.slideshowInterval
mSlideshowMoveBackwards = config.slideshowMoveBackwards mSlideshowMoveBackwards = config.slideshowMoveBackwards
@ -421,7 +435,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
val dragOffset = dragPosition - oldDragPosition val dragOffset = dragPosition - oldDragPosition
oldDragPosition = dragPosition oldDragPosition = dragPosition
try { try {
view_pager.fakeDragBy(dragOffset * (if (forward) 1f else -1f)) view_pager.fakeDragBy(dragOffset * (if (forward) -1f else 1f))
} catch (e: Exception) { } catch (e: Exception) {
stopSlideshow() stopSlideshow()
} }
@ -461,7 +475,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
if (mIsSlideshowActive) { if (mIsSlideshowActive) {
if (getCurrentMedium()!!.isImage() || getCurrentMedium()!!.isGIF()) { if (getCurrentMedium()!!.isImage() || getCurrentMedium()!!.isGIF()) {
mSlideshowHandler.postDelayed({ mSlideshowHandler.postDelayed({
if (mIsSlideshowActive && !isActivityDestroyed()) { if (mIsSlideshowActive && !isDestroyed) {
swipeToNextMedium() swipeToNextMedium()
} }
}, mSlideshowInterval * 1000L) }, mSlideshowInterval * 1000L)
@ -476,18 +490,9 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
private fun getMediaForSlideshow(): Boolean { private fun getMediaForSlideshow(): Boolean {
mSlideshowMedia = mMediaFiles.toMutableList() mSlideshowMedia = mMediaFiles.filter {
if (!config.slideshowIncludePhotos) { it.isImage() || (config.slideshowIncludeVideos && it.isVideo() || (config.slideshowIncludeGIFs && it.isGIF()))
mSlideshowMedia = mSlideshowMedia.filter { !it.isImage() } as MutableList }.toMutableList()
}
if (!config.slideshowIncludeVideos) {
mSlideshowMedia = mSlideshowMedia.filter { it.isImage() || it.isGIF() } as MutableList
}
if (!config.slideshowIncludeGIFs) {
mSlideshowMedia = mSlideshowMedia.filter { !it.isGIF() } as MutableList
}
if (config.slideshowRandomOrder) { if (config.slideshowRandomOrder) {
mSlideshowMedia.shuffle() mSlideshowMedia.shuffle()
@ -507,13 +512,25 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
} }
private fun moveFileTo() {
handleDeletePasswordProtection {
copyMoveTo(false)
}
}
private fun copyMoveTo(isCopyOperation: Boolean) { private fun copyMoveTo(isCopyOperation: Boolean) {
val currPath = getCurrentPath() val currPath = getCurrentPath()
if (!isCopyOperation && currPath.startsWith(recycleBinPath)) {
toast(R.string.moving_recycle_bin_items_disabled, Toast.LENGTH_LONG)
return
}
val fileDirItems = arrayListOf(FileDirItem(currPath, currPath.getFilenameFromPath())) val fileDirItems = arrayListOf(FileDirItem(currPath, currPath.getFilenameFromPath()))
tryCopyMoveFilesTo(fileDirItems, isCopyOperation) { tryCopyMoveFilesTo(fileDirItems, isCopyOperation) {
config.tempFolderPath = "" config.tempFolderPath = ""
if (!isCopyOperation) { if (!isCopyOperation) {
refreshViewPager() refreshViewPager()
updateFavoritePaths(fileDirItems, it)
} }
} }
} }
@ -534,10 +551,18 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
private fun rotateImage(degrees: Int) { private fun rotateImage(degrees: Int) {
mRotationDegrees = (mRotationDegrees + degrees) % 360 val currentPath = getCurrentPath()
getCurrentFragment()?.let { if (needsStupidWritePermissions(currentPath)) {
(it as? PhotoFragment)?.rotateImageViewBy(mRotationDegrees) handleSAFDialog(currentPath) {
rotateBy(degrees)
}
} else {
rotateBy(degrees)
} }
}
private fun rotateBy(degrees: Int) {
getCurrentPhotoFragment()?.rotateImageViewBy(degrees)
supportInvalidateOptionsMenu() supportInvalidateOptionsMenu()
} }
@ -563,110 +588,20 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
val currPath = getCurrentPath() val currPath = getCurrentPath()
SaveAsDialog(this, currPath, false) { SaveAsDialog(this, currPath, false) {
handleSAFDialog(it) { handleSAFDialog(it) {
toast(R.string.saving)
Thread { Thread {
saveImageToFile(currPath, it) val photoFragment = getCurrentPhotoFragment() ?: return@Thread
saveRotatedImageToFile(currPath, it, photoFragment.mCurrentRotationDegrees, true) {
toast(R.string.file_saved)
getCurrentPhotoFragment()?.mCurrentRotationDegrees = 0
invalidateOptionsMenu()
}
}.start() }.start()
} }
} }
} }
private fun saveImageToFile(oldPath: String, newPath: String) { private fun getCurrentPhotoFragment() = getCurrentFragment() as? PhotoFragment
toast(R.string.saving)
if (oldPath == newPath && oldPath.isJpg()) {
if (tryRotateByExif(oldPath)) {
return
}
}
val tmpPath = "${filesDir.absolutePath}/.tmp_${newPath.getFilenameFromPath()}"
val tmpFileDirItem = FileDirItem(tmpPath, tmpPath.getFilenameFromPath())
try {
getFileOutputStream(tmpFileDirItem) {
if (it == null) {
toast(R.string.unknown_error_occurred)
return@getFileOutputStream
}
val oldLastModified = getCurrentFile().lastModified()
if (oldPath.isJpg()) {
copyFile(getCurrentPath(), tmpPath)
saveExifRotation(ExifInterface(tmpPath), mRotationDegrees)
} else {
val inputstream = getFileInputStreamSync(oldPath)
val bitmap = BitmapFactory.decodeStream(inputstream)
saveFile(tmpPath, bitmap, it as FileOutputStream)
}
if (getDoesFilePathExist(newPath)) {
tryDeleteFileDirItem(FileDirItem(newPath, newPath.getFilenameFromPath()), false, true)
}
copyFile(tmpPath, newPath)
scanPathRecursively(newPath)
toast(R.string.file_saved)
if (config.keepLastModified) {
File(newPath).setLastModified(oldLastModified)
updateLastModified(newPath, oldLastModified)
}
it.flush()
it.close()
mRotationDegrees = 0
invalidateOptionsMenu()
// we cannot refresh a specific image in Glide Cache, so just clear it all
val glide = Glide.get(applicationContext)
glide.clearDiskCache()
runOnUiThread {
glide.clearMemory()
}
}
} catch (e: OutOfMemoryError) {
toast(R.string.out_of_memory_error)
} catch (e: Exception) {
showErrorToast(e)
} finally {
tryDeleteFileDirItem(tmpFileDirItem, false, true)
}
}
@TargetApi(Build.VERSION_CODES.N)
private fun tryRotateByExif(path: String): Boolean {
return try {
if (saveImageRotation(path, mRotationDegrees)) {
mRotationDegrees = 0
invalidateOptionsMenu()
toast(R.string.file_saved)
true
} else {
false
}
} catch (e: Exception) {
showErrorToast(e)
false
}
}
private fun copyFile(source: String, destination: String) {
var inputStream: InputStream? = null
var out: OutputStream? = null
try {
out = getFileOutputStreamSync(destination, source.getMimeType())
inputStream = getFileInputStreamSync(source)
inputStream?.copyTo(out!!)
} finally {
inputStream?.close()
out?.close()
}
}
private fun saveFile(path: String, bitmap: Bitmap, out: FileOutputStream) {
val matrix = Matrix()
matrix.postRotate(mRotationDegrees.toFloat())
val bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
bmp.compress(path.getCompressionFormat(), 90, out)
}
private fun isShowHiddenFlagNeeded(): Boolean { private fun isShowHiddenFlagNeeded(): Boolean {
val file = File(mPath) val file = File(mPath)
@ -839,6 +774,11 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
bottom_set_as.setOnClickListener { bottom_set_as.setOnClickListener {
setAs(getCurrentPath()) setAs(getCurrentPath())
} }
bottom_copy.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_COPY != 0)
bottom_copy.setOnClickListener {
copyMoveTo(true)
}
} }
private fun updateBottomActionIcons(medium: Medium?) { private fun updateBottomActionIcons(medium: Medium?) {
@ -877,15 +817,17 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == REQUEST_EDIT_IMAGE) { if (requestCode == REQUEST_EDIT_IMAGE && resultCode == Activity.RESULT_OK && resultData != null) {
if (resultCode == Activity.RESULT_OK && resultData != null) { mPos = -1
mPos = -1 mPrevHashcode = 0
mPrevHashcode = 0 refreshViewPager()
refreshViewPager() } else if (requestCode == REQUEST_SET_AS && resultCode == Activity.RESULT_OK) {
} toast(R.string.wallpaper_set_successfully)
} else if (requestCode == REQUEST_SET_AS) { } else if (requestCode == REQUEST_VIEW_VIDEO && resultCode == Activity.RESULT_OK && resultData != null) {
if (resultCode == Activity.RESULT_OK) { if (resultData.getBooleanExtra(GO_TO_NEXT_ITEM, false)) {
toast(R.string.wallpaper_set_successfully) goToNextItem()
} else if (resultData.getBooleanExtra(GO_TO_PREV_ITEM, false)) {
goToPrevItem()
} }
} }
super.onActivityResult(requestCode, resultCode, resultData) super.onActivityResult(requestCode, resultCode, resultData)
@ -896,7 +838,11 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
return return
} }
if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) { if (config.isDeletePasswordProtectionOn) {
handleDeletePasswordProtection {
deleteConfirmed()
}
} else if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) {
deleteConfirmed() deleteConfirmed()
} else { } else {
askConfirmDelete() askConfirmDelete()
@ -904,8 +850,16 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
} }
private fun askConfirmDelete() { private fun askConfirmDelete() {
val message = if (config.useRecycleBin && !getCurrentMedium()!!.getIsInRecycleBin()) R.string.are_you_sure_recycle_bin else R.string.are_you_sure_delete val filename = "\"${getCurrentPath().getFilenameFromPath()}\""
DeleteWithRememberDialog(this, getString(message)) {
val baseString = if (config.useRecycleBin && !getCurrentMedium()!!.getIsInRecycleBin()) {
R.string.move_to_recycle_bin_confirmation
} else {
R.string.deletion_confirmation
}
val message = String.format(resources.getString(baseString), filename)
DeleteWithRememberDialog(this, message) {
config.tempSkipDeleteConfirmation = it config.tempSkipDeleteConfirmation = it
deleteConfirmed() deleteConfirmed()
} }
@ -962,32 +916,19 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
override fun onConfigurationChanged(newConfig: Configuration?) { override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
measureScreen()
initBottomActionsLayout() initBottomActionsLayout()
} }
@SuppressLint("NewApi") private fun refreshViewPager() {
private fun measureScreen() { if (config.getFileSorting(mDirectory) and SORT_BY_RANDOM == 0) {
val metrics = DisplayMetrics() GetMediaAsynctask(applicationContext, mDirectory, false, false, mShowAll) {
if (isJellyBean1Plus()) { gotMedia(it)
windowManager.defaultDisplay.getRealMetrics(metrics) }.execute()
screenWidth = metrics.widthPixels
screenHeight = metrics.heightPixels
} else {
windowManager.defaultDisplay.getMetrics(metrics)
screenWidth = metrics.widthPixels
screenHeight = metrics.heightPixels
} }
} }
private fun refreshViewPager() {
GetMediaAsynctask(applicationContext, mDirectory, false, false, mShowAll) {
gotMedia(it)
}.execute()
}
private fun gotMedia(thumbnailItems: ArrayList<ThumbnailItem>) { private fun gotMedia(thumbnailItems: ArrayList<ThumbnailItem>) {
val media = thumbnailItems.filter { it is Medium }.map { it as Medium } as ArrayList<Medium> val media = thumbnailItems.asSequence().filter { it is Medium }.map { it as Medium }.toMutableList() as ArrayList<Medium>
if (isDirEmpty(media) || media.hashCode() == mPrevHashcode) { if (isDirEmpty(media) || media.hashCode() == mPrevHashcode) {
return return
} }
@ -1069,6 +1010,34 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
checkOrientation() checkOrientation()
} }
override fun launchViewVideoIntent(path: String) {
Thread {
val newUri = getFinalUriFromPath(path, BuildConfig.APPLICATION_ID) ?: return@Thread
val mimeType = getUriMimeType(path, newUri)
Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(newUri, mimeType)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
putExtra(IS_FROM_GALLERY, true)
putExtra(REAL_FILE_PATH, path)
putExtra(SHOW_PREV_ITEM, view_pager.currentItem != 0)
putExtra(SHOW_NEXT_ITEM, view_pager.currentItem != mMediaFiles.size - 1)
if (resolveActivity(packageManager) != null) {
try {
startActivityForResult(this, REQUEST_VIEW_VIDEO)
} catch (e: NullPointerException) {
showErrorToast(e)
}
} else {
if (!tryGenericMimeType(this, mimeType, newUri)) {
toast(R.string.no_app_found)
}
}
}
}.start()
}
private fun checkSystemUI() { private fun checkSystemUI() {
if (mIsFullScreen) { if (mIsFullScreen) {
hideSystemUI(true) hideSystemUI(true)
@ -1098,15 +1067,12 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private fun getCurrentPath() = getCurrentMedium()?.path ?: "" private fun getCurrentPath() = getCurrentMedium()?.path ?: ""
private fun getCurrentFile() = File(getCurrentPath())
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
if (mPos != position) { if (mPos != position) {
mPos = position mPos = position
updateActionbarTitle() updateActionbarTitle()
mRotationDegrees = 0
invalidateOptionsMenu() invalidateOptionsMenu()
scheduleSwipe() scheduleSwipe()
} }

View file

@ -0,0 +1,184 @@
package com.simplemobiletools.gallery.pro.activities
import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.widget.RelativeLayout
import android.widget.RemoteViews
import com.simplemobiletools.commons.dialogs.ColorPickerDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.dialogs.PickDirectoryDialog
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.MyWidgetProvider
import com.simplemobiletools.gallery.pro.models.Directory
import com.simplemobiletools.gallery.pro.models.Widget
import kotlinx.android.synthetic.main.activity_widget_config.*
class WidgetConfigureActivity : SimpleActivity() {
private var mBgAlpha = 0f
private var mWidgetId = 0
private var mBgColor = 0
private var mBgColorWithoutTransparency = 0
private var mTextColor = 0
private var mFolderPath = ""
private var mDirectories = ArrayList<Directory>()
public override fun onCreate(savedInstanceState: Bundle?) {
useDynamicTheme = false
super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED)
setContentView(R.layout.activity_widget_config)
initVariables()
mWidgetId = intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: AppWidgetManager.INVALID_APPWIDGET_ID
if (mWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish()
}
config_save.setOnClickListener { saveConfig() }
config_bg_color.setOnClickListener { pickBackgroundColor() }
config_text_color.setOnClickListener { pickTextColor() }
folder_picker_value.setOnClickListener { changeSelectedFolder() }
config_image_holder.setOnClickListener { changeSelectedFolder() }
folder_picker_show_folder_name.isChecked = config.showWidgetFolderName
handleFolderNameDisplay()
folder_picker_show_folder_name_holder.setOnClickListener {
folder_picker_show_folder_name.toggle()
handleFolderNameDisplay()
}
updateTextColors(folder_picker_holder)
folder_picker_holder.background = ColorDrawable(config.backgroundColor)
getCachedDirectories(false, false) {
mDirectories = it
val path = it.firstOrNull()?.path
if (path != null) {
updateFolderImage(path)
}
}
}
private fun initVariables() {
mBgColor = config.widgetBgColor
if (mBgColor == 1) {
mBgColor = Color.BLACK
mBgAlpha = .2f
} else {
mBgAlpha = Color.alpha(mBgColor) / 255f
}
mBgColorWithoutTransparency = Color.rgb(Color.red(mBgColor), Color.green(mBgColor), Color.blue(mBgColor))
config_bg_seekbar.apply {
progress = (mBgAlpha * 100).toInt()
onSeekBarChangeListener {
mBgAlpha = it / 100f
updateBackgroundColor()
}
}
updateBackgroundColor()
mTextColor = config.widgetTextColor
updateTextColor()
}
private fun saveConfig() {
val views = RemoteViews(packageName, R.layout.widget)
views.setBackgroundColor(R.id.widget_holder, mBgColor)
AppWidgetManager.getInstance(this).updateAppWidget(mWidgetId, views)
config.showWidgetFolderName = folder_picker_show_folder_name.isChecked
val widget = Widget(null, mWidgetId, mFolderPath)
Thread {
widgetsDB.insertOrUpdate(widget)
}.start()
storeWidgetColors()
requestWidgetUpdate()
Intent().apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
setResult(Activity.RESULT_OK, this)
}
finish()
}
private fun storeWidgetColors() {
config.apply {
widgetBgColor = mBgColor
widgetTextColor = mTextColor
}
}
private fun requestWidgetUpdate() {
Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, MyWidgetProvider::class.java).apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(mWidgetId))
sendBroadcast(this)
}
}
private fun updateBackgroundColor() {
mBgColor = mBgColorWithoutTransparency.adjustAlpha(mBgAlpha)
config_save.setBackgroundColor(mBgColor)
config_image_holder.setBackgroundColor(mBgColor)
config_bg_color.setFillWithStroke(mBgColor, Color.BLACK)
}
private fun updateTextColor() {
config_save.setTextColor(mTextColor)
config_folder_name.setTextColor(mTextColor)
config_text_color.setFillWithStroke(mTextColor, Color.BLACK)
}
private fun pickBackgroundColor() {
ColorPickerDialog(this, mBgColorWithoutTransparency) { wasPositivePressed, color ->
if (wasPositivePressed) {
mBgColorWithoutTransparency = color
updateBackgroundColor()
}
}
}
private fun pickTextColor() {
ColorPickerDialog(this, mTextColor) { wasPositivePressed, color ->
if (wasPositivePressed) {
mTextColor = color
updateTextColor()
}
}
}
private fun changeSelectedFolder() {
PickDirectoryDialog(this, "", false) {
updateFolderImage(it)
}
}
private fun updateFolderImage(folderPath: String) {
mFolderPath = folderPath
runOnUiThread {
folder_picker_value.text = getFolderNameFromPath(folderPath)
config_folder_name.text = getFolderNameFromPath(folderPath)
}
Thread {
val path = directoryDB.getDirectoryThumbnail(folderPath)
if (path != null) {
runOnUiThread {
loadJpg(path, config_image, config.cropThumbnails)
}
}
}.start()
}
private fun handleFolderNameDisplay() {
val showFolderName = folder_picker_show_folder_name.isChecked
config_folder_name.beVisibleIf(showFolderName)
(config_image.layoutParams as RelativeLayout.LayoutParams).bottomMargin = if (showFolderName) 0 else resources.getDimension(R.dimen.normal_margin).toInt()
}
}

View file

@ -1,6 +1,5 @@
package com.simplemobiletools.gallery.adapters package com.simplemobiletools.gallery.pro.adapters
import android.util.SparseArray
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -11,23 +10,22 @@ import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.PropertiesDialog import com.simplemobiletools.commons.dialogs.PropertiesDialog
import com.simplemobiletools.commons.dialogs.RenameItemDialog import com.simplemobiletools.commons.dialogs.RenameItemDialog
import com.simplemobiletools.commons.dialogs.RenameItemsDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.dialogs.ExcludeFolderDialog import com.simplemobiletools.gallery.pro.dialogs.ExcludeFolderDialog
import com.simplemobiletools.gallery.dialogs.PickMediumDialog import com.simplemobiletools.gallery.pro.dialogs.PickMediumDialog
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.interfaces.DirectoryOperationsListener import com.simplemobiletools.gallery.pro.interfaces.DirectoryOperationsListener
import com.simplemobiletools.gallery.models.AlbumCover import com.simplemobiletools.gallery.pro.models.AlbumCover
import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.pro.models.Directory
import kotlinx.android.synthetic.main.directory_item_list.view.* import kotlinx.android.synthetic.main.directory_item_list.view.*
import java.io.File import java.io.File
import java.util.*
import kotlin.collections.ArrayList
class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directory>, val listener: DirectoryOperationsListener?, recyclerView: MyRecyclerView, class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directory>, val listener: DirectoryOperationsListener?, recyclerView: MyRecyclerView,
val isPickIntent: Boolean, fastScroller: FastScroller? = null, itemClick: (Any) -> Unit) : val isPickIntent: Boolean, fastScroller: FastScroller? = null, itemClick: (Any) -> Unit) :
@ -40,6 +38,7 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
private var showMediaCount = config.showMediaCount private var showMediaCount = config.showMediaCount
private var animateGifs = config.animateGifs private var animateGifs = config.animateGifs
private var cropThumbnails = config.cropThumbnails private var cropThumbnails = config.cropThumbnails
private var groupDirectSubfolders = config.groupDirectSubfolders
private var currentDirectoriesHash = dirs.hashCode() private var currentDirectoriesHash = dirs.hashCode()
init { init {
@ -48,14 +47,6 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
override fun getActionMenuId() = R.menu.cab_directories override fun getActionMenuId() = R.menu.cab_directories
override fun prepareItemSelection(viewHolder: ViewHolder) {
viewHolder.itemView.dir_check?.background?.applyColorFilter(primaryColor)
}
override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) {
viewHolder?.itemView?.dir_check?.beVisibleIf(select)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutType = if (isListViewType) R.layout.directory_item_list else R.layout.directory_item_grid val layoutType = if (isListViewType) R.layout.directory_item_list else R.layout.directory_item_grid
return createViewHolder(layoutType, parent) return createViewHolder(layoutType, parent)
@ -63,34 +54,35 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) { override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
val dir = dirs.getOrNull(position) ?: return val dir = dirs.getOrNull(position) ?: return
val view = holder.bindView(dir, true, !isPickIntent) { itemView, adapterPosition -> holder.bindView(dir, true, !isPickIntent) { itemView, adapterPosition ->
setupView(itemView, dir) setupView(itemView, dir)
} }
bindViewHolder(holder, position, view) bindViewHolder(holder)
} }
override fun getItemCount() = dirs.size override fun getItemCount() = dirs.size
override fun prepareActionMode(menu: Menu) { override fun prepareActionMode(menu: Menu) {
if (getSelectedPaths().isEmpty()) { val selectedPaths = getSelectedPaths()
if (selectedPaths.isEmpty()) {
return return
} }
val selectedPaths = getSelectedPaths() val isOneItemSelected = isOneItemSelected()
menu.apply { menu.apply {
findItem(R.id.cab_rename).isVisible = isOneItemSelected() && !selectedPaths.contains(FAVORITES) && !selectedPaths.contains(RECYCLE_BIN) findItem(R.id.cab_rename).isVisible = !selectedPaths.contains(FAVORITES) && !selectedPaths.contains(RECYCLE_BIN)
findItem(R.id.cab_change_cover_image).isVisible = isOneItemSelected() findItem(R.id.cab_change_cover_image).isVisible = isOneItemSelected
findItem(R.id.cab_empty_recycle_bin).isVisible = isOneItemSelected() && selectedPaths.first() == RECYCLE_BIN findItem(R.id.cab_empty_recycle_bin).isVisible = isOneItemSelected && selectedPaths.first() == RECYCLE_BIN
findItem(R.id.cab_empty_disable_recycle_bin).isVisible = isOneItemSelected() && selectedPaths.first() == RECYCLE_BIN findItem(R.id.cab_empty_disable_recycle_bin).isVisible = isOneItemSelected && selectedPaths.first() == RECYCLE_BIN
checkHideBtnVisibility(this) checkHideBtnVisibility(this, selectedPaths)
checkPinBtnVisibility(this) checkPinBtnVisibility(this, selectedPaths)
} }
} }
override fun actionItemPressed(id: Int) { override fun actionItemPressed(id: Int) {
if (selectedPositions.isEmpty()) { if (selectedKeys.isEmpty()) {
return return
} }
@ -105,7 +97,7 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
R.id.cab_unhide -> toggleFoldersVisibility(false) R.id.cab_unhide -> toggleFoldersVisibility(false)
R.id.cab_exclude -> tryExcludeFolder() R.id.cab_exclude -> tryExcludeFolder()
R.id.cab_copy_to -> copyMoveTo(true) R.id.cab_copy_to -> copyMoveTo(true)
R.id.cab_move_to -> copyMoveTo(false) R.id.cab_move_to -> moveFilesTo()
R.id.cab_select_all -> selectAll() R.id.cab_select_all -> selectAll()
R.id.cab_delete -> askConfirmDelete() R.id.cab_delete -> askConfirmDelete()
R.id.cab_select_photo -> changeAlbumCover(false) R.id.cab_select_photo -> changeAlbumCover(false)
@ -117,49 +109,33 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
override fun getIsItemSelectable(position: Int) = true override fun getIsItemSelectable(position: Int) = true
override fun getItemSelectionKey(position: Int) = dirs.getOrNull(position)?.path?.hashCode()
override fun getItemKeyPosition(key: Int) = dirs.indexOfFirst { it.path.hashCode() == key }
override fun onViewRecycled(holder: ViewHolder) { override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder) super.onViewRecycled(holder)
if (!activity.isActivityDestroyed()) { if (!activity.isDestroyed) {
Glide.with(activity).clear(holder.itemView?.dir_thumbnail!!) Glide.with(activity).clear(holder.itemView.dir_thumbnail!!)
} }
} }
private fun checkHideBtnVisibility(menu: Menu) { private fun checkHideBtnVisibility(menu: Menu, selectedPaths: ArrayList<String>) {
var hiddenCnt = 0 menu.findItem(R.id.cab_hide).isVisible = selectedPaths.any { !File(it).doesThisOrParentHaveNoMedia() }
var unhiddenCnt = 0 menu.findItem(R.id.cab_unhide).isVisible = selectedPaths.any { File(it).doesThisOrParentHaveNoMedia() }
selectedPositions.mapNotNull { dirs.getOrNull(it)?.path }.forEach {
if (File(it).doesThisOrParentHaveNoMedia()) {
hiddenCnt++
} else {
unhiddenCnt++
}
}
menu.findItem(R.id.cab_hide).isVisible = unhiddenCnt > 0
menu.findItem(R.id.cab_unhide).isVisible = hiddenCnt > 0
} }
private fun checkPinBtnVisibility(menu: Menu) { private fun checkPinBtnVisibility(menu: Menu, selectedPaths: ArrayList<String>) {
val pinnedFolders = config.pinnedFolders val pinnedFolders = config.pinnedFolders
var pinnedCnt = 0 menu.findItem(R.id.cab_pin).isVisible = selectedPaths.any { !pinnedFolders.contains(it) }
var unpinnedCnt = 0 menu.findItem(R.id.cab_unpin).isVisible = selectedPaths.any { pinnedFolders.contains(it) }
selectedPositions.mapNotNull { dirs.getOrNull(it)?.path }.forEach {
if (pinnedFolders.contains(it)) {
pinnedCnt++
} else {
unpinnedCnt++
}
}
menu.findItem(R.id.cab_pin).isVisible = unpinnedCnt > 0
menu.findItem(R.id.cab_unpin).isVisible = pinnedCnt > 0
} }
private fun showProperties() { private fun showProperties() {
if (selectedPositions.size <= 1) { if (selectedKeys.size <= 1) {
val path = dirs[selectedPositions.first()].path val path = getFirstSelectedItemPath() ?: return
if (path != FAVORITES && path != RECYCLE_BIN) { if (path != FAVORITES && path != RECYCLE_BIN) {
PropertiesDialog(activity, dirs[selectedPositions.first()].path, config.shouldShowHidden) PropertiesDialog(activity, path, config.shouldShowHidden)
} }
} else { } else {
PropertiesDialog(activity, getSelectedPaths().filter { it != FAVORITES && it != RECYCLE_BIN }.toMutableList(), config.shouldShowHidden) PropertiesDialog(activity, getSelectedPaths().filter { it != FAVORITES && it != RECYCLE_BIN }.toMutableList(), config.shouldShowHidden)
@ -167,26 +143,33 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
} }
private fun renameDir() { private fun renameDir() {
val firstDir = dirs[selectedPositions.first()] if (selectedKeys.size == 1) {
val sourcePath = firstDir.path val firstDir = getFirstSelectedItem() ?: return
val dir = File(sourcePath) val sourcePath = firstDir.path
if (activity.isAStorageRootFolder(dir.absolutePath)) { val dir = File(sourcePath)
activity.toast(R.string.rename_folder_root) if (activity.isAStorageRootFolder(dir.absolutePath)) {
return activity.toast(R.string.rename_folder_root)
} return
}
RenameItemDialog(activity, dir.absolutePath) { RenameItemDialog(activity, dir.absolutePath) {
activity.runOnUiThread { activity.runOnUiThread {
firstDir.apply { firstDir.apply {
path = it path = it
name = it.getFilenameFromPath() name = it.getFilenameFromPath()
tmb = File(it, tmb.getFilenameFromPath()).absolutePath tmb = File(it, tmb.getFilenameFromPath()).absolutePath
}
updateDirs(dirs)
Thread {
activity.galleryDB.DirectoryDao().updateDirectoryAfterRename(firstDir.tmb, firstDir.name, firstDir.path, sourcePath)
listener?.refreshItems()
}.start()
} }
updateDirs(dirs) }
Thread { } else {
activity.galleryDB.DirectoryDao().updateDirectoryAfterRename(firstDir.tmb, firstDir.name, firstDir.path, sourcePath) val paths = getSelectedPaths().filter { !activity.isAStorageRootFolder(it) } as ArrayList<String>
listener?.refreshItems() RenameItemsDialog(activity, paths) {
}.start() listener?.refreshItems()
} }
} }
} }
@ -283,18 +266,6 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
notifyItemRemoved(it) notifyItemRemoved(it)
} }
val newViewHolders = SparseArray<ViewHolder>()
val cnt = viewHolders.size()
for (i in 0..cnt) {
if (affectedPositions.contains(i)) {
continue
}
val view = viewHolders.get(i, null)
val newIndex = i - selectedPositions.count { it <= i }
newViewHolders.put(newIndex, view)
}
viewHolders = newViewHolders
currentDirectoriesHash = newDirs.hashCode() currentDirectoriesHash = newDirs.hashCode()
dirs = newDirs dirs = newDirs
@ -330,27 +301,38 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
private fun pinFolders(pin: Boolean) { private fun pinFolders(pin: Boolean) {
if (pin) { if (pin) {
config.addPinnedFolders(getSelectedPaths()) config.addPinnedFolders(getSelectedPaths().toHashSet())
} else { } else {
config.removePinnedFolders(getSelectedPaths()) config.removePinnedFolders(getSelectedPaths().toHashSet())
} }
currentDirectoriesHash = 0
pinnedFolders = config.pinnedFolders pinnedFolders = config.pinnedFolders
listener?.recheckPinnedFolders() listener?.recheckPinnedFolders()
notifyDataSetChanged() }
finishActMode()
private fun moveFilesTo() {
activity.handleDeletePasswordProtection {
copyMoveTo(false)
}
} }
private fun copyMoveTo(isCopyOperation: Boolean) { private fun copyMoveTo(isCopyOperation: Boolean) {
val paths = ArrayList<String>() val paths = ArrayList<String>()
val showHidden = activity.config.shouldShowHidden val showHidden = activity.config.shouldShowHidden
selectedPositions.forEach { getSelectedPaths().forEach {
val path = dirs[it].path if (it.startsWith(OTG_PATH)) {
if (path.startsWith(OTG_PATH)) { paths.addAll(getOTGFilePaths(it, showHidden))
paths.addAll(getOTGFilePaths(path, showHidden)) } else if (it != FAVORITES) {
} else if (path != FAVORITES) { val filter = config.filterMedia
File(path).listFiles()?.filter { File(it).listFiles()?.filter {
!activity.getIsPathDirectory(it.absolutePath) && it.isMediaFile() && (showHidden || !it.name.startsWith('.')) !activity.getIsPathDirectory(it.absolutePath) &&
it.absolutePath.isMediaFile() && (showHidden || !it.name.startsWith('.')) &&
((it.isImageFast() && filter and TYPE_IMAGES != 0) ||
(it.isVideoFast() && filter and TYPE_VIDEOS != 0) ||
(it.isGif() && filter and TYPE_GIFS != 0) ||
(it.isRawFast() && filter and TYPE_RAWS != 0) ||
(it.isSvg() && filter and TYPE_SVGS != 0))
}?.mapTo(paths) { it.absolutePath } }?.mapTo(paths) { it.absolutePath }
} }
} }
@ -365,8 +347,16 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
private fun getOTGFilePaths(path: String, showHidden: Boolean): ArrayList<String> { private fun getOTGFilePaths(path: String, showHidden: Boolean): ArrayList<String> {
val paths = ArrayList<String>() val paths = ArrayList<String>()
activity.getOTGFolderChildren(path)?.forEach { val filter = config.filterMedia
if (!it.isDirectory && it.name.isMediaFile() && (showHidden || !it.name.startsWith('.'))) { activity.getOTGFolderChildren(path)?.filter { it.name != null }?.forEach {
if (!it.isDirectory &&
it.name!!.isMediaFile() && (showHidden || !it.name!!.startsWith('.')) &&
((it.name!!.isImageFast() && filter and TYPE_IMAGES != 0) ||
(it.name!!.isVideoFast() && filter and TYPE_VIDEOS != 0) ||
(it.name!!.isGif() && filter and TYPE_GIFS != 0) ||
(it.name!!.isRawFast() && filter and TYPE_RAWS != 0) ||
(it.name!!.isSvg() && filter and TYPE_SVGS != 0))
) {
val relativePath = it.uri.path.substringAfterLast("${activity.config.OTGPartition}:") val relativePath = it.uri.path.substringAfterLast("${activity.config.OTGPartition}:")
paths.add("$OTG_PATH$relativePath") paths.add("$OTG_PATH$relativePath")
} }
@ -375,81 +365,80 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
} }
private fun askConfirmDelete() { private fun askConfirmDelete() {
if (config.skipDeleteConfirmation) { when {
deleteFolders() config.isDeletePasswordProtectionOn -> activity.handleDeletePasswordProtection {
} else {
val itemsCnt = selectedPositions.size
val items = resources.getQuantityString(R.plurals.delete_items, itemsCnt, itemsCnt)
val fileDirItem = dirs.getOrNull(selectedPositions.first()) ?: return
val baseString = if (!config.useRecycleBin || (isOneItemSelected() && fileDirItem.isRecycleBin()) || (isOneItemSelected() && fileDirItem.areFavorites())) {
R.string.deletion_confirmation
} else {
R.string.move_to_recycle_bin_confirmation
}
var question = String.format(resources.getString(baseString), items)
val warning = resources.getQuantityString(R.plurals.delete_warning, itemsCnt, itemsCnt)
question += "\n\n$warning"
ConfirmationDialog(activity, question) {
deleteFolders() deleteFolders()
} }
config.skipDeleteConfirmation -> deleteFolders()
else -> {
val itemsCnt = selectedKeys.size
val items = if (itemsCnt == 1) {
"\"${getSelectedPaths().first().getFilenameFromPath()}\""
} else {
resources.getQuantityString(R.plurals.delete_items, itemsCnt, itemsCnt)
}
val fileDirItem = getFirstSelectedItem() ?: return
val baseString = if (!config.useRecycleBin || (isOneItemSelected() && fileDirItem.isRecycleBin()) || (isOneItemSelected() && fileDirItem.areFavorites())) {
R.string.deletion_confirmation
} else {
R.string.move_to_recycle_bin_confirmation
}
var question = String.format(resources.getString(baseString), items)
val warning = resources.getQuantityString(R.plurals.delete_warning, itemsCnt, itemsCnt)
question += "\n\n$warning"
ConfirmationDialog(activity, question) {
deleteFolders()
}
}
} }
} }
private fun deleteFolders() { private fun deleteFolders() {
if (selectedPositions.isEmpty()) { if (selectedKeys.isEmpty()) {
return return
} }
val folders = ArrayList<File>(selectedPositions.size)
val removeFolders = ArrayList<Directory>(selectedPositions.size)
var SAFPath = "" var SAFPath = ""
selectedPositions.forEach { val selectedDirs = getSelectedItems()
if (dirs.size > it) { selectedDirs.forEach {
val path = dirs[it].path val path = it.path
if (activity.needsStupidWritePermissions(path) && config.treeUri.isEmpty()) { if (activity.needsStupidWritePermissions(path) && config.treeUri.isEmpty()) {
SAFPath = path SAFPath = path
}
} }
} }
activity.handleSAFDialog(SAFPath) { activity.handleSAFDialog(SAFPath) {
selectedPositions.sortedDescending().forEach { val foldersToDelete = ArrayList<File>(selectedKeys.size)
val directory = dirs.getOrNull(it) selectedDirs.forEach {
if (directory != null) { if (it.areFavorites() || it.isRecycleBin()) {
if (directory.areFavorites() || directory.isRecycleBin()) { if (it.isRecycleBin()) {
if (directory.isRecycleBin()) { tryEmptyRecycleBin(false)
tryEmptyRecycleBin(false)
} else {
Thread {
activity.galleryDB.MediumDao().clearFavorites()
listener?.refreshItems()
}.start()
}
if (selectedPositions.size == 1) {
finishActMode()
} else {
selectedPositions.remove(it)
toggleItemSelection(false, it)
}
} else { } else {
folders.add(File(directory.path)) Thread {
removeFolders.add(directory) activity.galleryDB.MediumDao().clearFavorites()
listener?.refreshItems()
}.start()
} }
if (selectedKeys.size == 1) {
finishActMode()
}
} else {
foldersToDelete.add(File(it.path))
} }
} }
listener?.deleteFolders(folders) listener?.deleteFolders(foldersToDelete)
} }
} }
private fun changeAlbumCover(useDefault: Boolean) { private fun changeAlbumCover(useDefault: Boolean) {
if (selectedPositions.size != 1) if (selectedKeys.size != 1)
return return
val path = dirs[selectedPositions.first()].path val path = getFirstSelectedItemPath() ?: return
if (useDefault) { if (useDefault) {
val albumCovers = getAlbumCoversWithout(path) val albumCovers = getAlbumCoversWithout(path)
@ -480,15 +469,15 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
listener?.refreshItems() listener?.refreshItems()
} }
private fun getSelectedPaths(): HashSet<String> { private fun getSelectedItems() = dirs.filter { selectedKeys.contains(it.path.hashCode()) } as ArrayList<Directory>
val paths = HashSet<String>(selectedPositions.size)
selectedPositions.forEach { private fun getSelectedPaths() = getSelectedItems().map { it.path } as ArrayList<String>
(dirs.getOrNull(it))?.apply {
paths.add(path) private fun getFirstSelectedItem() = getItemWithKey(selectedKeys.first())
}
} private fun getFirstSelectedItemPath() = getFirstSelectedItem()?.path
return paths
} private fun getItemWithKey(key: Int): Directory? = dirs.firstOrNull { it.path.hashCode() == key }
fun updateDirs(newDirs: ArrayList<Directory>) { fun updateDirs(newDirs: ArrayList<Directory>) {
val directories = newDirs.clone() as ArrayList<Directory> val directories = newDirs.clone() as ArrayList<Directory>
@ -516,10 +505,11 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
} }
private fun setupView(view: View, directory: Directory) { private fun setupView(view: View, directory: Directory) {
val isSelected = selectedKeys.contains(directory.path.hashCode())
view.apply { view.apply {
dir_name.text = directory.name dir_name.text = if (groupDirectSubfolders) "${directory.name} (${directory.subfoldersCount})" else directory.name
dir_path?.text = "${directory.path.substringBeforeLast("/")}/" dir_path?.text = "${directory.path.substringBeforeLast("/")}/"
photo_cnt.text = directory.mediaCnt.toString() photo_cnt.text = directory.subfoldersMediaCount.toString()
val thumbnailType = when { val thumbnailType = when {
directory.tmb.isVideoFast() -> TYPE_VIDEOS directory.tmb.isVideoFast() -> TYPE_VIDEOS
directory.tmb.isGif() -> TYPE_GIFS directory.tmb.isGif() -> TYPE_GIFS
@ -528,6 +518,11 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
else -> TYPE_IMAGES else -> TYPE_IMAGES
} }
dir_check?.beVisibleIf(isSelected)
if (isSelected) {
dir_check.background?.applyColorFilter(primaryColor)
}
activity.loadImage(thumbnailType, directory.tmb, dir_thumbnail, scrollHorizontally, animateGifs, cropThumbnails) activity.loadImage(thumbnailType, directory.tmb, dir_thumbnail, scrollHorizontally, animateGifs, cropThumbnails)
dir_pin.beVisibleIf(pinnedFolders.contains(directory.path)) dir_pin.beVisibleIf(pinnedFolders.contains(directory.path))
dir_location.beVisibleIf(directory.location != LOCAITON_INTERNAL) dir_location.beVisibleIf(directory.location != LOCAITON_INTERNAL)

View file

@ -1,37 +1,34 @@
package com.simplemobiletools.gallery.adapters package com.simplemobiletools.gallery.pro.adapters
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.simplemobiletools.gallery.R import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.gallery.interfaces.FilterAdapterListener import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.models.FilterItem import com.simplemobiletools.gallery.pro.models.FilterItem
import kotlinx.android.synthetic.main.editor_filter_item.view.* import kotlinx.android.synthetic.main.editor_filter_item.view.*
import java.util.* import java.util.*
class FiltersAdapter(val context: Context, val filterItems: ArrayList<FilterItem>, val itemClick: (Int) -> Unit) : RecyclerView.Adapter<FiltersAdapter.ViewHolder>(), class FiltersAdapter(val context: Context, val filterItems: ArrayList<FilterItem>, val itemClick: (Int) -> Unit) : RecyclerView.Adapter<FiltersAdapter.ViewHolder>() {
FilterAdapterListener {
private var currentSelection = filterItems.first() private var currentSelection = filterItems.first()
private var strokeBackground = context.resources.getDrawable(R.drawable.stroke_background) private var strokeBackground = context.resources.getDrawable(R.drawable.stroke_background)
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindView(filterItems[position], strokeBackground) holder.bindView(filterItems[position])
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.editor_filter_item, parent, false) val view = LayoutInflater.from(parent.context).inflate(R.layout.editor_filter_item, parent, false)
return ViewHolder(view, this) return ViewHolder(view)
} }
override fun getItemCount() = filterItems.size override fun getItemCount() = filterItems.size
override fun getCurrentFilter() = currentSelection fun getCurrentFilter() = currentSelection
override fun setCurrentFilter(position: Int) { private fun setCurrentFilter(position: Int) {
val filterItem = filterItems.getOrNull(position) ?: return val filterItem = filterItems.getOrNull(position) ?: return
if (currentSelection != filterItem) { if (currentSelection != filterItem) {
currentSelection = filterItem currentSelection = filterItem
@ -40,19 +37,19 @@ class FiltersAdapter(val context: Context, val filterItems: ArrayList<FilterItem
} }
} }
class ViewHolder(view: View, val filterAdapterListener: FilterAdapterListener) : RecyclerView.ViewHolder(view) { inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindView(filterItem: FilterItem, strokeBackground: Drawable): View { fun bindView(filterItem: FilterItem): View {
itemView.apply { itemView.apply {
editor_filter_item_label.text = filterItem.filter.name editor_filter_item_label.text = filterItem.filter.name
editor_filter_item_thumbnail.setImageBitmap(filterItem.bitmap) editor_filter_item_thumbnail.setImageBitmap(filterItem.bitmap)
editor_filter_item_thumbnail.background = if (filterAdapterListener.getCurrentFilter() == filterItem) { editor_filter_item_thumbnail.background = if (getCurrentFilter() == filterItem) {
strokeBackground strokeBackground
} else { } else {
null null
} }
setOnClickListener { setOnClickListener {
filterAdapterListener.setCurrentFilter(adapterPosition) setCurrentFilter(adapterPosition)
} }
} }
return itemView return itemView

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.adapters package com.simplemobiletools.gallery.pro.adapters
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
@ -7,8 +7,8 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import kotlinx.android.synthetic.main.item_manage_folder.view.* import kotlinx.android.synthetic.main.item_manage_folder.view.*
import java.util.* import java.util.*
@ -25,12 +25,6 @@ class ManageFoldersAdapter(activity: BaseSimpleActivity, var folders: ArrayList<
override fun prepareActionMode(menu: Menu) {} override fun prepareActionMode(menu: Menu) {}
override fun prepareItemSelection(viewHolder: ViewHolder) {}
override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) {
viewHolder?.itemView?.manage_folder_holder?.isSelected = select
}
override fun actionItemPressed(id: Int) { override fun actionItemPressed(id: Int) {
when (id) { when (id) {
R.id.cab_remove -> removeSelection() R.id.cab_remove -> removeSelection()
@ -41,20 +35,27 @@ class ManageFoldersAdapter(activity: BaseSimpleActivity, var folders: ArrayList<
override fun getIsItemSelectable(position: Int) = true override fun getIsItemSelectable(position: Int) = true
override fun getItemSelectionKey(position: Int) = folders.getOrNull(position)?.hashCode()
override fun getItemKeyPosition(key: Int) = folders.indexOfFirst { it.hashCode() == key }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_manage_folder, parent) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_manage_folder, parent)
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val folder = folders[position] val folder = folders[position]
val view = holder.bindView(folder, true, true) { itemView, adapterPosition -> holder.bindView(folder, true, true) { itemView, adapterPosition ->
setupView(itemView, folder) setupView(itemView, folder)
} }
bindViewHolder(holder, position, view) bindViewHolder(holder)
} }
override fun getItemCount() = folders.size override fun getItemCount() = folders.size
private fun getSelectedItems() = folders.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<String>
private fun setupView(view: View, folder: String) { private fun setupView(view: View, folder: String) {
view.apply { view.apply {
manage_folder_holder?.isSelected = selectedKeys.contains(folder.hashCode())
manage_folder_title.apply { manage_folder_title.apply {
text = folder text = folder
setTextColor(config.textColor) setTextColor(config.textColor)
@ -63,20 +64,20 @@ class ManageFoldersAdapter(activity: BaseSimpleActivity, var folders: ArrayList<
} }
private fun removeSelection() { private fun removeSelection() {
val removeFolders = ArrayList<String>(selectedPositions.size) val removeFolders = ArrayList<String>(selectedKeys.size)
val positions = getSelectedItemPositions()
selectedPositions.sortedDescending().forEach { getSelectedItems().forEach {
val folder = folders[it] removeFolders.add(it)
removeFolders.add(folder)
if (isShowingExcludedFolders) { if (isShowingExcludedFolders) {
config.removeExcludedFolder(folder) config.removeExcludedFolder(it)
} else { } else {
config.removeIncludedFolder(folder) config.removeIncludedFolder(it)
} }
} }
folders.removeAll(removeFolders) folders.removeAll(removeFolders)
removeSelectedItems() removeSelectedItems(positions)
if (folders.isEmpty()) { if (folders.isEmpty()) {
listener?.refreshItems() listener?.refreshItems()
} }

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.adapters package com.simplemobiletools.gallery.pro.adapters
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
@ -8,9 +8,9 @@ import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.extensions.isPathOnSD import com.simplemobiletools.commons.extensions.isPathOnSD
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.extensions.removeNoMedia import com.simplemobiletools.gallery.pro.extensions.removeNoMedia
import kotlinx.android.synthetic.main.item_manage_folder.view.* import kotlinx.android.synthetic.main.item_manage_folder.view.*
import java.util.* import java.util.*
@ -27,12 +27,6 @@ class ManageHiddenFoldersAdapter(activity: BaseSimpleActivity, var folders: Arra
override fun prepareActionMode(menu: Menu) {} override fun prepareActionMode(menu: Menu) {}
override fun prepareItemSelection(viewHolder: ViewHolder) {}
override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) {
viewHolder?.itemView?.manage_folder_holder?.isSelected = select
}
override fun actionItemPressed(id: Int) { override fun actionItemPressed(id: Int) {
when (id) { when (id) {
R.id.cab_unhide -> tryUnhideFolders() R.id.cab_unhide -> tryUnhideFolders()
@ -43,20 +37,27 @@ class ManageHiddenFoldersAdapter(activity: BaseSimpleActivity, var folders: Arra
override fun getIsItemSelectable(position: Int) = true override fun getIsItemSelectable(position: Int) = true
override fun getItemSelectionKey(position: Int) = folders.getOrNull(position)?.hashCode()
override fun getItemKeyPosition(key: Int) = folders.indexOfFirst { it.hashCode() == key }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_manage_folder, parent) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_manage_folder, parent)
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val folder = folders[position] val folder = folders[position]
val view = holder.bindView(folder, true, true) { itemView, adapterPosition -> holder.bindView(folder, true, true) { itemView, adapterPosition ->
setupView(itemView, folder) setupView(itemView, folder)
} }
bindViewHolder(holder, position, view) bindViewHolder(holder)
} }
override fun getItemCount() = folders.size override fun getItemCount() = folders.size
private fun getSelectedItems() = folders.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<String>
private fun setupView(view: View, folder: String) { private fun setupView(view: View, folder: String) {
view.apply { view.apply {
manage_folder_holder?.isSelected = selectedKeys.contains(folder.hashCode())
manage_folder_title.apply { manage_folder_title.apply {
text = folder text = folder
setTextColor(config.textColor) setTextColor(config.textColor)
@ -65,12 +66,12 @@ class ManageHiddenFoldersAdapter(activity: BaseSimpleActivity, var folders: Arra
} }
private fun tryUnhideFolders() { private fun tryUnhideFolders() {
val removeFolders = ArrayList<String>(selectedPositions.size) val removeFolders = ArrayList<String>(selectedKeys.size)
val sdCardPaths = ArrayList<String>() val sdCardPaths = ArrayList<String>()
selectedPositions.forEach { getSelectedItems().forEach {
if (activity.isPathOnSD(folders[it])) { if (activity.isPathOnSD(it)) {
sdCardPaths.add(folders[it]) sdCardPaths.add(it)
} }
} }
@ -84,14 +85,14 @@ class ManageHiddenFoldersAdapter(activity: BaseSimpleActivity, var folders: Arra
} }
private fun unhideFolders(removeFolders: ArrayList<String>) { private fun unhideFolders(removeFolders: ArrayList<String>) {
selectedPositions.sortedDescending().forEach { val position = getSelectedItemPositions()
val folder = folders[it] getSelectedItems().forEach {
removeFolders.add(folder) removeFolders.add(it)
activity.removeNoMedia(folder) activity.removeNoMedia(it)
} }
folders.removeAll(removeFolders) folders.removeAll(removeFolders)
removeSelectedItems() removeSelectedItems(position)
if (folders.isEmpty()) { if (folders.isEmpty()) {
listener?.refreshItems() listener?.refreshItems()
} }

View file

@ -1,50 +1,49 @@
package com.simplemobiletools.gallery.adapters package com.simplemobiletools.gallery.pro.adapters
import android.content.ContentProviderOperation
import android.media.ExifInterface
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.MediaStore
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.dialogs.PropertiesDialog import com.simplemobiletools.commons.dialogs.PropertiesDialog
import com.simplemobiletools.commons.dialogs.RenameItemDialog import com.simplemobiletools.commons.dialogs.RenameItemDialog
import com.simplemobiletools.commons.dialogs.RenameItemsDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.dialogs.DeleteWithRememberDialog import com.simplemobiletools.gallery.pro.dialogs.DeleteWithRememberDialog
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.helpers.VIEW_TYPE_LIST import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL
import com.simplemobiletools.gallery.interfaces.MediaOperationsListener import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_LIST
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.interfaces.MediaOperationsListener
import com.simplemobiletools.gallery.models.ThumbnailItem import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailSection import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import com.simplemobiletools.gallery.pro.models.ThumbnailSection
import kotlinx.android.synthetic.main.photo_video_item_grid.view.* import kotlinx.android.synthetic.main.photo_video_item_grid.view.*
import kotlinx.android.synthetic.main.thumbnail_section.view.* import kotlinx.android.synthetic.main.thumbnail_section.view.*
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.* import java.util.*
class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<ThumbnailItem>, val listener: MediaOperationsListener?, val isAGetIntent: Boolean, class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<ThumbnailItem>, val listener: MediaOperationsListener?, val isAGetIntent: Boolean,
val allowMultiplePicks: Boolean, recyclerView: MyRecyclerView, fastScroller: FastScroller? = null, itemClick: (Any) -> Unit) : val allowMultiplePicks: Boolean, val path: String, recyclerView: MyRecyclerView, fastScroller: FastScroller? = null, itemClick: (Any) -> Unit) :
MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
private val INSTANT_LOAD_DURATION = 2000L private val INSTANT_LOAD_DURATION = 2000L
private val IMAGE_LOAD_DELAY = 100L private val IMAGE_LOAD_DELAY = 100L
private val BATCH_SIZE = 100
private val ITEM_SECTION = 0 private val ITEM_SECTION = 0
private val ITEM_MEDIUM = 1 private val ITEM_MEDIUM = 1
private val config = activity.config private val config = activity.config
private val isListViewType = config.viewTypeFiles == VIEW_TYPE_LIST private val viewType = config.getFolderViewType(if (config.showAll) SHOW_ALL else path)
private val isListViewType = viewType == VIEW_TYPE_LIST
private var visibleItemPaths = ArrayList<String>() private var visibleItemPaths = ArrayList<String>()
private var rotatedImagePaths = ArrayList<String>()
private var loadImageInstantly = false private var loadImageInstantly = false
private var delayHandler = Handler(Looper.getMainLooper()) private var delayHandler = Handler(Looper.getMainLooper())
private var currentMediaHash = media.hashCode() private var currentMediaHash = media.hashCode()
@ -62,14 +61,6 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
override fun getActionMenuId() = R.menu.cab_media override fun getActionMenuId() = R.menu.cab_media
override fun prepareItemSelection(viewHolder: ViewHolder) {
viewHolder.itemView?.medium_check?.background?.applyColorFilter(primaryColor)
}
override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) {
viewHolder?.itemView?.medium_check?.beVisibleIf(select)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutType = if (viewType == ITEM_SECTION) { val layoutType = if (viewType == ITEM_SECTION) {
R.layout.thumbnail_section R.layout.thumbnail_section
@ -89,15 +80,15 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
visibleItemPaths.add(tmbItem.path) visibleItemPaths.add(tmbItem.path)
} }
val allowLongPress = !allowMultiplePicks && tmbItem is Medium val allowLongPress = (!isAGetIntent || allowMultiplePicks) && tmbItem is Medium
val view = holder.bindView(tmbItem, tmbItem is Medium, allowLongPress) { itemView, adapterPosition -> holder.bindView(tmbItem, tmbItem is Medium, allowLongPress) { itemView, adapterPosition ->
if (tmbItem is Medium) { if (tmbItem is Medium) {
setupThumbnail(itemView, tmbItem) setupThumbnail(itemView, tmbItem)
} else { } else {
setupSection(itemView, tmbItem as ThumbnailSection) setupSection(itemView, tmbItem as ThumbnailSection)
} }
} }
bindViewHolder(holder, position, view) bindViewHolder(holder)
} }
override fun getItemCount() = media.size override fun getItemCount() = media.size
@ -112,19 +103,26 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
} }
override fun prepareActionMode(menu: Menu) { override fun prepareActionMode(menu: Menu) {
menu.apply { val selectedItems = getSelectedItems()
findItem(R.id.cab_rename).isVisible = isOneItemSelected() && getSelectedMedia().firstOrNull()?.getIsInRecycleBin() == false if (selectedItems.isEmpty()) {
findItem(R.id.cab_open_with).isVisible = isOneItemSelected() return
findItem(R.id.cab_confirm_selection).isVisible = isAGetIntent && allowMultiplePicks && selectedPositions.size > 0 }
findItem(R.id.cab_restore_recycle_bin_files).isVisible = getSelectedPaths().all { it.startsWith(activity.filesDir.absolutePath) }
checkHideBtnVisibility(this) val isOneItemSelected = isOneItemSelected()
checkFavoriteBtnVisibility(this) val selectedPaths = selectedItems.map { it.path } as ArrayList<String>
menu.apply {
findItem(R.id.cab_rename).isVisible = selectedItems.firstOrNull()?.getIsInRecycleBin() == false
findItem(R.id.cab_open_with).isVisible = isOneItemSelected
findItem(R.id.cab_confirm_selection).isVisible = isAGetIntent && allowMultiplePicks && selectedKeys.isNotEmpty()
findItem(R.id.cab_restore_recycle_bin_files).isVisible = selectedPaths.all { it.startsWith(activity.recycleBinPath) }
checkHideBtnVisibility(this, selectedItems)
checkFavoriteBtnVisibility(this, selectedItems)
} }
} }
override fun actionItemPressed(id: Int) { override fun actionItemPressed(id: Int) {
if (selectedPositions.isEmpty()) { if (selectedKeys.isEmpty()) {
return return
} }
@ -139,12 +137,15 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
R.id.cab_remove_from_favorites -> toggleFavorites(false) R.id.cab_remove_from_favorites -> toggleFavorites(false)
R.id.cab_restore_recycle_bin_files -> restoreFiles() R.id.cab_restore_recycle_bin_files -> restoreFiles()
R.id.cab_share -> shareMedia() R.id.cab_share -> shareMedia()
R.id.cab_rotate_right -> rotateSelection(90)
R.id.cab_rotate_left -> rotateSelection(270)
R.id.cab_rotate_one_eighty -> rotateSelection(180)
R.id.cab_copy_to -> copyMoveTo(true) R.id.cab_copy_to -> copyMoveTo(true)
R.id.cab_move_to -> copyMoveTo(false) R.id.cab_move_to -> moveFilesTo()
R.id.cab_select_all -> selectAll() R.id.cab_select_all -> selectAll()
R.id.cab_open_with -> activity.openPath(getCurrentPath(), true) R.id.cab_open_with -> openPath()
R.id.cab_fix_date_taken -> fixDateTaken() R.id.cab_fix_date_taken -> fixDateTaken()
R.id.cab_set_as -> activity.setAs(getCurrentPath()) R.id.cab_set_as -> setAs()
R.id.cab_delete -> checkDeleteConfirmation() R.id.cab_delete -> checkDeleteConfirmation()
} }
} }
@ -153,12 +154,16 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
override fun getIsItemSelectable(position: Int) = !isASectionTitle(position) override fun getIsItemSelectable(position: Int) = !isASectionTitle(position)
override fun getItemSelectionKey(position: Int) = (media.getOrNull(position) as? Medium)?.path?.hashCode()
override fun getItemKeyPosition(key: Int) = media.indexOfFirst { (it as? Medium)?.path?.hashCode() == key }
override fun onViewRecycled(holder: ViewHolder) { override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder) super.onViewRecycled(holder)
if (!activity.isActivityDestroyed()) { if (!activity.isDestroyed) {
val itemView = holder.itemView val itemView = holder.itemView
visibleItemPaths.remove(itemView?.photo_name?.tag) visibleItemPaths.remove(itemView.medium_name?.tag)
val tmb = itemView?.medium_thumbnail val tmb = itemView.medium_thumbnail
if (tmb != null) { if (tmb != null) {
Glide.with(activity).clear(tmb) Glide.with(activity).clear(tmb)
} }
@ -167,34 +172,15 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
fun isASectionTitle(position: Int) = media.getOrNull(position) is ThumbnailSection fun isASectionTitle(position: Int) = media.getOrNull(position) is ThumbnailSection
private fun checkHideBtnVisibility(menu: Menu) { private fun checkHideBtnVisibility(menu: Menu, selectedItems: ArrayList<Medium>) {
var hiddenCnt = 0 val isInRecycleBin = selectedItems.firstOrNull()?.getIsInRecycleBin() == true
var unhiddenCnt = 0 menu.findItem(R.id.cab_hide).isVisible = !isInRecycleBin && selectedItems.any { !it.isHidden() }
getSelectedMedia().forEach { menu.findItem(R.id.cab_unhide).isVisible = !isInRecycleBin && selectedItems.any { it.isHidden() }
if (it.isHidden()) {
hiddenCnt++
} else {
unhiddenCnt++
}
}
menu.findItem(R.id.cab_hide).isVisible = unhiddenCnt > 0
menu.findItem(R.id.cab_unhide).isVisible = hiddenCnt > 0
} }
private fun checkFavoriteBtnVisibility(menu: Menu) { private fun checkFavoriteBtnVisibility(menu: Menu, selectedItems: ArrayList<Medium>) {
var favoriteCnt = 0 menu.findItem(R.id.cab_add_to_favorites).isVisible = selectedItems.any { !it.isFavorite }
var nonFavoriteCnt = 0 menu.findItem(R.id.cab_remove_from_favorites).isVisible = selectedItems.any { it.isFavorite }
getSelectedMedia().forEach {
if (it.isFavorite) {
favoriteCnt++
} else {
nonFavoriteCnt++
}
}
menu.findItem(R.id.cab_add_to_favorites).isVisible = nonFavoriteCnt > 0
menu.findItem(R.id.cab_remove_from_favorites).isVisible = favoriteCnt > 0
} }
private fun confirmSelection() { private fun confirmSelection() {
@ -202,8 +188,9 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
} }
private fun showProperties() { private fun showProperties() {
if (selectedPositions.size <= 1) { if (selectedKeys.size <= 1) {
PropertiesDialog(activity, (media[selectedPositions.first()] as Medium).path, config.shouldShowHidden) val path = getFirstSelectedItemPath() ?: return
PropertiesDialog(activity, path, config.shouldShowHidden)
} else { } else {
val paths = getSelectedPaths() val paths = getSelectedPaths()
PropertiesDialog(activity, paths, config.shouldShowHidden) PropertiesDialog(activity, paths, config.shouldShowHidden)
@ -211,27 +198,46 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
} }
private fun renameFile() { private fun renameFile() {
val oldPath = getCurrentPath() if (selectedKeys.size == 1) {
RenameItemDialog(activity, oldPath) { val oldPath = getFirstSelectedItemPath() ?: return
Thread { RenameItemDialog(activity, oldPath) {
activity.updateDBMediaPath(oldPath, it) Thread {
activity.updateDBMediaPath(oldPath, it)
activity.runOnUiThread { activity.runOnUiThread {
enableInstantLoad() enableInstantLoad()
listener?.refreshItems() listener?.refreshItems()
finishActMode() finishActMode()
} }
}.start() }.start()
}
} else {
RenameItemsDialog(activity, getSelectedPaths()) {
enableInstantLoad()
listener?.refreshItems()
finishActMode()
}
} }
} }
private fun editFile() { private fun editFile() {
activity.openEditor(getCurrentPath()) val path = getFirstSelectedItemPath() ?: return
activity.openEditor(path)
}
private fun openPath() {
val path = getFirstSelectedItemPath() ?: return
activity.openPath(path, true)
}
private fun setAs() {
val path = getFirstSelectedItemPath() ?: return
activity.setAs(path)
} }
private fun toggleFileVisibility(hide: Boolean) { private fun toggleFileVisibility(hide: Boolean) {
Thread { Thread {
getSelectedMedia().forEach { getSelectedItems().forEach {
activity.toggleFileVisibility(it.path, hide) activity.toggleFileVisibility(it.path, hide)
} }
activity.runOnUiThread { activity.runOnUiThread {
@ -244,7 +250,7 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
private fun toggleFavorites(add: Boolean) { private fun toggleFavorites(add: Boolean) {
Thread { Thread {
val mediumDao = activity.galleryDB.MediumDao() val mediumDao = activity.galleryDB.MediumDao()
getSelectedMedia().forEach { getSelectedItems().forEach {
it.isFavorite = add it.isFavorite = add
mediumDao.updateFavorite(it.path, add) mediumDao.updateFavorite(it.path, add)
} }
@ -263,19 +269,55 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
} }
private fun shareMedia() { private fun shareMedia() {
if (selectedPositions.size == 1 && selectedPositions.first() != -1) { if (selectedKeys.size == 1 && selectedKeys.first() != -1) {
activity.shareMediumPath(getSelectedMedia().first().path) activity.shareMediumPath(getSelectedItems().first().path)
} else if (selectedPositions.size > 1) { } else if (selectedKeys.size > 1) {
activity.shareMediaPaths(getSelectedPaths()) activity.shareMediaPaths(getSelectedPaths())
} }
} }
private fun rotateSelection(degrees: Int) {
activity.toast(R.string.saving)
Thread {
val paths = getSelectedPaths().filter { it.isImageFast() }
var fileCnt = paths.size
rotatedImagePaths.clear()
paths.forEach {
rotatedImagePaths.add(it)
activity.saveRotatedImageToFile(it, it, degrees, true) {
fileCnt--
if (fileCnt == 0) {
activity.runOnUiThread {
listener?.refreshItems()
finishActMode()
}
}
}
}
}.start()
}
private fun moveFilesTo() {
activity.handleDeletePasswordProtection {
copyMoveTo(false)
}
}
private fun copyMoveTo(isCopyOperation: Boolean) { private fun copyMoveTo(isCopyOperation: Boolean) {
val paths = getSelectedPaths() val paths = getSelectedPaths()
val fileDirItems = paths.map { val recycleBinPath = activity.recycleBinPath
val fileDirItems = paths.asSequence().filter { isCopyOperation || !it.startsWith(recycleBinPath) }.map {
FileDirItem(it, it.getFilenameFromPath()) FileDirItem(it, it.getFilenameFromPath())
} as ArrayList }.toMutableList() as ArrayList
if (!isCopyOperation && paths.any { it.startsWith(recycleBinPath) }) {
activity.toast(R.string.moving_recycle_bin_items_disabled, Toast.LENGTH_LONG)
}
if (fileDirItems.isEmpty()) {
return
}
activity.tryCopyMoveFilesTo(fileDirItems, isCopyOperation) { activity.tryCopyMoveFilesTo(fileDirItems, isCopyOperation) {
config.tempFolderPath = "" config.tempFolderPath = ""
@ -283,60 +325,26 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
activity.applicationContext.rescanFolderMedia(fileDirItems.first().getParentPath()) activity.applicationContext.rescanFolderMedia(fileDirItems.first().getParentPath())
if (!isCopyOperation) { if (!isCopyOperation) {
listener?.refreshItems() listener?.refreshItems()
activity.updateFavoritePaths(fileDirItems, it)
} }
} }
} }
private fun fixDateTaken() { private fun fixDateTaken() {
activity.toast(R.string.fixing)
Thread { Thread {
try { activity.fixDateTaken(getSelectedPaths()) {
val operations = ArrayList<ContentProviderOperation>() listener?.refreshItems()
val mediumDao = activity.galleryDB.MediumDao() finishActMode()
val paths = getSelectedPaths()
for (path in paths) {
val dateTime = ExifInterface(path).getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL)
?: ExifInterface(path).getAttribute(ExifInterface.TAG_DATETIME) ?: continue
// some formats contain a "T" in the middle, some don't
// sample dates: 2015-07-26T14:55:23, 2018:09:05 15:09:05
val t = if (dateTime.substring(10, 11) == "T") "\'T\'" else " "
val separator = dateTime.substring(4, 5)
val format = "yyyy${separator}MM${separator}dd${t}kk:mm:ss"
val formatter = SimpleDateFormat(format, Locale.getDefault())
val timestamp = formatter.parse(dateTime).time
val uri = activity.getFileUri(path)
ContentProviderOperation.newUpdate(uri).apply {
val selection = "${MediaStore.Images.Media.DATA} = ?"
val selectionArgs = arrayOf(path)
withSelection(selection, selectionArgs)
withValue(MediaStore.Images.Media.DATE_TAKEN, timestamp)
operations.add(build())
}
if (operations.size % BATCH_SIZE == 0) {
activity.contentResolver.applyBatch(MediaStore.AUTHORITY, operations)
operations.clear()
}
mediumDao.updateFavoriteDateTaken(path, timestamp)
}
activity.contentResolver.applyBatch(MediaStore.AUTHORITY, operations)
activity.toast(R.string.dates_fixed_successfully)
activity.runOnUiThread {
listener?.refreshItems()
finishActMode()
}
} catch (e: Exception) {
activity.showErrorToast(e)
} }
}.start() }.start()
} }
private fun checkDeleteConfirmation() { private fun checkDeleteConfirmation() {
if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) { if (config.isDeletePasswordProtectionOn) {
activity.handleDeletePasswordProtection {
deleteFiles()
}
} else if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) {
deleteFiles() deleteFiles()
} else { } else {
askConfirmDelete() askConfirmDelete()
@ -344,8 +352,15 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
} }
private fun askConfirmDelete() { private fun askConfirmDelete() {
val items = resources.getQuantityString(R.plurals.delete_items, selectedPositions.size, selectedPositions.size) val itemsCnt = selectedKeys.size
val isRecycleBin = getSelectedPaths().first().startsWith(activity.filesDir.absolutePath) val firstPath = getSelectedPaths().first()
val items = if (itemsCnt == 1) {
"\"${firstPath.getFilenameFromPath()}\""
} else {
resources.getQuantityString(R.plurals.delete_items, itemsCnt, itemsCnt)
}
val isRecycleBin = firstPath.startsWith(activity.recycleBinPath)
val baseString = if (config.useRecycleBin && !isRecycleBin) R.string.move_to_recycle_bin_confirmation else R.string.deletion_confirmation val baseString = if (config.useRecycleBin && !isRecycleBin) R.string.move_to_recycle_bin_confirmation else R.string.deletion_confirmation
val question = String.format(resources.getString(baseString), items) val question = String.format(resources.getString(baseString), items)
DeleteWithRememberDialog(activity, question) { DeleteWithRememberDialog(activity, question) {
@ -354,48 +369,35 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
} }
} }
private fun getCurrentPath() = (media[selectedPositions.first()] as Medium).path
private fun deleteFiles() { private fun deleteFiles() {
if (selectedPositions.isEmpty()) { if (selectedKeys.isEmpty()) {
return return
} }
val fileDirItems = ArrayList<FileDirItem>(selectedPositions.size) val SAFPath = getSelectedPaths().firstOrNull { activity.isPathOnSD(it) } ?: getFirstSelectedItemPath() ?: return
val removeMedia = ArrayList<Medium>(selectedPositions.size)
if (media.size <= selectedPositions.first()) {
finishActMode()
return
}
val SAFPath = (media[selectedPositions.first()] as Medium).path
activity.handleSAFDialog(SAFPath) { activity.handleSAFDialog(SAFPath) {
selectedPositions.sortedDescending().forEach { val fileDirItems = ArrayList<FileDirItem>(selectedKeys.size)
val thumbnailItem = media.getOrNull(it) val removeMedia = ArrayList<Medium>(selectedKeys.size)
if (thumbnailItem is Medium) { val positions = getSelectedItemPositions()
fileDirItems.add(FileDirItem(thumbnailItem.path, thumbnailItem.name))
removeMedia.add(thumbnailItem) getSelectedItems().forEach {
} fileDirItems.add(FileDirItem(it.path, it.name))
removeMedia.add(it)
} }
media.removeAll(removeMedia) media.removeAll(removeMedia)
listener?.tryDeleteFiles(fileDirItems) listener?.tryDeleteFiles(fileDirItems)
removeSelectedItems() removeSelectedItems(positions)
} }
} }
private fun getSelectedMedia(): List<Medium> { private fun getSelectedItems() = media.filter { selectedKeys.contains((it as? Medium)?.path?.hashCode()) } as ArrayList<Medium>
val selectedMedia = ArrayList<Medium>(selectedPositions.size)
selectedPositions.forEach {
(media.getOrNull(it) as? Medium)?.apply {
selectedMedia.add(this)
}
}
return selectedMedia
}
private fun getSelectedPaths() = getSelectedMedia().map { it.path } as ArrayList<String> private fun getSelectedPaths() = getSelectedItems().map { it.path } as ArrayList<String>
private fun getFirstSelectedItemPath() = getItemWithKey(selectedKeys.first())?.path
private fun getItemWithKey(key: Int): Medium? = media.firstOrNull { (it as? Medium)?.path?.hashCode() == key } as? Medium
fun updateMedia(newMedia: ArrayList<ThumbnailItem>) { fun updateMedia(newMedia: ArrayList<ThumbnailItem>) {
val thumbnailItems = newMedia.clone() as ArrayList<ThumbnailItem> val thumbnailItems = newMedia.clone() as ArrayList<ThumbnailItem>
@ -436,11 +438,24 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
fun getItemBubbleText(position: Int, sorting: Int) = (media[position] as? Medium)?.getBubbleText(sorting) fun getItemBubbleText(position: Int, sorting: Int) = (media[position] as? Medium)?.getBubbleText(sorting)
private fun setupThumbnail(view: View, medium: Medium) { private fun setupThumbnail(view: View, medium: Medium) {
val isSelected = selectedKeys.contains(medium.path.hashCode())
view.apply { view.apply {
play_outline.beVisibleIf(medium.isVideo()) play_outline.beVisibleIf(medium.isVideo())
photo_name.beVisibleIf(displayFilenames || isListViewType) medium_name.beVisibleIf(displayFilenames || isListViewType)
photo_name.text = medium.name medium_name.text = medium.name
photo_name.tag = medium.path medium_name.tag = medium.path
val showVideoDuration = medium.isVideo() && config.showThumbnailVideoDuration
if (showVideoDuration) {
video_duration.text = medium.videoDuration.getFormattedDuration()
video_duration.setTextColor(textColor)
}
video_duration.beVisibleIf(showVideoDuration)
medium_check?.beVisibleIf(isSelected)
if (isSelected) {
medium_check?.background?.applyColorFilter(primaryColor)
}
var path = medium.path var path = medium.path
if (hasOTGConnected && path.startsWith(OTG_PATH)) { if (hasOTGConnected && path.startsWith(OTG_PATH)) {
@ -448,20 +463,20 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
} }
if (loadImageInstantly) { if (loadImageInstantly) {
activity.loadImage(medium.type, path, medium_thumbnail, scrollHorizontally, animateGifs, cropThumbnails) activity.loadImage(medium.type, path, medium_thumbnail, scrollHorizontally, animateGifs, cropThumbnails, rotatedImagePaths)
} else { } else {
medium_thumbnail.setImageDrawable(null) medium_thumbnail.setImageDrawable(null)
medium_thumbnail.isHorizontalScrolling = scrollHorizontally medium_thumbnail.isHorizontalScrolling = scrollHorizontally
delayHandler.postDelayed({ delayHandler.postDelayed({
val isVisible = visibleItemPaths.contains(medium.path) val isVisible = visibleItemPaths.contains(medium.path)
if (isVisible) { if (isVisible) {
activity.loadImage(medium.type, path, medium_thumbnail, scrollHorizontally, animateGifs, cropThumbnails) activity.loadImage(medium.type, path, medium_thumbnail, scrollHorizontally, animateGifs, cropThumbnails, rotatedImagePaths)
} }
}, IMAGE_LOAD_DELAY) }, IMAGE_LOAD_DELAY)
} }
if (isListViewType) { if (isListViewType) {
photo_name.setTextColor(textColor) medium_name.setTextColor(textColor)
play_outline.applyColorFilter(textColor) play_outline.applyColorFilter(textColor)
} }
} }

View file

@ -1,18 +1,18 @@
package com.simplemobiletools.gallery.adapters package com.simplemobiletools.gallery.pro.adapters
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentStatePagerAdapter
import android.support.v4.view.PagerAdapter
import android.view.ViewGroup import android.view.ViewGroup
import com.simplemobiletools.gallery.activities.ViewPagerActivity import androidx.fragment.app.Fragment
import com.simplemobiletools.gallery.fragments.PhotoFragment import androidx.fragment.app.FragmentManager
import com.simplemobiletools.gallery.fragments.VideoFragment import androidx.fragment.app.FragmentStatePagerAdapter
import com.simplemobiletools.gallery.fragments.ViewPagerFragment import androidx.viewpager.widget.PagerAdapter
import com.simplemobiletools.gallery.helpers.MEDIUM import com.simplemobiletools.gallery.pro.activities.ViewPagerActivity
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.fragments.PhotoFragment
import com.simplemobiletools.gallery.pro.fragments.VideoFragment
import com.simplemobiletools.gallery.pro.fragments.ViewPagerFragment
import com.simplemobiletools.gallery.pro.helpers.MEDIUM
import com.simplemobiletools.gallery.pro.models.Medium
class MyPagerAdapter(val activity: ViewPagerActivity, fm: FragmentManager, val media: MutableList<Medium>) : FragmentStatePagerAdapter(fm) { class MyPagerAdapter(val activity: ViewPagerActivity, fm: FragmentManager, val media: MutableList<Medium>) : FragmentStatePagerAdapter(fm) {
private val fragments = HashMap<Int, ViewPagerFragment>() private val fragments = HashMap<Int, ViewPagerFragment>()

View file

@ -1,16 +1,16 @@
package com.simplemobiletools.gallery.asynctasks package com.simplemobiletools.gallery.pro.asynctasks
import android.content.Context import android.content.Context
import android.os.AsyncTask import android.os.AsyncTask
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_TAKEN import com.simplemobiletools.commons.helpers.SORT_BY_DATE_TAKEN
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.extensions.getFavoritePaths import com.simplemobiletools.gallery.pro.extensions.getFavoritePaths
import com.simplemobiletools.gallery.helpers.FAVORITES import com.simplemobiletools.gallery.pro.helpers.FAVORITES
import com.simplemobiletools.gallery.helpers.MediaFetcher import com.simplemobiletools.gallery.pro.helpers.MediaFetcher
import com.simplemobiletools.gallery.helpers.RECYCLE_BIN import com.simplemobiletools.gallery.pro.helpers.RECYCLE_BIN
import com.simplemobiletools.gallery.helpers.SHOW_ALL import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import java.util.* import java.util.*
class GetMediaAsynctask(val context: Context, val mPath: String, val isPickImage: Boolean = false, val isPickVideo: Boolean = false, class GetMediaAsynctask(val context: Context, val mPath: String, val isPickImage: Boolean = false, val isPickVideo: Boolean = false,
@ -22,18 +22,19 @@ class GetMediaAsynctask(val context: Context, val mPath: String, val isPickImage
val pathToUse = if (showAll) SHOW_ALL else mPath val pathToUse = if (showAll) SHOW_ALL else mPath
val getProperDateTaken = context.config.getFileSorting(pathToUse) and SORT_BY_DATE_TAKEN != 0 val getProperDateTaken = context.config.getFileSorting(pathToUse) and SORT_BY_DATE_TAKEN != 0
val favoritePaths = context.getFavoritePaths() val favoritePaths = context.getFavoritePaths()
val getVideoDurations = context.config.showThumbnailVideoDuration
val media = if (showAll) { val media = if (showAll) {
val foldersToScan = mediaFetcher.getFoldersToScan().filter { it != RECYCLE_BIN && it != FAVORITES } val foldersToScan = mediaFetcher.getFoldersToScan().filter { it != RECYCLE_BIN && it != FAVORITES }
val media = ArrayList<Medium>() val media = ArrayList<Medium>()
foldersToScan.forEach { foldersToScan.forEach {
val newMedia = mediaFetcher.getFilesFrom(it, isPickImage, isPickVideo, getProperDateTaken, favoritePaths) val newMedia = mediaFetcher.getFilesFrom(it, isPickImage, isPickVideo, getProperDateTaken, favoritePaths, getVideoDurations, false)
media.addAll(newMedia) media.addAll(newMedia)
} }
mediaFetcher.sortMedia(media, context.config.getFileSorting(SHOW_ALL)) mediaFetcher.sortMedia(media, context.config.getFileSorting(SHOW_ALL))
media media
} else { } else {
mediaFetcher.getFilesFrom(mPath, isPickImage, isPickVideo, getProperDateTaken, favoritePaths) mediaFetcher.getFilesFrom(mPath, isPickImage, isPickVideo, getProperDateTaken, favoritePaths, getVideoDurations)
} }
return mediaFetcher.groupMedia(media, pathToUse) return mediaFetcher.groupMedia(media, pathToUse)
} }

View file

@ -0,0 +1,60 @@
package com.simplemobiletools.gallery.pro.databases
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.simplemobiletools.gallery.pro.interfaces.DirectoryDao
import com.simplemobiletools.gallery.pro.interfaces.MediumDao
import com.simplemobiletools.gallery.pro.interfaces.WidgetsDao
import com.simplemobiletools.gallery.pro.models.Directory
import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.pro.models.Widget
@Database(entities = [Directory::class, Medium::class, Widget::class], version = 6)
abstract class GalleryDatabase : RoomDatabase() {
abstract fun DirectoryDao(): DirectoryDao
abstract fun MediumDao(): MediumDao
abstract fun WidgetsDao(): WidgetsDao
companion object {
private var db: GalleryDatabase? = null
fun getInstance(context: Context): GalleryDatabase {
if (db == null) {
synchronized(GalleryDatabase::class) {
if (db == null) {
db = Room.databaseBuilder(context.applicationContext, GalleryDatabase::class.java, "gallery.db")
.fallbackToDestructiveMigration()
.addMigrations(MIGRATION_4_5)
.addMigrations(MIGRATION_5_6)
.build()
}
}
}
return db!!
}
fun destroyInstance() {
db = null
}
private val MIGRATION_4_5 = object : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE media ADD COLUMN video_duration INTEGER default 0 NOT NULL")
}
}
private val MIGRATION_5_6 = object : Migration(5, 6) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `widgets` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `widget_id` INTEGER NOT NULL, `folder_path` TEXT NOT NULL)")
database.execSQL("CREATE UNIQUE INDEX `index_widgets_widget_id` ON `widgets` (`widget_id`)")
}
}
}
}

View file

@ -1,14 +1,14 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.content.DialogInterface import android.content.DialogInterface
import android.support.v7.app.AlertDialog
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.dialog_change_grouping.view.* import kotlinx.android.synthetic.main.dialog_change_grouping.view.*
class ChangeGroupingDialog(val activity: BaseSimpleActivity, val path: String = "", val callback: () -> Unit) : class ChangeGroupingDialog(val activity: BaseSimpleActivity, val path: String = "", val callback: () -> Unit) :

View file

@ -1,15 +1,15 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.content.DialogInterface import android.content.DialogInterface
import android.support.v7.app.AlertDialog
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.helpers.SHOW_ALL import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL
import kotlinx.android.synthetic.main.dialog_change_sorting.view.* import kotlinx.android.synthetic.main.dialog_change_sorting.view.*
class ChangeSortingDialog(val activity: BaseSimpleActivity, val isDirectorySorting: Boolean, showFolderCheckbox: Boolean, class ChangeSortingDialog(val activity: BaseSimpleActivity, val isDirectorySorting: Boolean, showFolderCheckbox: Boolean,
@ -47,6 +47,7 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, val isDirectorySorti
currSorting and SORT_BY_SIZE != 0 -> sortingRadio.sorting_dialog_radio_size currSorting and SORT_BY_SIZE != 0 -> sortingRadio.sorting_dialog_radio_size
currSorting and SORT_BY_DATE_MODIFIED != 0 -> sortingRadio.sorting_dialog_radio_last_modified currSorting and SORT_BY_DATE_MODIFIED != 0 -> sortingRadio.sorting_dialog_radio_last_modified
currSorting and SORT_BY_DATE_TAKEN != 0 -> sortingRadio.sorting_dialog_radio_date_taken currSorting and SORT_BY_DATE_TAKEN != 0 -> sortingRadio.sorting_dialog_radio_date_taken
currSorting and SORT_BY_RANDOM != 0 -> sortingRadio.sorting_dialog_radio_random
else -> sortingRadio.sorting_dialog_radio_name else -> sortingRadio.sorting_dialog_radio_name
} }
sortBtn.isChecked = true sortBtn.isChecked = true
@ -69,6 +70,7 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, val isDirectorySorti
R.id.sorting_dialog_radio_path -> SORT_BY_PATH R.id.sorting_dialog_radio_path -> SORT_BY_PATH
R.id.sorting_dialog_radio_size -> SORT_BY_SIZE R.id.sorting_dialog_radio_size -> SORT_BY_SIZE
R.id.sorting_dialog_radio_last_modified -> SORT_BY_DATE_MODIFIED R.id.sorting_dialog_radio_last_modified -> SORT_BY_DATE_MODIFIED
R.id.sorting_dialog_radio_random -> SORT_BY_RANDOM
else -> SORT_BY_DATE_TAKEN else -> SORT_BY_DATE_TAKEN
} }

View file

@ -0,0 +1,73 @@
package com.simplemobiletools.gallery.pro.dialogs
import android.view.View
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL
import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_GRID
import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_LIST
import kotlinx.android.synthetic.main.dialog_change_view_type.view.*
class ChangeViewTypeDialog(val activity: BaseSimpleActivity, val fromFoldersView: Boolean, val path: String = "", val callback: () -> Unit) {
private var view: View
private var config = activity.config
private var pathToUse = if (path.isEmpty()) SHOW_ALL else path
init {
view = activity.layoutInflater.inflate(R.layout.dialog_change_view_type, null).apply {
val viewToCheck = if (fromFoldersView) {
if (config.viewTypeFolders == VIEW_TYPE_GRID) {
change_view_type_dialog_radio_grid.id
} else {
change_view_type_dialog_radio_list.id
}
} else {
val currViewType = config.getFolderViewType(pathToUse)
if (currViewType == VIEW_TYPE_GRID) {
change_view_type_dialog_radio_grid.id
} else {
change_view_type_dialog_radio_list.id
}
}
change_view_type_dialog_radio.check(viewToCheck)
change_view_type_dialog_group_direct_subfolders.apply {
beVisibleIf(fromFoldersView)
isChecked = config.groupDirectSubfolders
}
change_view_type_dialog_use_for_this_folder.apply {
beVisibleIf(!fromFoldersView)
isChecked = config.hasCustomViewType(pathToUse)
}
}
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this)
}
}
private fun dialogConfirmed() {
val viewType = if (view.change_view_type_dialog_radio.checkedRadioButtonId == view.change_view_type_dialog_radio_grid.id) VIEW_TYPE_GRID else VIEW_TYPE_LIST
if (fromFoldersView) {
config.viewTypeFolders = viewType
config.groupDirectSubfolders = view.change_view_type_dialog_group_direct_subfolders.isChecked
} else {
if (view.change_view_type_dialog_use_for_this_folder.isChecked) {
config.saveFolderViewType(pathToUse, viewType)
} else {
config.removeFolderViewType(pathToUse)
config.viewTypeFiles = viewType
}
}
callback()
}
}

View file

@ -0,0 +1,39 @@
package com.simplemobiletools.gallery.pro.dialogs
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.showKeyboard
import com.simplemobiletools.commons.extensions.value
import com.simplemobiletools.gallery.pro.R
import kotlinx.android.synthetic.main.dialog_custom_aspect_ratio.view.*
class CustomAspectRatioDialog(val activity: BaseSimpleActivity, val defaultCustomAspectRatio: Pair<Int, Int>?, val callback: (aspectRatio: Pair<Int, Int>) -> Unit) {
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_custom_aspect_ratio, null).apply {
aspect_ratio_width.setText(defaultCustomAspectRatio?.first?.toString() ?: "")
aspect_ratio_height.setText(defaultCustomAspectRatio?.second?.toString() ?: "")
}
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this) {
showKeyboard(view.aspect_ratio_width)
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val width = getViewValue(view.aspect_ratio_width)
val height = getViewValue(view.aspect_ratio_height)
callback(Pair(width, height))
dismiss()
}
}
}
}
private fun getViewValue(view: EditText): Int {
val textValue = view.value
return if (textValue.isEmpty()) 0 else textValue.toInt()
}
}

View file

@ -1,9 +1,9 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.app.Activity import android.app.Activity
import android.support.v7.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import kotlinx.android.synthetic.main.dialog_delete_with_remember.view.* import kotlinx.android.synthetic.main.dialog_delete_with_remember.view.*
class DeleteWithRememberDialog(val activity: Activity, val message: String, val callback: (remember: Boolean) -> Unit) { class DeleteWithRememberDialog(val activity: Activity, val message: String, val callback: (remember: Boolean) -> Unit) {

View file

@ -1,15 +1,15 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.support.v7.app.AlertDialog
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RadioButton import android.widget.RadioButton
import android.widget.RadioGroup import android.widget.RadioGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.getBasePath import com.simplemobiletools.commons.extensions.getBasePath
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import kotlinx.android.synthetic.main.dialog_exclude_folder.view.* import kotlinx.android.synthetic.main.dialog_exclude_folder.view.*
class ExcludeFolderDialog(val activity: BaseSimpleActivity, val selectedPaths: List<String>, val callback: () -> Unit) { class ExcludeFolderDialog(val activity: BaseSimpleActivity, val selectedPaths: List<String>, val callback: () -> Unit) {
@ -34,11 +34,11 @@ class ExcludeFolderDialog(val activity: BaseSimpleActivity, val selectedPaths: L
} }
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, { dialog, which -> dialogConfirmed() }) .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.create().apply { .create().apply {
activity.setupDialogStuff(view, this) activity.setupDialogStuff(view, this)
} }
} }
private fun dialogConfirmed() { private fun dialogConfirmed() {

View file

@ -1,11 +1,11 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.support.v7.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.dialog_filter_media.view.* import kotlinx.android.synthetic.main.dialog_filter_media.view.*
class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) { class FilterMediaDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) {

View file

@ -1,11 +1,11 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.support.v7.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.dialog_manage_bottom_actions.view.* import kotlinx.android.synthetic.main.dialog_manage_bottom_actions.view.*
class ManageBottomActionsDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) { class ManageBottomActionsDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) {
@ -26,6 +26,7 @@ class ManageBottomActionsDialog(val activity: BaseSimpleActivity, val callback:
manage_bottom_actions_toggle_visibility.isChecked = actions and BOTTOM_ACTION_TOGGLE_VISIBILITY != 0 manage_bottom_actions_toggle_visibility.isChecked = actions and BOTTOM_ACTION_TOGGLE_VISIBILITY != 0
manage_bottom_actions_rename.isChecked = actions and BOTTOM_ACTION_RENAME != 0 manage_bottom_actions_rename.isChecked = actions and BOTTOM_ACTION_RENAME != 0
manage_bottom_actions_set_as.isChecked = actions and BOTTOM_ACTION_SET_AS != 0 manage_bottom_actions_set_as.isChecked = actions and BOTTOM_ACTION_SET_AS != 0
manage_bottom_actions_copy.isChecked = actions and BOTTOM_ACTION_COPY != 0
} }
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
@ -63,6 +64,8 @@ class ManageBottomActionsDialog(val activity: BaseSimpleActivity, val callback:
result += BOTTOM_ACTION_RENAME result += BOTTOM_ACTION_RENAME
if (manage_bottom_actions_set_as.isChecked) if (manage_bottom_actions_set_as.isChecked)
result += BOTTOM_ACTION_SET_AS result += BOTTOM_ACTION_SET_AS
if (manage_bottom_actions_copy.isChecked)
result += BOTTOM_ACTION_COPY
} }
activity.config.visibleBottomActions = result activity.config.visibleBottomActions = result

View file

@ -1,11 +1,11 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.support.v7.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.dialog_manage_extended_details.view.* import kotlinx.android.synthetic.main.dialog_manage_extended_details.view.*
class ManageExtendedDetailsDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) { class ManageExtendedDetailsDialog(val activity: BaseSimpleActivity, val callback: (result: Int) -> Unit) {
@ -31,8 +31,8 @@ class ManageExtendedDetailsDialog(val activity: BaseSimpleActivity, val callback
.setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() } .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.create().apply { .create().apply {
activity.setupDialogStuff(view, this) activity.setupDialogStuff(view, this)
} }
} }
private fun dialogConfirmed() { private fun dialogConfirmed() {

View file

@ -0,0 +1,74 @@
package com.simplemobiletools.gallery.pro.dialogs
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.pro.R
import kotlinx.android.synthetic.main.dialog_other_aspect_ratio.view.*
class OtherAspectRatioDialog(val activity: BaseSimpleActivity, val lastOtherAspectRatio: Pair<Int, Int>?, val callback: (aspectRatio: Pair<Int, Int>) -> Unit) {
private val dialog: AlertDialog
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_other_aspect_ratio, null).apply {
other_aspect_ratio_2_1.setOnClickListener { ratioPicked(Pair(2, 1)) }
other_aspect_ratio_3_2.setOnClickListener { ratioPicked(Pair(3, 2)) }
other_aspect_ratio_4_3.setOnClickListener { ratioPicked(Pair(4, 3)) }
other_aspect_ratio_5_3.setOnClickListener { ratioPicked(Pair(5, 3)) }
other_aspect_ratio_16_9.setOnClickListener { ratioPicked(Pair(16, 9)) }
other_aspect_ratio_19_9.setOnClickListener { ratioPicked(Pair(19, 9)) }
other_aspect_ratio_custom.setOnClickListener { customRatioPicked() }
other_aspect_ratio_1_2.setOnClickListener { ratioPicked(Pair(1, 2)) }
other_aspect_ratio_2_3.setOnClickListener { ratioPicked(Pair(2, 3)) }
other_aspect_ratio_3_4.setOnClickListener { ratioPicked(Pair(3, 4)) }
other_aspect_ratio_3_5.setOnClickListener { ratioPicked(Pair(3, 5)) }
other_aspect_ratio_9_16.setOnClickListener { ratioPicked(Pair(9, 16)) }
other_aspect_ratio_9_19.setOnClickListener { ratioPicked(Pair(9, 19)) }
val radio1SelectedItemId = when (lastOtherAspectRatio) {
Pair(2, 1) -> other_aspect_ratio_2_1.id
Pair(3, 2) -> other_aspect_ratio_3_2.id
Pair(4, 3) -> other_aspect_ratio_4_3.id
Pair(5, 3) -> other_aspect_ratio_5_3.id
Pair(16, 9) -> other_aspect_ratio_16_9.id
Pair(19, 9) -> other_aspect_ratio_19_9.id
else -> 0
}
other_aspect_ratio_dialog_radio_1.check(radio1SelectedItemId)
val radio2SelectedItemId = when (lastOtherAspectRatio) {
Pair(1, 2) -> other_aspect_ratio_1_2.id
Pair(2, 3) -> other_aspect_ratio_2_3.id
Pair(3, 4) -> other_aspect_ratio_3_4.id
Pair(3, 5) -> other_aspect_ratio_3_5.id
Pair(9, 16) -> other_aspect_ratio_9_16.id
Pair(9, 19) -> other_aspect_ratio_9_19.id
else -> 0
}
other_aspect_ratio_dialog_radio_2.check(radio2SelectedItemId)
if (radio1SelectedItemId == 0 && radio2SelectedItemId == 0) {
other_aspect_ratio_dialog_radio_1.check(other_aspect_ratio_custom.id)
}
}
dialog = AlertDialog.Builder(activity)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this)
}
}
private fun customRatioPicked() {
CustomAspectRatioDialog(activity, lastOtherAspectRatio) {
callback(it)
dialog.dismiss()
}
}
private fun ratioPicked(pair: Pair<Int, Int>) {
callback(pair)
dialog.dismiss()
}
}

View file

@ -0,0 +1,151 @@
package com.simplemobiletools.gallery.pro.dialogs
import android.view.KeyEvent
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.DirectoryAdapter
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_GRID
import com.simplemobiletools.gallery.pro.models.Directory
import kotlinx.android.synthetic.main.dialog_directory_picker.view.*
class PickDirectoryDialog(val activity: BaseSimpleActivity, val sourcePath: String, showOtherFolderButton: Boolean, val callback: (path: String) -> Unit) {
private var dialog: AlertDialog
private var shownDirectories = ArrayList<Directory>()
private var allDirectories = ArrayList<Directory>()
private var openedSubfolders = arrayListOf("")
private var view = activity.layoutInflater.inflate(R.layout.dialog_directory_picker, null)
private var isGridViewType = activity.config.viewTypeFolders == VIEW_TYPE_GRID
private var showHidden = activity.config.shouldShowHidden
private var currentPathPrefix = ""
init {
(view.directories_grid.layoutManager as MyGridLayoutManager).apply {
orientation = if (activity.config.scrollHorizontally && isGridViewType) RecyclerView.HORIZONTAL else RecyclerView.VERTICAL
spanCount = if (isGridViewType) activity.config.dirColumnCnt else 1
}
val builder = AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.setOnKeyListener { dialogInterface, i, keyEvent ->
if (keyEvent.action == KeyEvent.ACTION_UP && i == KeyEvent.KEYCODE_BACK) {
backPressed()
}
true
}
if (showOtherFolderButton) {
builder.setNeutralButton(R.string.other_folder) { dialogInterface, i -> showOtherFolder() }
}
dialog = builder.create().apply {
activity.setupDialogStuff(view, this, R.string.select_destination) {
view.directories_show_hidden.beVisibleIf(!context.config.shouldShowHidden)
view.directories_show_hidden.setOnClickListener {
activity.handleHiddenFolderPasswordProtection {
view.directories_show_hidden.beGone()
showHidden = true
fetchDirectories(true)
}
}
}
}
fetchDirectories(false)
}
private fun fetchDirectories(forceShowHidden: Boolean) {
activity.getCachedDirectories(forceShowHidden = forceShowHidden) {
if (it.isNotEmpty()) {
it.forEach {
it.subfoldersMediaCount = it.mediaCnt
}
activity.runOnUiThread {
gotDirectories(activity.addTempFolderIfNeeded(it))
}
}
}
}
private fun showOtherFolder() {
FilePickerDialog(activity, sourcePath, false, showHidden, true, true) {
callback(it)
}
}
private fun gotDirectories(newDirs: ArrayList<Directory>) {
if (allDirectories.isEmpty()) {
allDirectories = newDirs.clone() as ArrayList<Directory>
}
val distinctDirs = newDirs.distinctBy { it.path.getDistinctPath() }.toMutableList() as ArrayList<Directory>
val sortedDirs = activity.getSortedDirectories(distinctDirs)
val dirs = activity.getDirsToShow(sortedDirs, allDirectories, currentPathPrefix).clone() as ArrayList<Directory>
if (dirs.hashCode() == shownDirectories.hashCode()) {
return
}
shownDirectories = dirs
val adapter = DirectoryAdapter(activity, dirs.clone() as ArrayList<Directory>, null, view.directories_grid, true) {
val clickedDir = it as Directory
val path = clickedDir.path
if (clickedDir.subfoldersCount == 1 || !activity.config.groupDirectSubfolders) {
if (path.trimEnd('/') == sourcePath) {
activity.toast(R.string.source_and_destination_same)
return@DirectoryAdapter
} else {
callback(path)
dialog.dismiss()
}
} else {
currentPathPrefix = path
openedSubfolders.add(path)
gotDirectories(allDirectories)
}
}
val scrollHorizontally = activity.config.scrollHorizontally && isGridViewType
val sorting = activity.config.directorySorting
view.apply {
directories_grid.adapter = adapter
directories_vertical_fastscroller.isHorizontal = false
directories_vertical_fastscroller.beGoneIf(scrollHorizontally)
directories_horizontal_fastscroller.isHorizontal = true
directories_horizontal_fastscroller.beVisibleIf(scrollHorizontally)
if (scrollHorizontally) {
directories_horizontal_fastscroller.allowBubbleDisplay = activity.config.showInfoBubble
directories_horizontal_fastscroller.setViews(directories_grid) {
directories_horizontal_fastscroller.updateBubbleText(dirs[it].getBubbleText(sorting))
}
} else {
directories_vertical_fastscroller.allowBubbleDisplay = activity.config.showInfoBubble
directories_vertical_fastscroller.setViews(directories_grid) {
directories_vertical_fastscroller.updateBubbleText(dirs[it].getBubbleText(sorting))
}
}
}
}
private fun backPressed() {
if (activity.config.groupDirectSubfolders) {
if (currentPathPrefix.isEmpty()) {
dialog.dismiss()
} else {
openedSubfolders.removeAt(openedSubfolders.size - 1)
currentPathPrefix = openedSubfolders.last()
gotDirectories(allDirectories)
}
} else {
dialog.dismiss()
}
}
}

View file

@ -1,32 +1,33 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.support.v7.app.AlertDialog import androidx.appcompat.app.AlertDialog
import android.support.v7.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.beGoneIf import com.simplemobiletools.commons.extensions.beGoneIf
import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.views.MyGridLayoutManager import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.adapters.MediaAdapter import com.simplemobiletools.gallery.pro.adapters.MediaAdapter
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask import com.simplemobiletools.gallery.pro.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.extensions.getCachedMedia import com.simplemobiletools.gallery.pro.extensions.getCachedMedia
import com.simplemobiletools.gallery.helpers.SHOW_ALL import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL
import com.simplemobiletools.gallery.helpers.VIEW_TYPE_GRID import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_GRID
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import kotlinx.android.synthetic.main.dialog_medium_picker.view.* import kotlinx.android.synthetic.main.dialog_medium_picker.view.*
class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val callback: (path: String) -> Unit) { class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val callback: (path: String) -> Unit) {
var dialog: AlertDialog var dialog: AlertDialog
var shownMedia = ArrayList<ThumbnailItem>() var shownMedia = ArrayList<ThumbnailItem>()
val view = activity.layoutInflater.inflate(R.layout.dialog_medium_picker, null) val view = activity.layoutInflater.inflate(R.layout.dialog_medium_picker, null)
var isGridViewType = activity.config.viewTypeFiles == VIEW_TYPE_GRID val viewType = activity.config.getFolderViewType(if (activity.config.showAll) SHOW_ALL else path)
var isGridViewType = viewType == VIEW_TYPE_GRID
init { init {
(view.media_grid.layoutManager as MyGridLayoutManager).apply { (view.media_grid.layoutManager as MyGridLayoutManager).apply {
orientation = if (activity.config.scrollHorizontally && isGridViewType) GridLayoutManager.HORIZONTAL else GridLayoutManager.VERTICAL orientation = if (activity.config.scrollHorizontally && isGridViewType) RecyclerView.HORIZONTAL else RecyclerView.VERTICAL
spanCount = if (isGridViewType) activity.config.mediaColumnCnt else 1 spanCount = if (isGridViewType) activity.config.mediaColumnCnt else 1
} }
@ -53,7 +54,7 @@ class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val c
} }
private fun showOtherFolder() { private fun showOtherFolder() {
PickDirectoryDialog(activity, path) { PickDirectoryDialog(activity, path, true) {
callback(it) callback(it)
dialog.dismiss() dialog.dismiss()
} }
@ -64,7 +65,7 @@ class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val c
return return
shownMedia = media shownMedia = media
val adapter = MediaAdapter(activity, shownMedia.clone() as ArrayList<ThumbnailItem>, null, true, false, view.media_grid, null) { val adapter = MediaAdapter(activity, shownMedia.clone() as ArrayList<ThumbnailItem>, null, true, false, path, view.media_grid, null) {
if (it is Medium) { if (it is Medium) {
callback(it.path) callback(it.path)
dialog.dismiss() dialog.dismiss()

View file

@ -0,0 +1,76 @@
package com.simplemobiletools.gallery.pro.dialogs
import android.graphics.Point
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.gallery.pro.R
import kotlinx.android.synthetic.main.dialog_resize_image.view.*
class ResizeDialog(val activity: BaseSimpleActivity, val size: Point, val callback: (newSize: Point) -> Unit) {
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_resize_image, null)
val widthView = view.image_width
val heightView = view.image_height
widthView.setText(size.x.toString())
heightView.setText(size.y.toString())
val ratio = size.x / size.y.toFloat()
widthView.onTextChangeListener {
if (widthView.hasFocus()) {
var width = getViewValue(widthView)
if (width > size.x) {
widthView.setText(size.x.toString())
width = size.x
}
if (view.keep_aspect_ratio.isChecked) {
heightView.setText((width / ratio).toInt().toString())
}
}
}
heightView.onTextChangeListener {
if (heightView.hasFocus()) {
var height = getViewValue(heightView)
if (height > size.y) {
heightView.setText(size.y.toString())
height = size.y
}
if (view.keep_aspect_ratio.isChecked) {
widthView.setText((height * ratio).toInt().toString())
}
}
}
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.resize_and_save) {
showKeyboard(view.image_width)
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val width = getViewValue(widthView)
val height = getViewValue(heightView)
if (width <= 0 || height <= 0) {
activity.toast(R.string.invalid_values)
return@setOnClickListener
}
val newSize = Point(getViewValue(widthView), getViewValue(heightView))
callback(newSize)
dismiss()
}
}
}
}
private fun getViewValue(view: EditText): Int {
val textValue = view.value
return if (textValue.isEmpty()) 0 else textValue.toInt()
}
}

View file

@ -1,11 +1,11 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.support.v7.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import kotlinx.android.synthetic.main.dialog_save_as.view.* import kotlinx.android.synthetic.main.dialog_save_as.view.*
class SaveAsDialog(val activity: BaseSimpleActivity, val path: String, val appendFilename: Boolean, val callback: (savePath: String) -> Unit) { class SaveAsDialog(val activity: BaseSimpleActivity, val path: String, val appendFilename: Boolean, val callback: (savePath: String) -> Unit) {
@ -32,7 +32,7 @@ class SaveAsDialog(val activity: BaseSimpleActivity, val path: String, val appen
save_as_name.setText(name) save_as_name.setText(name)
save_as_path.setOnClickListener { save_as_path.setOnClickListener {
FilePickerDialog(activity, realPath, false, false, true) { FilePickerDialog(activity, realPath, false, false, true, true) {
save_as_path.text = activity.humanizePath(it) save_as_path.text = activity.humanizePath(it)
realPath = it realPath = it
} }

View file

@ -1,14 +1,13 @@
package com.simplemobiletools.gallery.dialogs package com.simplemobiletools.gallery.pro.dialogs
import android.support.v7.app.AlertDialog
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.hideKeyboard import com.simplemobiletools.commons.extensions.hideKeyboard
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.pro.helpers.SLIDESHOW_DEFAULT_INTERVAL
import com.simplemobiletools.gallery.helpers.SLIDESHOW_DEFAULT_INTERVAL
import kotlinx.android.synthetic.main.dialog_slideshow.view.* import kotlinx.android.synthetic.main.dialog_slideshow.view.*
class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit) { class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit) {
@ -29,11 +28,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit
activity.hideKeyboard(v) activity.hideKeyboard(v)
} }
include_photos_holder.setOnClickListener {
interval_value.clearFocus()
include_photos.toggle()
}
include_videos_holder.setOnClickListener { include_videos_holder.setOnClickListener {
interval_value.clearFocus() interval_value.clearFocus()
include_videos.toggle() include_videos.toggle()
@ -73,11 +67,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit
activity.setupDialogStuff(view, this) { activity.setupDialogStuff(view, this) {
hideKeyboard() hideKeyboard()
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (!view.include_photos.isChecked && !view.include_videos.isChecked && !view.include_gifs.isChecked) {
activity.toast(R.string.no_media_for_slideshow)
return@setOnClickListener
}
storeValues() storeValues()
callback() callback()
dismiss() dismiss()
@ -90,7 +79,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit
val config = activity.config val config = activity.config
view.apply { view.apply {
interval_value.setText(config.slideshowInterval.toString()) interval_value.setText(config.slideshowInterval.toString())
include_photos.isChecked = config.slideshowIncludePhotos
include_videos.isChecked = config.slideshowIncludeVideos include_videos.isChecked = config.slideshowIncludeVideos
include_gifs.isChecked = config.slideshowIncludeGIFs include_gifs.isChecked = config.slideshowIncludeGIFs
random_order.isChecked = config.slideshowRandomOrder random_order.isChecked = config.slideshowRandomOrder
@ -107,7 +95,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit
activity.config.apply { activity.config.apply {
slideshowInterval = interval.toInt() slideshowInterval = interval.toInt()
slideshowIncludePhotos = view.include_photos.isChecked
slideshowIncludeVideos = view.include_videos.isChecked slideshowIncludeVideos = view.include_videos.isChecked
slideshowIncludeGIFs = view.include_gifs.isChecked slideshowIncludeGIFs = view.include_gifs.isChecked
slideshowRandomOrder = view.random_order.isChecked slideshowRandomOrder = view.random_order.isChecked

View file

@ -0,0 +1,490 @@
package com.simplemobiletools.gallery.pro.extensions
import android.annotation.TargetApi
import android.app.Activity
import android.content.ContentProviderOperation
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.ExifInterface
import android.os.Build
import android.provider.MediaStore
import android.util.DisplayMetrics
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.FAQItem
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.activities.SimpleActivity
import com.simplemobiletools.gallery.pro.dialogs.PickDirectoryDialog
import com.simplemobiletools.gallery.pro.helpers.NOMEDIA
import com.simplemobiletools.gallery.pro.helpers.RECYCLE_BIN
import com.simplemobiletools.gallery.pro.interfaces.MediumDao
import com.squareup.picasso.Picasso
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.text.SimpleDateFormat
import java.util.*
fun Activity.sharePath(path: String) {
sharePathIntent(path, BuildConfig.APPLICATION_ID)
}
fun Activity.sharePaths(paths: ArrayList<String>) {
sharePathsIntent(paths, BuildConfig.APPLICATION_ID)
}
fun Activity.shareMediumPath(path: String) {
sharePath(path)
}
fun Activity.shareMediaPaths(paths: ArrayList<String>) {
sharePaths(paths)
}
fun Activity.setAs(path: String) {
setAsIntent(path, BuildConfig.APPLICATION_ID)
}
fun Activity.openPath(path: String, forceChooser: Boolean) {
openPathIntent(path, forceChooser, BuildConfig.APPLICATION_ID)
}
fun Activity.openEditor(path: String, forceChooser: Boolean = false) {
openEditorIntent(path, forceChooser, BuildConfig.APPLICATION_ID)
}
fun Activity.launchCamera() {
val intent = Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
} else {
toast(R.string.no_app_found)
}
}
fun SimpleActivity.launchAbout() {
val licenses = LICENSE_GLIDE or LICENSE_CROPPER or LICENSE_RTL or LICENSE_SUBSAMPLING or LICENSE_PATTERN or LICENSE_REPRINT or LICENSE_GIF_DRAWABLE or
LICENSE_PICASSO or LICENSE_EXOPLAYER or LICENSE_PANORAMA_VIEW or LICENSE_SANSELAN or LICENSE_FILTERS or LICENSE_GESTURE_VIEWS
val faqItems = arrayListOf(
FAQItem(R.string.faq_5_title_commons, R.string.faq_5_text_commons),
FAQItem(R.string.faq_1_title, R.string.faq_1_text),
FAQItem(R.string.faq_2_title, R.string.faq_2_text),
FAQItem(R.string.faq_3_title, R.string.faq_3_text),
FAQItem(R.string.faq_4_title, R.string.faq_4_text),
FAQItem(R.string.faq_5_title, R.string.faq_5_text),
FAQItem(R.string.faq_6_title, R.string.faq_6_text),
FAQItem(R.string.faq_7_title, R.string.faq_7_text),
FAQItem(R.string.faq_8_title, R.string.faq_8_text),
FAQItem(R.string.faq_10_title, R.string.faq_10_text),
FAQItem(R.string.faq_11_title, R.string.faq_11_text),
FAQItem(R.string.faq_12_title, R.string.faq_12_text),
FAQItem(R.string.faq_13_title, R.string.faq_13_text),
FAQItem(R.string.faq_14_title, R.string.faq_14_text),
FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons),
FAQItem(R.string.faq_6_title_commons, R.string.faq_6_text_commons))
startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
}
fun AppCompatActivity.showSystemUI(toggleActionBarVisibility: Boolean) {
if (toggleActionBarVisibility) {
supportActionBar?.show()
}
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
fun AppCompatActivity.hideSystemUI(toggleActionBarVisibility: Boolean) {
if (toggleActionBarVisibility) {
supportActionBar?.hide()
}
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LOW_PROFILE or
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_IMMERSIVE
}
fun BaseSimpleActivity.addNoMedia(path: String, callback: () -> Unit) {
val file = File(path, NOMEDIA)
if (file.exists()) {
callback()
return
}
if (needsStupidWritePermissions(path)) {
handleSAFDialog(file.absolutePath) {
val fileDocument = getDocumentFile(path)
if (fileDocument?.exists() == true && fileDocument.isDirectory) {
fileDocument.createFile("", NOMEDIA)
applicationContext.scanFileRecursively(file) {
callback()
}
} else {
toast(R.string.unknown_error_occurred)
callback()
}
}
} else {
try {
file.createNewFile()
applicationContext.scanFileRecursively(file) {
callback()
}
} catch (e: Exception) {
showErrorToast(e)
callback()
}
}
}
fun BaseSimpleActivity.removeNoMedia(path: String, callback: (() -> Unit)? = null) {
val file = File(path, NOMEDIA)
if (!file.exists()) {
callback?.invoke()
return
}
tryDeleteFileDirItem(file.toFileDirItem(applicationContext), false, false) {
scanPathRecursively(file.parent)
callback?.invoke()
}
}
fun BaseSimpleActivity.toggleFileVisibility(oldPath: String, hide: Boolean, callback: ((newPath: String) -> Unit)? = null) {
val path = oldPath.getParentPath()
var filename = oldPath.getFilenameFromPath()
if ((hide && filename.startsWith('.')) || (!hide && !filename.startsWith('.'))) {
callback?.invoke(oldPath)
return
}
filename = if (hide) {
".${filename.trimStart('.')}"
} else {
filename.substring(1, filename.length)
}
val newPath = "$path/$filename"
renameFile(oldPath, newPath) {
callback?.invoke(newPath)
Thread {
updateDBMediaPath(oldPath, newPath)
}.start()
}
}
fun BaseSimpleActivity.tryCopyMoveFilesTo(fileDirItems: ArrayList<FileDirItem>, isCopyOperation: Boolean, callback: (destinationPath: String) -> Unit) {
if (fileDirItems.isEmpty()) {
toast(R.string.unknown_error_occurred)
return
}
val source = fileDirItems[0].getParentPath()
PickDirectoryDialog(this, source, true) {
copyMoveFilesTo(fileDirItems, source.trimEnd('/'), it, isCopyOperation, true, config.shouldShowHidden, callback)
}
}
fun BaseSimpleActivity.tryDeleteFileDirItem(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, deleteFromDatabase: Boolean,
callback: ((wasSuccess: Boolean) -> Unit)? = null) {
deleteFile(fileDirItem, allowDeleteFolder) {
if (deleteFromDatabase) {
Thread {
deleteDBPath(galleryDB.MediumDao(), fileDirItem.path)
runOnUiThread {
callback?.invoke(it)
}
}.start()
} else {
callback?.invoke(it)
}
}
}
fun BaseSimpleActivity.movePathsInRecycleBin(paths: ArrayList<String>, mediumDao: MediumDao = galleryDB.MediumDao(), callback: ((wasSuccess: Boolean) -> Unit)?) {
Thread {
var pathsCnt = paths.size
paths.forEach {
val file = File(it)
val internalFile = File(recycleBinPath, it)
try {
if (file.copyRecursively(internalFile, true)) {
mediumDao.updateDeleted("$RECYCLE_BIN$it", System.currentTimeMillis(), it)
pathsCnt--
}
} catch (e: Exception) {
showErrorToast(e)
return@forEach
}
}
callback?.invoke(pathsCnt == 0)
}.start()
}
fun BaseSimpleActivity.restoreRecycleBinPath(path: String, callback: () -> Unit) {
restoreRecycleBinPaths(arrayListOf(path), galleryDB.MediumDao(), callback)
}
fun BaseSimpleActivity.restoreRecycleBinPaths(paths: ArrayList<String>, mediumDao: MediumDao = galleryDB.MediumDao(), callback: () -> Unit) {
Thread {
val newPaths = ArrayList<String>()
paths.forEach {
val source = it
val destination = it.removePrefix(recycleBinPath)
var inputStream: InputStream? = null
var out: OutputStream? = null
try {
out = getFileOutputStreamSync(destination, source.getMimeType())
inputStream = getFileInputStreamSync(source)!!
inputStream.copyTo(out!!)
if (File(source).length() == File(destination).length()) {
mediumDao.updateDeleted(destination.removePrefix(recycleBinPath), 0, "$RECYCLE_BIN$destination")
}
newPaths.add(destination)
} catch (e: Exception) {
showErrorToast(e)
} finally {
inputStream?.close()
out?.close()
}
}
runOnUiThread {
callback()
}
fixDateTaken(newPaths)
}.start()
}
fun BaseSimpleActivity.emptyTheRecycleBin(callback: (() -> Unit)? = null) {
Thread {
recycleBin.deleteRecursively()
galleryDB.MediumDao().clearRecycleBin()
galleryDB.DirectoryDao().deleteRecycleBin()
toast(R.string.recycle_bin_emptied)
callback?.invoke()
}.start()
}
fun BaseSimpleActivity.emptyAndDisableTheRecycleBin(callback: () -> Unit) {
Thread {
emptyTheRecycleBin {
config.useRecycleBin = false
callback()
}
}.start()
}
fun BaseSimpleActivity.showRecycleBinEmptyingDialog(callback: () -> Unit) {
ConfirmationDialog(this, "", R.string.empty_recycle_bin_confirmation, R.string.yes, R.string.no) {
callback()
}
}
fun BaseSimpleActivity.updateFavoritePaths(fileDirItems: ArrayList<FileDirItem>, destination: String) {
Thread {
fileDirItems.forEach {
val newPath = "$destination/${it.name}"
updateDBMediaPath(it.path, newPath)
}
}.start()
}
fun Activity.hasNavBar(): Boolean {
val display = windowManager.defaultDisplay
val realDisplayMetrics = DisplayMetrics()
display.getRealMetrics(realDisplayMetrics)
val displayMetrics = DisplayMetrics()
display.getMetrics(displayMetrics)
return (realDisplayMetrics.widthPixels - displayMetrics.widthPixels > 0) || (realDisplayMetrics.heightPixels - displayMetrics.heightPixels > 0)
}
fun Activity.fixDateTaken(paths: ArrayList<String>, callback: (() -> Unit)? = null) {
val BATCH_SIZE = 50
toast(R.string.fixing)
try {
var didUpdateFile = false
val operations = ArrayList<ContentProviderOperation>()
val mediumDao = galleryDB.MediumDao()
for (path in paths) {
val dateTime = ExifInterface(path).getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL)
?: ExifInterface(path).getAttribute(ExifInterface.TAG_DATETIME) ?: continue
// some formats contain a "T" in the middle, some don't
// sample dates: 2015-07-26T14:55:23, 2018:09:05 15:09:05
val t = if (dateTime.substring(10, 11) == "T") "\'T\'" else " "
val separator = dateTime.substring(4, 5)
val format = "yyyy${separator}MM${separator}dd${t}kk:mm:ss"
val formatter = SimpleDateFormat(format, Locale.getDefault())
val timestamp = formatter.parse(dateTime).time
val uri = getFileUri(path)
ContentProviderOperation.newUpdate(uri).apply {
val selection = "${MediaStore.Images.Media.DATA} = ?"
val selectionArgs = arrayOf(path)
withSelection(selection, selectionArgs)
withValue(MediaStore.Images.Media.DATE_TAKEN, timestamp)
operations.add(build())
}
if (operations.size % BATCH_SIZE == 0) {
contentResolver.applyBatch(MediaStore.AUTHORITY, operations)
operations.clear()
}
mediumDao.updateFavoriteDateTaken(path, timestamp)
didUpdateFile = true
}
val resultSize = contentResolver.applyBatch(MediaStore.AUTHORITY, operations).size
if (resultSize == 0) {
didUpdateFile = false
rescanPaths(paths)
}
toast(if (didUpdateFile) R.string.dates_fixed_successfully else R.string.unknown_error_occurred)
runOnUiThread {
callback?.invoke()
}
} catch (e: Exception) {
showErrorToast(e)
}
}
fun BaseSimpleActivity.saveRotatedImageToFile(oldPath: String, newPath: String, degrees: Int, showToasts: Boolean, callback: () -> Unit) {
var newDegrees = degrees
if (newDegrees < 0) {
newDegrees += 360
}
if (oldPath == newPath && oldPath.isJpg()) {
if (tryRotateByExif(oldPath, newDegrees, showToasts, callback)) {
return
}
}
val tmpPath = "$recycleBinPath/.tmp_${newPath.getFilenameFromPath()}"
val tmpFileDirItem = FileDirItem(tmpPath, tmpPath.getFilenameFromPath())
try {
getFileOutputStream(tmpFileDirItem) {
if (it == null) {
if (showToasts) {
toast(R.string.unknown_error_occurred)
}
return@getFileOutputStream
}
val oldLastModified = File(oldPath).lastModified()
if (oldPath.isJpg()) {
copyFile(oldPath, tmpPath)
saveExifRotation(ExifInterface(tmpPath), newDegrees)
} else {
val inputstream = getFileInputStreamSync(oldPath)
val bitmap = BitmapFactory.decodeStream(inputstream)
saveFile(tmpPath, bitmap, it as FileOutputStream, newDegrees)
}
if (getDoesFilePathExist(newPath)) {
tryDeleteFileDirItem(FileDirItem(newPath, newPath.getFilenameFromPath()), false, true)
}
copyFile(tmpPath, newPath)
scanPathRecursively(newPath)
fileRotatedSuccessfully(newPath, oldLastModified)
it.flush()
it.close()
callback.invoke()
}
} catch (e: OutOfMemoryError) {
if (showToasts) {
toast(R.string.out_of_memory_error)
}
} catch (e: Exception) {
if (showToasts) {
showErrorToast(e)
}
} finally {
tryDeleteFileDirItem(tmpFileDirItem, false, true)
}
}
@TargetApi(Build.VERSION_CODES.N)
fun Activity.tryRotateByExif(path: String, degrees: Int, showToasts: Boolean, callback: () -> Unit): Boolean {
return try {
val file = File(path)
val oldLastModified = file.lastModified()
if (saveImageRotation(path, degrees)) {
fileRotatedSuccessfully(path, oldLastModified)
callback.invoke()
if (showToasts) {
toast(R.string.file_saved)
}
true
} else {
false
}
} catch (e: Exception) {
if (showToasts) {
showErrorToast(e)
}
false
}
}
fun Activity.fileRotatedSuccessfully(path: String, lastModified: Long) {
if (config.keepLastModified) {
File(path).setLastModified(lastModified)
updateLastModified(path, lastModified)
}
Picasso.get().invalidate(path.getFileKey())
// we cannot refresh a specific image in Glide Cache, so just clear it all
val glide = Glide.get(applicationContext)
glide.clearDiskCache()
runOnUiThread {
glide.clearMemory()
}
}
fun BaseSimpleActivity.copyFile(source: String, destination: String) {
var inputStream: InputStream? = null
var out: OutputStream? = null
try {
out = getFileOutputStreamSync(destination, source.getMimeType())
inputStream = getFileInputStreamSync(source)
inputStream?.copyTo(out!!)
} finally {
inputStream?.close()
out?.close()
}
}
fun saveFile(path: String, bitmap: Bitmap, out: FileOutputStream, degrees: Int) {
val matrix = Matrix()
matrix.postRotate(degrees.toFloat())
val bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
bmp.compress(path.getCompressionFormat(), 90, out)
}

View file

@ -1,7 +1,7 @@
package com.simplemobiletools.gallery.extensions package com.simplemobiletools.gallery.pro.extensions
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.models.Medium
fun ArrayList<Medium>.getDirMediaTypes(): Int { fun ArrayList<Medium>.getDirMediaTypes(): Int {
var types = 0 var types = 0

View file

@ -1,5 +1,7 @@
package com.simplemobiletools.gallery.extensions package com.simplemobiletools.gallery.pro.extensions
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
@ -8,7 +10,6 @@ import android.database.sqlite.SQLiteException
import android.graphics.Point import android.graphics.Point
import android.graphics.drawable.PictureDrawable import android.graphics.drawable.PictureDrawable
import android.media.AudioManager import android.media.AudioManager
import android.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import android.view.WindowManager import android.view.WindowManager
import android.widget.ImageView import android.widget.ImageView
@ -19,20 +20,28 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.activities.SettingsActivity import com.simplemobiletools.gallery.pro.activities.SettingsActivity
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask import com.simplemobiletools.gallery.pro.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.databases.GalleryDatabase import com.simplemobiletools.gallery.pro.databases.GalleryDatabase
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.interfaces.DirectoryDao import com.simplemobiletools.gallery.pro.interfaces.DirectoryDao
import com.simplemobiletools.gallery.interfaces.MediumDao import com.simplemobiletools.gallery.pro.interfaces.MediumDao
import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.pro.interfaces.WidgetsDao
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.models.Directory
import com.simplemobiletools.gallery.models.ThumbnailItem import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.svg.SvgSoftwareLayerSetter import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import com.simplemobiletools.gallery.views.MySquareImageView import com.simplemobiletools.gallery.pro.svg.SvgSoftwareLayerSetter
import com.simplemobiletools.gallery.pro.views.MySquareImageView
import pl.droidsonroids.gif.GifDrawable import pl.droidsonroids.gif.GifDrawable
import java.io.File import java.io.File
import java.io.FileInputStream
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.util.HashSet
import java.util.LinkedHashSet
import kotlin.Comparator
import kotlin.collections.ArrayList
val Context.portrait get() = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT val Context.portrait get() = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
val Context.audioManager get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager val Context.audioManager get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager
@ -44,11 +53,40 @@ val Context.navigationBarWidth: Int get() = if (navigationBarRight) navigationBa
internal val Context.navigationBarSize: Point internal val Context.navigationBarSize: Point
get() = when { get() = when {
navigationBarRight -> Point(realScreenSize.x - usableScreenSize.x, usableScreenSize.y) navigationBarRight -> Point(newNavigationBarHeight, usableScreenSize.y)
navigationBarBottom -> Point(usableScreenSize.x, realScreenSize.y - usableScreenSize.y) navigationBarBottom -> Point(usableScreenSize.x, newNavigationBarHeight)
else -> Point() else -> Point()
} }
internal val Context.newNavigationBarHeight: Int
get() {
var navigationBarHeight = 0
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) {
navigationBarHeight = resources.getDimensionPixelSize(resourceId)
}
return navigationBarHeight
}
internal val Context.statusBarHeight: Int
get() {
var statusBarHeight = 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
statusBarHeight = resources.getDimensionPixelSize(resourceId)
}
return statusBarHeight
}
internal val Context.actionBarHeight: Int
get() {
val styledAttributes = theme.obtainStyledAttributes(intArrayOf(android.R.attr.actionBarSize))
val actionBarHeight = styledAttributes.getDimension(0, 0f)
styledAttributes.recycle()
return actionBarHeight.toInt()
}
val Context.usableScreenSize: Point val Context.usableScreenSize: Point
get() { get() {
val size = Point() val size = Point()
@ -59,8 +97,7 @@ val Context.usableScreenSize: Point
val Context.realScreenSize: Point val Context.realScreenSize: Point
get() { get() {
val size = Point() val size = Point()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) windowManager.defaultDisplay.getRealSize(size)
windowManager.defaultDisplay.getRealSize(size)
return size return size
} }
@ -77,6 +114,14 @@ val Context.config: Config get() = Config.newInstance(applicationContext)
val Context.galleryDB: GalleryDatabase get() = GalleryDatabase.getInstance(applicationContext) val Context.galleryDB: GalleryDatabase get() = GalleryDatabase.getInstance(applicationContext)
val Context.widgetsDB: WidgetsDao get() = GalleryDatabase.getInstance(applicationContext).WidgetsDao()
val Context.directoryDB: DirectoryDao get() = GalleryDatabase.getInstance(applicationContext).DirectoryDao()
val Context.recycleBin: File get() = filesDir
val Context.recycleBinPath: String get() = filesDir.absolutePath
fun Context.movePinnedDirectoriesToFront(dirs: ArrayList<Directory>): ArrayList<Directory> { fun Context.movePinnedDirectoriesToFront(dirs: ArrayList<Directory>): ArrayList<Directory> {
val foundFolders = ArrayList<Directory>() val foundFolders = ArrayList<Directory>()
val pinnedFolders = config.pinnedFolders val pinnedFolders = config.pinnedFolders
@ -96,6 +141,14 @@ fun Context.movePinnedDirectoriesToFront(dirs: ArrayList<Directory>): ArrayList<
dirs.add(0, newFolder) dirs.add(0, newFolder)
} }
} }
if (config.useRecycleBin && config.showRecycleBinAtFolders && config.showRecycleBinLast) {
val binIndex = dirs.indexOfFirst { it.isRecycleBin() }
if (binIndex != -1) {
val bin = dirs.removeAt(binIndex)
dirs.add(bin)
}
}
return dirs return dirs
} }
@ -104,6 +157,11 @@ fun Context.getSortedDirectories(source: ArrayList<Directory>): ArrayList<Direct
val sorting = config.directorySorting val sorting = config.directorySorting
val dirs = source.clone() as ArrayList<Directory> val dirs = source.clone() as ArrayList<Directory>
if (sorting and SORT_BY_RANDOM != 0) {
dirs.shuffle()
return movePinnedDirectoriesToFront(dirs)
}
dirs.sortWith(Comparator { o1, o2 -> dirs.sortWith(Comparator { o1, o2 ->
o1 as Directory o1 as Directory
o2 as Directory o2 as Directory
@ -124,6 +182,120 @@ fun Context.getSortedDirectories(source: ArrayList<Directory>): ArrayList<Direct
return movePinnedDirectoriesToFront(dirs) return movePinnedDirectoriesToFront(dirs)
} }
fun Context.getDirsToShow(dirs: ArrayList<Directory>, allDirs: ArrayList<Directory>, currentPathPrefix: String): ArrayList<Directory> {
return if (config.groupDirectSubfolders) {
dirs.forEach {
it.subfoldersCount = 0
it.subfoldersMediaCount = it.mediaCnt
}
val dirFolders = dirs.map { it.path }.sorted().toMutableSet() as HashSet<String>
val foldersToShow = getDirectParentSubfolders(dirFolders, currentPathPrefix)
val parentDirs = dirs.filter { foldersToShow.contains(it.path) } as ArrayList<Directory>
updateSubfolderCounts(dirs, parentDirs)
// show the current folder as an available option too, not just subfolders
if (currentPathPrefix.isNotEmpty()) {
val currentFolder = allDirs.firstOrNull { parentDirs.firstOrNull { it.path == currentPathPrefix } == null && it.path == currentPathPrefix }
currentFolder?.apply {
subfoldersCount = 1
parentDirs.add(this)
}
}
parentDirs
} else {
dirs.forEach { it.subfoldersMediaCount = it.mediaCnt }
dirs
}
}
fun Context.getDirectParentSubfolders(folders: HashSet<String>, currentPathPrefix: String): HashSet<String> {
val internalPath = internalStoragePath
val sdPath = sdCardPath
val currentPaths = LinkedHashSet<String>()
folders.forEach {
val path = it
if (path != RECYCLE_BIN && path != FAVORITES && !path.equals(internalPath, true) && !path.equals(sdPath, true)) {
if (currentPathPrefix.isNotEmpty()) {
if (path == currentPathPrefix || File(path).parent.equals(currentPathPrefix, true)) {
currentPaths.add(path)
}
} else if (folders.any { !it.equals(path, true) && (File(path).parent.equals(it, true) || File(it).parent.equals(File(path).parent, true)) }) {
// if we have folders like
// /storage/emulated/0/Pictures/Images and
// /storage/emulated/0/Pictures/Screenshots,
// but /storage/emulated/0/Pictures is empty, show Images and Screenshots as separate folders, do not group them at /Pictures
val parent = File(path).parent
if (folders.contains(parent)) {
currentPaths.add(parent)
} else {
currentPaths.add(path)
}
} else {
currentPaths.add(path)
}
}
}
var areDirectSubfoldersAvailable = false
currentPaths.forEach {
val path = it
currentPaths.forEach {
if (!it.equals(path) && File(it).parent?.equals(path) == true) {
areDirectSubfoldersAvailable = true
}
}
}
if (currentPathPrefix.isEmpty() && folders.contains(RECYCLE_BIN)) {
currentPaths.add(RECYCLE_BIN)
}
if (currentPathPrefix.isEmpty() && folders.contains(FAVORITES)) {
currentPaths.add(FAVORITES)
}
if (folders.size == currentPaths.size) {
return currentPaths
}
folders.clear()
folders.addAll(currentPaths)
return if (areDirectSubfoldersAvailable) {
getDirectParentSubfolders(folders, currentPathPrefix)
} else {
folders
}
}
fun Context.updateSubfolderCounts(children: ArrayList<Directory>, parentDirs: ArrayList<Directory>) {
for (child in children) {
var longestSharedPath = ""
for (parentDir in parentDirs) {
if (parentDir.path == child.path) {
longestSharedPath = child.path
continue
}
if (child.path.startsWith(parentDir.path, true) && parentDir.path.length > longestSharedPath.length) {
longestSharedPath = parentDir.path
}
}
// make sure we count only the proper direct subfolders, grouped the same way as on the main screen
parentDirs.firstOrNull { it.path == longestSharedPath }?.apply {
if (path.equals(child.path, true) || path.equals(File(child.path).parent, true) || children.any { it.path.equals(File(child.path).parent, true) }) {
subfoldersCount++
if (path != child.path) {
subfoldersMediaCount += child.mediaCnt
}
}
}
}
}
fun Context.getNoMediaFolders(callback: (folders: ArrayList<String>) -> Unit) { fun Context.getNoMediaFolders(callback: (folders: ArrayList<String>) -> Unit) {
Thread { Thread {
val folders = ArrayList<String>() val folders = ArrayList<String>()
@ -174,7 +346,7 @@ fun Context.rescanFolderMediaSync(path: String) {
if (!newMedia.contains(it)) { if (!newMedia.contains(it)) {
val mediumPath = (it as? Medium)?.path val mediumPath = (it as? Medium)?.path
if (mediumPath != null) { if (mediumPath != null) {
mediumDao.deleteMediumPath(mediumPath) deleteDBPath(mediumDao, mediumPath)
} }
} }
} }
@ -190,10 +362,21 @@ fun Context.storeDirectoryItems(items: ArrayList<Directory>, directoryDao: Direc
} }
fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders: MutableSet<String>): String { fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders: MutableSet<String>): String {
val dirName = when (path) { val dirName = getFolderNameFromPath(path)
return if (File(path).doesThisOrParentHaveNoMedia() && !path.isThisOrParentIncluded(includedFolders)) {
"$dirName $hidden"
} else {
dirName
}
}
fun Context.getFolderNameFromPath(path: String): String {
return when (path) {
internalStoragePath -> getString(R.string.internal) internalStoragePath -> getString(R.string.internal)
sdCardPath -> getString(R.string.sd_card) sdCardPath -> getString(R.string.sd_card)
OTG_PATH -> getString(R.string.otg) OTG_PATH -> getString(R.string.usb)
FAVORITES -> getString(R.string.favorites)
RECYCLE_BIN -> getString(R.string.recycle_bin)
else -> { else -> {
if (path.startsWith(OTG_PATH)) { if (path.startsWith(OTG_PATH)) {
path.trimEnd('/').substringAfterLast('/') path.trimEnd('/').substringAfterLast('/')
@ -202,21 +385,16 @@ fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders:
} }
} }
} }
return if (File(path).doesThisOrParentHaveNoMedia() && !path.isThisOrParentIncluded(includedFolders)) {
"$dirName $hidden"
} else {
dirName
}
} }
fun Context.loadImage(type: Int, path: String, target: MySquareImageView, horizontalScroll: Boolean, animateGifs: Boolean, cropThumbnails: Boolean) { fun Context.loadImage(type: Int, path: String, target: MySquareImageView, horizontalScroll: Boolean, animateGifs: Boolean, cropThumbnails: Boolean,
skipMemoryCacheAtPaths: ArrayList<String>? = null) {
target.isHorizontalScrolling = horizontalScroll target.isHorizontalScrolling = horizontalScroll
if (type == TYPE_IMAGES || type == TYPE_VIDEOS || type == TYPE_RAWS) { if (type == TYPE_IMAGES || type == TYPE_VIDEOS || type == TYPE_RAWS) {
if (type == TYPE_IMAGES && path.isPng()) { if (type == TYPE_IMAGES && path.isPng()) {
loadPng(path, target, cropThumbnails) loadPng(path, target, cropThumbnails, skipMemoryCacheAtPaths)
} else { } else {
loadJpg(path, target, cropThumbnails) loadJpg(path, target, cropThumbnails, skipMemoryCacheAtPaths)
} }
} else if (type == TYPE_GIFS) { } else if (type == TYPE_GIFS) {
try { try {
@ -230,9 +408,9 @@ fun Context.loadImage(type: Int, path: String, target: MySquareImageView, horizo
target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER
} catch (e: Exception) { } catch (e: Exception) {
loadJpg(path, target, cropThumbnails) loadJpg(path, target, cropThumbnails, skipMemoryCacheAtPaths)
} catch (e: OutOfMemoryError) { } catch (e: OutOfMemoryError) {
loadJpg(path, target, cropThumbnails) loadJpg(path, target, cropThumbnails, skipMemoryCacheAtPaths)
} }
} else if (type == TYPE_SVGS) { } else if (type == TYPE_SVGS) {
loadSVG(path, target, cropThumbnails) loadSVG(path, target, cropThumbnails)
@ -258,9 +436,10 @@ fun Context.getPathLocation(path: String): Int {
} }
} }
fun Context.loadPng(path: String, target: MySquareImageView, cropThumbnails: Boolean) { fun Context.loadPng(path: String, target: MySquareImageView, cropThumbnails: Boolean, skipMemoryCacheAtPaths: ArrayList<String>? = null) {
val options = RequestOptions() val options = RequestOptions()
.signature(path.getFileSignature()) .signature(path.getFileSignature())
.skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.format(DecodeFormat.PREFER_ARGB_8888) .format(DecodeFormat.PREFER_ARGB_8888)
@ -272,9 +451,10 @@ fun Context.loadPng(path: String, target: MySquareImageView, cropThumbnails: Boo
builder.apply(options).into(target) builder.apply(options).into(target)
} }
fun Context.loadJpg(path: String, target: MySquareImageView, cropThumbnails: Boolean) { fun Context.loadJpg(path: String, target: MySquareImageView, cropThumbnails: Boolean, skipMemoryCacheAtPaths: ArrayList<String>? = null) {
val options = RequestOptions() val options = RequestOptions()
.signature(path.getFileSignature()) .signature(path.getFileSignature())
.skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
val builder = Glide.with(applicationContext) val builder = Glide.with(applicationContext)
@ -299,7 +479,7 @@ fun Context.loadSVG(path: String, target: MySquareImageView, cropThumbnails: Boo
.into(target) .into(target)
} }
fun Context.getCachedDirectories(getVideosOnly: Boolean = false, getImagesOnly: Boolean = false, directoryDao: DirectoryDao = galleryDB.DirectoryDao(), callback: (ArrayList<Directory>) -> Unit) { fun Context.getCachedDirectories(getVideosOnly: Boolean = false, getImagesOnly: Boolean = false, directoryDao: DirectoryDao = galleryDB.DirectoryDao(), forceShowHidden: Boolean = false, callback: (ArrayList<Directory>) -> Unit) {
Thread { Thread {
val directories = try { val directories = try {
directoryDao.getAll() as ArrayList<Directory> directoryDao.getAll() as ArrayList<Directory>
@ -307,11 +487,11 @@ fun Context.getCachedDirectories(getVideosOnly: Boolean = false, getImagesOnly:
ArrayList<Directory>() ArrayList<Directory>()
} }
if (!config.showRecycleBinAtFolders) { if (!config.showRecycleBinAtFolders || !config.useRecycleBin) {
directories.removeAll { it.isRecycleBin() } directories.removeAll { it.isRecycleBin() }
} }
val shouldShowHidden = config.shouldShowHidden val shouldShowHidden = config.shouldShowHidden || forceShowHidden
val excludedPaths = config.excludedFolders val excludedPaths = config.excludedFolders
val includedPaths = config.includedFolders val includedPaths = config.includedFolders
var filteredDirectories = directories.filter { it.path.shouldFolderBeVisible(excludedPaths, includedPaths, shouldShowHidden) } as ArrayList<Directory> var filteredDirectories = directories.filter { it.path.shouldFolderBeVisible(excludedPaths, includedPaths, shouldShowHidden) } as ArrayList<Directory>
@ -390,11 +570,10 @@ fun Context.getCachedMedia(path: String, getVideosOnly: Boolean = false, getImag
val grouped = mediaFetcher.groupMedia(media, pathToUse) val grouped = mediaFetcher.groupMedia(media, pathToUse)
callback(grouped.clone() as ArrayList<ThumbnailItem>) callback(grouped.clone() as ArrayList<ThumbnailItem>)
val recycleBinPath = filesDir.absolutePath
val mediaToDelete = ArrayList<Medium>() val mediaToDelete = ArrayList<Medium>()
media.filter { !getDoesFilePathExist(it.path) }.forEach { media.filter { !getDoesFilePathExist(it.path) }.forEach {
if (it.path.startsWith(recycleBinPath)) { if (it.path.startsWith(recycleBinPath)) {
mediumDao.deleteMediumPath(it.path.removePrefix(recycleBinPath)) deleteDBPath(mediumDao, it.path)
} else { } else {
mediaToDelete.add(it) mediaToDelete.add(it)
} }
@ -427,10 +606,83 @@ fun Context.getOTGFolderChildrenNames(path: String) = getOTGFolderChildren(path)
fun Context.getFavoritePaths() = galleryDB.MediumDao().getFavoritePaths() as ArrayList<String> fun Context.getFavoritePaths() = galleryDB.MediumDao().getFavoritePaths() as ArrayList<String>
// remove the "recycle_bin" from the file path prefix, replace it with real bin path /data/user...
fun Context.getUpdatedDeletedMedia(mediumDao: MediumDao): ArrayList<Medium> { fun Context.getUpdatedDeletedMedia(mediumDao: MediumDao): ArrayList<Medium> {
val media = mediumDao.getDeletedMedia() as ArrayList<Medium> val media = mediumDao.getDeletedMedia() as ArrayList<Medium>
media.forEach { media.forEach {
it.path = File(filesDir.absolutePath, it.path).toString() it.path = File(recycleBinPath, it.path.removePrefix(RECYCLE_BIN)).toString()
} }
return media return media
} }
fun Context.deleteDBPath(mediumDao: MediumDao, path: String) {
mediumDao.deleteMediumPath(path.replaceFirst(recycleBinPath, RECYCLE_BIN))
}
fun Context.updateWidgets() {
val widgetIDs = AppWidgetManager.getInstance(applicationContext).getAppWidgetIds(ComponentName(applicationContext, MyWidgetProvider::class.java))
if (widgetIDs.isNotEmpty()) {
Intent(applicationContext, MyWidgetProvider::class.java).apply {
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIDs)
sendBroadcast(this)
}
}
}
// based on https://github.com/sannies/mp4parser/blob/master/examples/src/main/java/com/google/code/mp4parser/example/PrintStructure.java
fun Context.parseFileChannel(path: String, fc: FileChannel, level: Int, start: Long, end: Long, callback: () -> Unit) {
val FILE_CHANNEL_CONTAINERS = arrayListOf("moov", "trak", "mdia", "minf", "udta", "stbl")
try {
var iteration = 0
var currEnd = end
fc.position(start)
if (currEnd <= 0) {
currEnd = start + fc.size()
}
while (currEnd - fc.position() > 8) {
// just a check to avoid deadloop at some videos
if (iteration++ > 50) {
return
}
val begin = fc.position()
val byteBuffer = ByteBuffer.allocate(8)
fc.read(byteBuffer)
byteBuffer.rewind()
val size = IsoTypeReader.readUInt32(byteBuffer)
val type = IsoTypeReader.read4cc(byteBuffer)
val newEnd = begin + size
if (type == "uuid") {
val fis = FileInputStream(File(path))
fis.skip(begin)
val sb = StringBuilder()
val buffer = ByteArray(1024)
while (true) {
val n = fis.read(buffer)
if (n != -1) {
sb.append(String(buffer, 0, n))
} else {
break
}
}
val xmlString = sb.toString().toLowerCase()
if (xmlString.contains("gspherical:projectiontype>equirectangular") || xmlString.contains("gspherical:projectiontype=\"equirectangular\"")) {
callback.invoke()
}
return
}
if (FILE_CHANNEL_CONTAINERS.contains(type)) {
parseFileChannel(path, fc, level + 1, begin + 8, newEnd, callback)
}
fc.position(newEnd)
}
} catch (ignored: Exception) {
}
}

View file

@ -1,6 +1,6 @@
package com.simplemobiletools.gallery.extensions package com.simplemobiletools.gallery.pro.extensions
import com.simplemobiletools.gallery.helpers.NOMEDIA import com.simplemobiletools.gallery.pro.helpers.NOMEDIA
import java.io.File import java.io.File
fun File.containsNoMedia() = isDirectory && File(this, NOMEDIA).exists() fun File.containsNoMedia() = isDirectory && File(this, NOMEDIA).exists()

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.extensions package com.simplemobiletools.gallery.pro.extensions
import android.os.Environment import android.os.Environment
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.extensions package com.simplemobiletools.gallery.pro.extensions
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources

View file

@ -1,13 +1,16 @@
package com.simplemobiletools.gallery.extensions package com.simplemobiletools.gallery.pro.extensions
import android.media.MediaMetadataRetriever
import com.bumptech.glide.signature.ObjectKey import com.bumptech.glide.signature.ObjectKey
import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.commons.helpers.OTG_PATH
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
fun String.getFileSignature(): ObjectKey { fun String.getFileSignature() = ObjectKey(getFileKey())
fun String.getFileKey(): String {
val file = File(this) val file = File(this)
return ObjectKey("${file.absolutePath}${file.lastModified()}") return "${file.absolutePath}${file.lastModified()}"
} }
fun String.isThisOrParentIncluded(includedPaths: MutableSet<String>) = includedPaths.any { startsWith(it, true) } fun String.isThisOrParentIncluded(includedPaths: MutableSet<String>) = includedPaths.any { startsWith(it, true) }
@ -18,8 +21,14 @@ fun String.shouldFolderBeVisible(excludedPaths: MutableSet<String>, includedPath
val file = File(this) val file = File(this)
return if (isEmpty()) { return if (isEmpty()) {
false false
} else if (!showHidden && file.isHidden) {
false
} else if (includedPaths.contains(this)) {
true
} else if (!showHidden && file.containsNoMedia()) { } else if (!showHidden && file.containsNoMedia()) {
false false
} else if (excludedPaths.contains(this)) {
false
} else if (isThisOrParentIncluded(includedPaths)) { } else if (isThisOrParentIncluded(includedPaths)) {
true true
} else if (isThisOrParentExcluded(excludedPaths)) { } else if (isThisOrParentExcluded(excludedPaths)) {
@ -43,3 +52,14 @@ fun String.getDistinctPath(): String {
toLowerCase() toLowerCase()
} }
} }
fun String.getVideoDuration(): Int {
var seconds = 0
try {
val retriever = MediaMetadataRetriever()
retriever.setDataSource(this)
seconds = Math.round(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toInt() / 1000f)
} catch (e: Exception) {
}
return seconds
}

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.extensions package com.simplemobiletools.gallery.pro.extensions
import android.os.SystemClock import android.os.SystemClock
import android.view.MotionEvent import android.view.MotionEvent

View file

@ -0,0 +1,630 @@
package com.simplemobiletools.gallery.pro.fragments
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.PictureDrawable
import android.media.ExifInterface.*
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.alexvasilkov.gestures.GestureController
import com.alexvasilkov.gestures.State
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.davemorrissey.labs.subscaleview.DecoderFactory
import com.davemorrissey.labs.subscaleview.ImageDecoder
import com.davemorrissey.labs.subscaleview.ImageRegionDecoder
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.activities.PanoramaPhotoActivity
import com.simplemobiletools.gallery.pro.activities.PhotoActivity
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.pro.svg.SvgSoftwareLayerSetter
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import it.sephiroth.android.library.exif2.ExifInterface
import kotlinx.android.synthetic.main.pager_photo_item.view.*
import org.apache.sanselan.common.byteSources.ByteSourceInputStream
import org.apache.sanselan.formats.jpeg.JpegImageParser
import pl.droidsonroids.gif.InputSource
import java.io.File
import java.io.FileOutputStream
import java.util.*
class PhotoFragment : ViewPagerFragment() {
private val DEFAULT_DOUBLE_TAP_ZOOM = 2f
private val ZOOMABLE_VIEW_LOAD_DELAY = 150L
private val SAME_ASPECT_RATIO_THRESHOLD = 0.01
// devices with good displays, but the rest of the hardware not good enough for them
private val WEIRD_DEVICES = arrayListOf(
"motorola xt1685",
"google nexus 5x"
)
var mCurrentRotationDegrees = 0
private var mIsFragmentVisible = false
private var mIsFullscreen = false
private var mWasInit = false
private var mIsPanorama = false
private var mIsSubsamplingVisible = false // checking view.visibility is unreliable, use an extra variable for it
private var mImageOrientation = -1
private var mLoadZoomableViewHandler = Handler()
private var mScreenWidth = 0
private var mScreenHeight = 0
private var mCurrentGestureViewZoom = 1f
private var mStoredShowExtendedDetails = false
private var mStoredHideExtendedDetails = false
private var mStoredAllowDeepZoomableImages = false
private var mStoredShowHighestQuality = false
private var mStoredExtendedDetails = 0
private lateinit var mView: ViewGroup
private lateinit var mMedium: Medium
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
mView = (inflater.inflate(R.layout.pager_photo_item, container, false) as ViewGroup).apply {
subsampling_view.setOnClickListener { photoClicked() }
gestures_view.setOnClickListener { photoClicked() }
gif_view.setOnClickListener { photoClicked() }
instant_prev_item.setOnClickListener { listener?.goToPrevItem() }
instant_next_item.setOnClickListener { listener?.goToNextItem() }
panorama_outline.setOnClickListener { openPanorama() }
instant_prev_item.parentView = container
instant_next_item.parentView = container
photo_brightness_controller.initialize(activity!!, slide_info, true, container) { x, y ->
mView.apply {
if (subsampling_view.isVisible()) {
subsampling_view.sendFakeClick(x, y)
} else {
gestures_view.sendFakeClick(x, y)
}
}
}
if (context.config.allowDownGesture) {
gif_view.setOnTouchListener { v, event ->
if (gif_view_frame.controller.state.zoom == 1f) {
handleEvent(event)
}
false
}
gestures_view.controller.addOnStateChangeListener(object : GestureController.OnStateChangeListener {
override fun onStateChanged(state: State) {
mCurrentGestureViewZoom = state.zoom
}
})
gestures_view.setOnTouchListener { v, event ->
if (mCurrentGestureViewZoom == 1f) {
handleEvent(event)
}
false
}
subsampling_view.setOnTouchListener { v, event ->
if (subsampling_view.isZoomedOut()) {
handleEvent(event)
}
false
}
}
}
checkScreenDimensions()
storeStateVariables()
if (!mIsFragmentVisible && activity is PhotoActivity) {
mIsFragmentVisible = true
}
mMedium = arguments!!.getSerializable(MEDIUM) as Medium
if (mMedium.path.startsWith("content://") && !mMedium.path.startsWith("content://mms/")) {
val originalPath = mMedium.path
mMedium.path = context!!.getRealPathFromURI(Uri.parse(originalPath)) ?: mMedium.path
if (mMedium.path.isEmpty()) {
var out: FileOutputStream? = null
try {
var inputStream = context!!.contentResolver.openInputStream(Uri.parse(originalPath))
val exif = ExifInterface()
exif.readExif(inputStream, ExifInterface.Options.OPTION_ALL)
val tag = exif.getTag(ExifInterface.TAG_ORIENTATION)
val orientation = tag?.getValueAsInt(-1) ?: -1
inputStream = context!!.contentResolver.openInputStream(Uri.parse(originalPath))
val original = BitmapFactory.decodeStream(inputStream)
val rotated = rotateViaMatrix(original, orientation)
exif.setTagValue(ExifInterface.TAG_ORIENTATION, 1)
exif.removeCompressedThumbnail()
val file = File(context!!.externalCacheDir, Uri.parse(originalPath).lastPathSegment)
out = FileOutputStream(file)
rotated.compress(Bitmap.CompressFormat.JPEG, 100, out)
mMedium.path = file.absolutePath
} catch (e: Exception) {
activity!!.toast(R.string.unknown_error_occurred)
return mView
} finally {
out?.close()
}
}
}
mIsFullscreen = activity!!.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN == View.SYSTEM_UI_FLAG_FULLSCREEN
loadImage()
initExtendedDetails()
mWasInit = true
checkIfPanorama()
updateInstantSwitchWidths()
return mView
}
override fun onPause() {
super.onPause()
storeStateVariables()
}
override fun onResume() {
super.onResume()
val config = context!!.config
if (mWasInit && (config.showExtendedDetails != mStoredShowExtendedDetails || config.extendedDetails != mStoredExtendedDetails)) {
initExtendedDetails()
}
if (mWasInit) {
if (config.allowZoomingImages != mStoredAllowDeepZoomableImages || config.showHighestQuality != mStoredShowHighestQuality) {
mIsSubsamplingVisible = false
mView.subsampling_view.beGone()
loadImage()
} else if (mMedium.isGIF()) {
loadGif()
}
}
val allowPhotoGestures = config.allowPhotoGestures
val allowInstantChange = config.allowInstantChange
mView.apply {
photo_brightness_controller.beVisibleIf(allowPhotoGestures)
instant_prev_item.beVisibleIf(allowInstantChange)
instant_next_item.beVisibleIf(allowInstantChange)
}
storeStateVariables()
}
override fun onDestroyView() {
super.onDestroyView()
if (activity?.isDestroyed == false) {
mView.subsampling_view.recycle()
}
mLoadZoomableViewHandler.removeCallbacksAndMessages(null)
if (mCurrentRotationDegrees != 0) {
Thread {
val path = mMedium.path
(activity as? BaseSimpleActivity)?.saveRotatedImageToFile(path, path, mCurrentRotationDegrees, false) {}
}.start()
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// avoid GIFs being skewed, played in wrong aspect ratio
if (mMedium.isGIF()) {
mView.onGlobalLayout {
measureScreen()
Handler().postDelayed({
mView.gif_view_frame.controller.resetState()
loadGif()
}, 50)
}
} else {
hideZoomableView()
loadImage()
}
initExtendedDetails()
updateInstantSwitchWidths()
}
override fun setMenuVisibility(menuVisible: Boolean) {
super.setMenuVisibility(menuVisible)
mIsFragmentVisible = menuVisible
if (mWasInit) {
if (!mMedium.isGIF()) {
photoFragmentVisibilityChanged(menuVisible)
}
}
}
private fun storeStateVariables() {
context!!.config.apply {
mStoredShowExtendedDetails = showExtendedDetails
mStoredHideExtendedDetails = hideExtendedDetails
mStoredAllowDeepZoomableImages = allowZoomingImages
mStoredShowHighestQuality = showHighestQuality
mStoredExtendedDetails = extendedDetails
}
}
private fun checkScreenDimensions() {
if (mScreenWidth == 0 || mScreenHeight == 0) {
measureScreen()
}
}
private fun measureScreen() {
val metrics = DisplayMetrics()
activity!!.windowManager.defaultDisplay.getRealMetrics(metrics)
mScreenWidth = metrics.widthPixels
mScreenHeight = metrics.heightPixels
}
private fun photoFragmentVisibilityChanged(isVisible: Boolean) {
if (isVisible) {
scheduleZoomableView()
} else {
hideZoomableView()
}
}
private fun degreesForRotation(orientation: Int) = when (orientation) {
ORIENTATION_ROTATE_270 -> 270
ORIENTATION_ROTATE_180 -> 180
ORIENTATION_ROTATE_90 -> 90
else -> 0
}
private fun rotateViaMatrix(original: Bitmap, orientation: Int): Bitmap {
val degrees = degreesForRotation(orientation).toFloat()
return if (degrees == 0f) {
original
} else {
val matrix = Matrix()
matrix.setRotate(degrees)
Bitmap.createBitmap(original, 0, 0, original.width, original.height, matrix, true)
}
}
private fun loadImage() {
checkScreenDimensions()
mImageOrientation = getImageOrientation()
when {
mMedium.isGIF() -> loadGif()
mMedium.isSVG() -> loadSVG()
else -> loadBitmap()
}
}
private fun loadGif() {
try {
val pathToLoad = getPathToLoad(mMedium)
val source = if (pathToLoad.startsWith("content://") || pathToLoad.startsWith("file://")) {
InputSource.UriSource(context!!.contentResolver, Uri.parse(pathToLoad))
} else {
InputSource.FileSource(pathToLoad)
}
mView.apply {
gestures_view.beGone()
gif_view.setInputSource(source)
gif_view_frame.beVisible()
}
} catch (e: Exception) {
loadBitmap()
} catch (e: OutOfMemoryError) {
loadBitmap()
}
}
private fun loadSVG() {
Glide.with(context!!)
.`as`(PictureDrawable::class.java)
.listener(SvgSoftwareLayerSetter())
.load(mMedium.path)
.into(mView.gestures_view)
}
private fun loadBitmap(addZoomableView: Boolean = true) {
val options = RequestOptions()
.signature(mMedium.path.getFileSignature())
.format(DecodeFormat.PREFER_ARGB_8888)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.fitCenter()
if (mCurrentRotationDegrees != 0) {
options.transform(GlideRotateTransformation(mCurrentRotationDegrees))
options.diskCacheStrategy(DiskCacheStrategy.NONE)
}
Glide.with(context!!)
.load(getPathToLoad(mMedium))
.apply(options)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
if (activity != null) {
tryLoadingWithPicasso(addZoomableView)
}
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
if (mIsFragmentVisible && addZoomableView) {
scheduleZoomableView()
}
return false
}
}).into(mView.gestures_view)
}
private fun tryLoadingWithPicasso(addZoomableView: Boolean) {
var pathToLoad = if (mMedium.path.startsWith("content://")) mMedium.path else "file://${mMedium.path}"
pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23")
try {
val picasso = Picasso.get()
.load(pathToLoad)
.centerInside()
.stableKey(mMedium.path.getFileKey())
.resize(mScreenWidth, mScreenHeight)
if (mCurrentRotationDegrees != 0) {
picasso.rotate(mCurrentRotationDegrees.toFloat())
} else {
degreesForRotation(mImageOrientation).toFloat()
}
picasso.into(mView.gestures_view, object : Callback {
override fun onSuccess() {
mView.gestures_view.controller.settings.isZoomEnabled = mCurrentRotationDegrees != 0 || context?.config?.allowZoomingImages == false
if (mIsFragmentVisible && addZoomableView) {
scheduleZoomableView()
}
}
override fun onError(e: Exception) {}
})
} catch (ignored: Exception) {
}
}
private fun openPanorama() {
Intent(context, PanoramaPhotoActivity::class.java).apply {
putExtra(PATH, mMedium.path)
startActivity(this)
}
}
private fun scheduleZoomableView() {
mLoadZoomableViewHandler.removeCallbacksAndMessages(null)
mLoadZoomableViewHandler.postDelayed({
if (mIsFragmentVisible && context?.config?.allowZoomingImages == true && mMedium.isImage() && !mIsSubsamplingVisible) {
addZoomableView()
}
}, ZOOMABLE_VIEW_LOAD_DELAY)
}
private fun addZoomableView() {
val rotation = degreesForRotation(mImageOrientation)
val path = getPathToLoad(mMedium)
mIsSubsamplingVisible = true
val bitmapDecoder = object : DecoderFactory<ImageDecoder> {
override fun make() = PicassoDecoder(path, Picasso.get(), rotation)
}
val regionDecoder = object : DecoderFactory<ImageRegionDecoder> {
override fun make() = PicassoRegionDecoder()
}
var newOrientation = (rotation + mCurrentRotationDegrees) % 360
if (newOrientation < 0) {
newOrientation += 360
}
val config = context!!.config
mView.subsampling_view.apply {
setMaxTileSize(if (config.showHighestQuality) Integer.MAX_VALUE else 4096)
setMinimumTileDpi(if (config.showHighestQuality) -1 else getMinTileDpi())
background = ColorDrawable(Color.TRANSPARENT)
bitmapDecoderFactory = bitmapDecoder
regionDecoderFactory = regionDecoder
maxScale = 10f
beVisible()
isOneToOneZoomEnabled = config.allowOneToOneZoom
orientation = newOrientation
setImage(path)
onImageEventListener = object : SubsamplingScaleImageView.OnImageEventListener {
override fun onReady() {
background = ColorDrawable(if (config.blackBackground) Color.BLACK else config.backgroundColor)
val useWidth = if (mImageOrientation == ORIENTATION_ROTATE_90 || mImageOrientation == ORIENTATION_ROTATE_270) sHeight else sWidth
val useHeight = if (mImageOrientation == ORIENTATION_ROTATE_90 || mImageOrientation == ORIENTATION_ROTATE_270) sWidth else sHeight
doubleTapZoomScale = getDoubleTapZoomScale(useWidth, useHeight)
}
override fun onImageLoadError(e: Exception) {
mView.gestures_view.controller.settings.isZoomEnabled = true
background = ColorDrawable(Color.TRANSPARENT)
mIsSubsamplingVisible = false
beGone()
}
override fun onImageRotation(degrees: Int) {
val fullRotation = (rotation + degrees) % 360
val useWidth = if (fullRotation == 90 || fullRotation == 270) sHeight else sWidth
val useHeight = if (fullRotation == 90 || fullRotation == 270) sWidth else sHeight
doubleTapZoomScale = getDoubleTapZoomScale(useWidth, useHeight)
mCurrentRotationDegrees = (mCurrentRotationDegrees + degrees) % 360
loadBitmap(false)
activity?.invalidateOptionsMenu()
}
}
}
}
private fun getMinTileDpi(): Int {
val metrics = resources.displayMetrics
val averageDpi = (metrics.xdpi + metrics.ydpi) / 2
val device = "${Build.BRAND} ${Build.MODEL}".toLowerCase()
return when {
WEIRD_DEVICES.contains(device) -> 240
averageDpi > 400 -> 280
averageDpi > 300 -> 220
else -> 160
}
}
private fun checkIfPanorama() {
mIsPanorama = try {
val inputStream = if (mMedium.path.startsWith("content:/")) context!!.contentResolver.openInputStream(Uri.parse(mMedium.path)) else File(mMedium.path).inputStream()
val imageParser = JpegImageParser().getXmpXml(ByteSourceInputStream(inputStream, mMedium.name), HashMap<String, Any>())
imageParser.contains("GPano:UsePanoramaViewer=\"True\"", true) || imageParser.contains("<GPano:UsePanoramaViewer>True</GPano:UsePanoramaViewer>", true)
} catch (e: Exception) {
false
} catch (e: OutOfMemoryError) {
false
}
mView.panorama_outline.beVisibleIf(mIsPanorama)
}
private fun getImageOrientation(): Int {
val defaultOrientation = -1
var orient = defaultOrientation
try {
val pathToLoad = getPathToLoad(mMedium)
val exif = android.media.ExifInterface(pathToLoad)
orient = exif.getAttributeInt(android.media.ExifInterface.TAG_ORIENTATION, defaultOrientation)
if (orient == defaultOrientation || mMedium.path.startsWith(OTG_PATH)) {
val uri = if (pathToLoad.startsWith("content:/")) Uri.parse(pathToLoad) else Uri.fromFile(File(pathToLoad))
val inputStream = context!!.contentResolver.openInputStream(uri)
val exif2 = ExifInterface()
exif2.readExif(inputStream, ExifInterface.Options.OPTION_ALL)
orient = exif2.getTag(ExifInterface.TAG_ORIENTATION)?.getValueAsInt(defaultOrientation) ?: defaultOrientation
}
} catch (ignored: Exception) {
} catch (ignored: OutOfMemoryError) {
}
return orient
}
private fun getDoubleTapZoomScale(width: Int, height: Int): Float {
val bitmapAspectRatio = height / width.toFloat()
val screenAspectRatio = mScreenHeight / mScreenWidth.toFloat()
return if (context == null || Math.abs(bitmapAspectRatio - screenAspectRatio) < SAME_ASPECT_RATIO_THRESHOLD) {
DEFAULT_DOUBLE_TAP_ZOOM
} else if (context!!.portrait && bitmapAspectRatio <= screenAspectRatio) {
mScreenHeight / height.toFloat()
} else if (context!!.portrait && bitmapAspectRatio > screenAspectRatio) {
mScreenWidth / width.toFloat()
} else if (!context!!.portrait && bitmapAspectRatio >= screenAspectRatio) {
mScreenWidth / width.toFloat()
} else if (!context!!.portrait && bitmapAspectRatio < screenAspectRatio) {
mScreenHeight / height.toFloat()
} else {
DEFAULT_DOUBLE_TAP_ZOOM
}
}
fun rotateImageViewBy(degrees: Int) {
if (mIsSubsamplingVisible) {
mView.subsampling_view.rotateBy(degrees)
} else {
mCurrentRotationDegrees = (mCurrentRotationDegrees + degrees) % 360
mLoadZoomableViewHandler.removeCallbacksAndMessages(null)
mIsSubsamplingVisible = false
loadBitmap()
}
}
private fun initExtendedDetails() {
if (context!!.config.showExtendedDetails) {
mView.photo_details.apply {
beInvisible() // make it invisible so we can measure it, but not show yet
text = getMediumExtendedDetails(mMedium)
onGlobalLayout {
if (isAdded) {
val realY = getExtendedDetailsY(height)
if (realY > 0) {
y = realY
beVisibleIf(text.isNotEmpty())
alpha = if (!context!!.config.hideExtendedDetails || !mIsFullscreen) 1f else 0f
}
}
}
}
} else {
mView.photo_details.beGone()
}
}
private fun hideZoomableView() {
if (context?.config?.allowZoomingImages == true) {
mIsSubsamplingVisible = false
mView.subsampling_view.recycle()
mView.subsampling_view.beGone()
mLoadZoomableViewHandler.removeCallbacksAndMessages(null)
}
}
private fun photoClicked() {
listener?.fragmentClicked()
}
private fun updateInstantSwitchWidths() {
val newWidth = resources.getDimension(R.dimen.instant_change_bar_width) + if (activity?.portrait == false) activity!!.navigationBarWidth else 0
mView.instant_prev_item.layoutParams.width = newWidth.toInt()
mView.instant_next_item.layoutParams.width = newWidth.toInt()
}
override fun fullscreenToggled(isFullscreen: Boolean) {
this.mIsFullscreen = isFullscreen
mView.photo_details.apply {
if (mStoredShowExtendedDetails && isVisible()) {
animate().y(getExtendedDetailsY(height))
if (mStoredHideExtendedDetails) {
animate().alpha(if (isFullscreen) 0f else 1f).start()
}
}
}
}
private fun getExtendedDetailsY(height: Int): Float {
val smallMargin = resources.getDimension(R.dimen.small_margin)
val fullscreenOffset = smallMargin + if (mIsFullscreen) 0 else context!!.navigationBarHeight
val actionsHeight = if (context!!.config.bottomActions && !mIsFullscreen) resources.getDimension(R.dimen.bottom_actions_height) else 0f
return context!!.realScreenSize.y - height - actionsHeight - fullscreenOffset
}
}

View file

@ -1,18 +1,16 @@
package com.simplemobiletools.gallery.fragments package com.simplemobiletools.gallery.pro.fragments
import android.annotation.TargetApi import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Point import android.graphics.Point
import android.graphics.SurfaceTexture import android.graphics.SurfaceTexture
import android.media.AudioManager
import android.media.MediaMetadataRetriever
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.* import android.view.*
import android.view.animation.AnimationUtils import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.SeekBar import android.widget.SeekBar
import android.widget.TextView import android.widget.TextView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -20,68 +18,115 @@ import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.ExtractorMediaSource import com.google.android.exoplayer2.source.ExtractorMediaSource
import com.google.android.exoplayer2.source.TrackGroupArray import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelectionArray import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.google.android.exoplayer2.upstream.ContentDataSource import com.google.android.exoplayer2.upstream.ContentDataSource
import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DataSpec import com.google.android.exoplayer2.upstream.DataSpec
import com.google.android.exoplayer2.upstream.FileDataSource import com.google.android.exoplayer2.upstream.FileDataSource
import com.google.android.exoplayer2.video.VideoListener
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.isJellyBean1Plus import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.activities.PanoramaVideoActivity
import com.simplemobiletools.gallery.activities.VideoActivity import com.simplemobiletools.gallery.pro.activities.VideoActivity
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.helpers.MEDIUM import com.simplemobiletools.gallery.pro.helpers.Config
import com.simplemobiletools.gallery.helpers.MediaSideScroll import com.simplemobiletools.gallery.pro.helpers.MEDIUM
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.helpers.MIN_SKIP_LENGTH
import com.simplemobiletools.gallery.pro.helpers.PATH
import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.pro.views.MediaSideScroll
import kotlinx.android.synthetic.main.bottom_video_time_holder.view.*
import kotlinx.android.synthetic.main.pager_video_item.view.* import kotlinx.android.synthetic.main.pager_video_item.view.*
import java.io.File import java.io.File
import java.io.FileInputStream
class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, SeekBar.OnSeekBarChangeListener { class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, SeekBar.OnSeekBarChangeListener {
private val PROGRESS = "progress" private val PROGRESS = "progress"
private val MIN_SKIP_LENGTH = 2000
private val HIDE_PAUSE_DELAY = 2000L
private val PLAY_PAUSE_VISIBLE_ALPHA = 0.8f
private var mTextureView: TextureView? = null private var mIsFullscreen = false
private var mCurrTimeView: TextView? = null private var mWasFragmentInit = false
private var mSeekBar: SeekBar? = null private var mIsPanorama = false
private var mTimeHolder: View? = null private var mIsFragmentVisible = false
private var mView: View? = null private var mIsPlaying = false
private var mIsDragged = false
private var mWasVideoStarted = false
private var mCurrTime = 0
private var mDuration = 0
private var mExoPlayer: SimpleExoPlayer? = null private var mExoPlayer: SimpleExoPlayer? = null
private var mVideoSize = Point(0, 0) private var mVideoSize = Point(0, 0)
private var mTimerHandler = Handler() private var mTimerHandler = Handler()
private var mHidePauseHandler = Handler()
private var mIsPlaying = false
private var mIsDragged = false
private var mIsFullscreen = false
private var mIsFragmentVisible = false
private var mWasFragmentInit = false
private var mIsExoPlayerInitialized = false
private var mCurrTime = 0
private var mDuration = 0
private var mStoredShowExtendedDetails = false private var mStoredShowExtendedDetails = false
private var mStoredHideExtendedDetails = false private var mStoredHideExtendedDetails = false
private var mStoredBottomActions = true private var mStoredBottomActions = true
private var mStoredExtendedDetails = 0 private var mStoredExtendedDetails = 0
private var mStoredRememberLastVideoPosition = false
private var mStoredLastVideoPath = ""
private var mStoredLastVideoPosition = 0
private lateinit var brightnessSideScroll: MediaSideScroll private lateinit var mTimeHolder: View
private lateinit var volumeSideScroll: MediaSideScroll private lateinit var mBrightnessSideScroll: MediaSideScroll
private lateinit var mVolumeSideScroll: MediaSideScroll
lateinit var medium: Medium private lateinit var mView: View
private lateinit var mMedium: Medium
private lateinit var mConfig: Config
private lateinit var mTextureView: TextureView
private lateinit var mCurrTimeView: TextView
private lateinit var mPlayPauseButton: ImageView
private lateinit var mSeekBar: SeekBar
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
mConfig = context!!.config
mView = inflater.inflate(R.layout.pager_video_item, container, false).apply { mView = inflater.inflate(R.layout.pager_video_item, container, false).apply {
instant_prev_item.setOnClickListener { listener?.goToPrevItem() } instant_prev_item.setOnClickListener { listener?.goToPrevItem() }
instant_next_item.setOnClickListener { listener?.goToNextItem() } instant_next_item.setOnClickListener { listener?.goToNextItem() }
video_curr_time.setOnClickListener { skip(false) }
video_duration.setOnClickListener { skip(true) }
video_holder.setOnClickListener { toggleFullscreen() }
video_preview.setOnClickListener { toggleFullscreen() }
panorama_outline.setOnClickListener { openPanorama() }
video_play_outline.setOnClickListener {
if (mConfig.openVideosOnSeparateScreen) {
launchVideoPlayer()
} else {
togglePlayPause()
}
}
mPlayPauseButton = video_toggle_play_pause
mPlayPauseButton.setOnClickListener {
togglePlayPause()
}
mSeekBar = video_seekbar
mSeekBar.setOnSeekBarChangeListener(this@VideoFragment)
// adding an empty click listener just to avoid ripple animation at toggling fullscreen
mSeekBar.setOnClickListener { }
mTimeHolder = video_time_holder mTimeHolder = video_time_holder
mCurrTimeView = video_curr_time
mBrightnessSideScroll = video_brightness_controller
mVolumeSideScroll = video_volume_controller
mTextureView = video_surface
if (mConfig.allowDownGesture) {
video_preview.setOnTouchListener { view, event ->
handleEvent(event)
false
}
video_surface_frame.setOnTouchListener { view, event ->
if (video_surface_frame.controller.state.zoom == 1f) {
handleEvent(event)
}
false
}
}
} }
storeStateVariables() storeStateVariables()
medium = arguments!!.getSerializable(MEDIUM) as Medium mMedium = arguments!!.getSerializable(MEDIUM) as Medium
Glide.with(context!!).load(mMedium.path).into(mView.video_preview)
// setMenuVisibility is not called at VideoActivity (third party intent) // setMenuVisibility is not called at VideoActivity (third party intent)
if (!mIsFragmentVisible && activity is VideoActivity) { if (!mIsFragmentVisible && activity is VideoActivity) {
@ -89,81 +134,91 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
} }
mIsFullscreen = activity!!.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN == View.SYSTEM_UI_FLAG_FULLSCREEN mIsFullscreen = activity!!.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN == View.SYSTEM_UI_FLAG_FULLSCREEN
initTimeHolder()
checkIfPanorama()
setupPlayer() mMedium.path.getVideoResolution()?.apply {
if (savedInstanceState != null) {
mCurrTime = savedInstanceState.getInt(PROGRESS)
}
checkFullscreen()
mWasFragmentInit = true
mView!!.apply {
brightnessSideScroll = video_brightness_controller
brightnessSideScroll.initialize(activity!!, slide_info, true, container) { x, y ->
video_holder.performClick()
}
volumeSideScroll = video_volume_controller
volumeSideScroll.initialize(activity!!, slide_info, false, container) { x, y ->
video_holder.performClick()
}
video_curr_time.setOnClickListener { skip(false) }
video_duration.setOnClickListener { skip(true) }
Glide.with(context!!).load(medium.path).into(video_preview)
}
mExoPlayer = ExoPlayerFactory.newSimpleInstance(context, DefaultTrackSelector())
initExoPlayerListeners()
medium.path.getVideoResolution()?.apply {
mVideoSize.x = x mVideoSize.x = x
mVideoSize.y = y mVideoSize.y = y
setVideoSize() }
if (mIsPanorama) {
mView.apply {
panorama_outline.beVisible()
video_play_outline.beGone()
mVolumeSideScroll.beGone()
mBrightnessSideScroll.beGone()
Glide.with(context!!).load(mMedium.path).into(video_preview)
}
}
if (!mIsPanorama) {
setupPlayer()
if (savedInstanceState != null) {
mCurrTime = savedInstanceState.getInt(PROGRESS)
}
mWasFragmentInit = true
if (mVideoSize.x != 0 && mVideoSize.y != 0) {
setVideoSize()
}
mView.apply {
mBrightnessSideScroll.initialize(activity!!, slide_info, true, container) { x, y ->
video_holder.performClick()
}
mVolumeSideScroll.initialize(activity!!, slide_info, false, container) { x, y ->
video_holder.performClick()
}
video_surface.onGlobalLayout {
if (mIsFragmentVisible && mConfig.autoplayVideos && !mConfig.openVideosOnSeparateScreen) {
playVideo()
}
}
}
} }
setupVideoDuration() setupVideoDuration()
if (mStoredRememberLastVideoPosition) {
mView!!.video_surface.onGlobalLayout { setLastVideoSavedPosition()
if (mIsFragmentVisible && context?.config?.autoplayVideos == true) {
playVideo()
}
} }
updateInstantSwitchWidths()
return mView return mView
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
activity!!.updateTextColors(mView!!.video_holder) mConfig = context!!.config // make sure we get a new config, in case the user changed something in the app settings
val config = context!!.config activity!!.updateTextColors(mView.video_holder)
val allowVideoGestures = config.allowVideoGestures val allowVideoGestures = mConfig.allowVideoGestures
val allowInstantChange = config.allowInstantChange val allowInstantChange = mConfig.allowInstantChange
mView!!.apply { mTextureView.beGoneIf(mConfig.openVideosOnSeparateScreen || mIsPanorama)
video_volume_controller.beVisibleIf(allowVideoGestures) mView.apply {
video_brightness_controller.beVisibleIf(allowVideoGestures) video_surface_frame.beGoneIf(mTextureView.isGone())
video_volume_controller.beVisibleIf(allowVideoGestures && !mIsPanorama)
video_brightness_controller.beVisibleIf(allowVideoGestures && !mIsPanorama)
instant_prev_item.beVisibleIf(allowInstantChange) instant_prev_item.beVisibleIf(allowInstantChange)
instant_next_item.beVisibleIf(allowInstantChange) instant_next_item.beVisibleIf(allowInstantChange)
} }
if (config.showExtendedDetails != mStoredShowExtendedDetails || config.extendedDetails != mStoredExtendedDetails) { checkExtendedDetails()
checkExtendedDetails() initTimeHolder()
}
if (config.bottomActions != mStoredBottomActions) {
initTimeHolder()
}
mView!!.video_time_holder.setBackgroundResource(if (config.bottomActions) 0 else R.drawable.gradient_background)
storeStateVariables() storeStateVariables()
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
pauseVideo()
storeStateVariables() storeStateVariables()
pauseVideo()
if (mStoredRememberLastVideoPosition && mIsFragmentVisible && mWasVideoStarted) {
saveVideoProgress()
}
} }
override fun onDestroy() { override fun onDestroy() {
@ -180,7 +235,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
} }
mIsFragmentVisible = menuVisible mIsFragmentVisible = menuVisible
if (mWasFragmentInit && menuVisible && context?.config?.autoplayVideos == true) { if (mWasFragmentInit && menuVisible && mConfig.autoplayVideos && !mConfig.openVideosOnSeparateScreen) {
playVideo() playVideo()
} }
} }
@ -190,35 +245,85 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
setVideoSize() setVideoSize()
initTimeHolder() initTimeHolder()
checkExtendedDetails() checkExtendedDetails()
updateInstantSwitchWidths()
mView.video_surface_frame.onGlobalLayout {
mView.video_surface_frame.controller.resetState()
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(PROGRESS, mCurrTime)
} }
private fun storeStateVariables() { private fun storeStateVariables() {
context!!.config.apply { mConfig.apply {
mStoredShowExtendedDetails = showExtendedDetails mStoredShowExtendedDetails = showExtendedDetails
mStoredHideExtendedDetails = hideExtendedDetails mStoredHideExtendedDetails = hideExtendedDetails
mStoredExtendedDetails = extendedDetails mStoredExtendedDetails = extendedDetails
mStoredBottomActions = bottomActions mStoredBottomActions = bottomActions
mStoredRememberLastVideoPosition = rememberLastVideoPosition
mStoredLastVideoPath = lastVideoPath
mStoredLastVideoPosition = lastVideoPosition
} }
} }
private fun setupPlayer() { private fun setupPlayer() {
if (activity == null) if (activity == null || mConfig.openVideosOnSeparateScreen || mIsPanorama) {
return return
}
mView!!.video_play_outline.setOnClickListener { togglePlayPause() } mView.video_surface_frame.setOnClickListener { toggleFullscreen() }
mTextureView.surfaceTextureListener = this
mTextureView = mView!!.video_surface
mTextureView!!.setOnClickListener { toggleFullscreen() }
mTextureView!!.surfaceTextureListener = this
mView!!.video_holder.setOnClickListener { toggleFullscreen() }
initTimeHolder()
checkExtendedDetails() checkExtendedDetails()
mExoPlayer = ExoPlayerFactory.newSimpleInstance(context)
mExoPlayer!!.seekParameters = SeekParameters.CLOSEST_SYNC
initExoPlayerListeners()
}
private fun saveVideoProgress() {
if (!videoEnded()) {
mStoredLastVideoPosition = mExoPlayer!!.currentPosition.toInt() / 1000
mStoredLastVideoPath = mMedium.path
}
mConfig.apply {
lastVideoPosition = mStoredLastVideoPosition
lastVideoPath = mStoredLastVideoPath
}
}
private fun setLastVideoSavedPosition() {
if (mStoredLastVideoPath == mMedium.path && mStoredLastVideoPosition > 0) {
setPosition(mStoredLastVideoPosition)
}
}
private fun setupTimeHolder() {
mSeekBar.max = mDuration
mView.video_duration.text = mDuration.getFormattedDuration()
setupTimer()
}
private fun setupTimer() {
activity!!.runOnUiThread(object : Runnable {
override fun run() {
if (mExoPlayer != null && !mIsDragged && mIsPlaying) {
mCurrTime = (mExoPlayer!!.currentPosition / 1000).toInt()
mSeekBar.progress = mCurrTime
mCurrTimeView.text = mCurrTime.getFormattedDuration()
}
mTimerHandler.postDelayed(this, 1000)
}
})
} }
private fun initExoPlayer() { private fun initExoPlayer() {
val isContentUri = medium.path.startsWith("content://") val isContentUri = mMedium.path.startsWith("content://")
val uri = if (isContentUri) Uri.parse(medium.path) else Uri.fromFile(File(medium.path)) val uri = if (isContentUri) Uri.parse(mMedium.path) else Uri.fromFile(File(mMedium.path))
val dataSpec = DataSpec(uri) val dataSpec = DataSpec(uri)
val fileDataSource = if (isContentUri) ContentDataSource(context) else FileDataSource() val fileDataSource = if (isContentUri) ContentDataSource(context) else FileDataSource()
try { try {
@ -229,7 +334,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
val factory = DataSource.Factory { fileDataSource } val factory = DataSource.Factory { fileDataSource }
val audioSource = ExtractorMediaSource(fileDataSource.uri, factory, DefaultExtractorsFactory(), null, null) val audioSource = ExtractorMediaSource(fileDataSource.uri, factory, DefaultExtractorsFactory(), null, null)
mExoPlayer!!.audioStreamType = AudioManager.STREAM_MUSIC mExoPlayer!!.audioStreamType = C.STREAM_TYPE_MUSIC
mExoPlayer!!.prepare(audioSource) mExoPlayer!!.prepare(audioSource)
} }
@ -241,9 +346,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {} override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {}
override fun onPlayerError(error: ExoPlaybackException?) { override fun onPlayerError(error: ExoPlaybackException?) {}
mIsExoPlayerInitialized = false
}
override fun onLoadingChanged(isLoading: Boolean) {} override fun onLoadingChanged(isLoading: Boolean) {}
@ -256,7 +359,6 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {} override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
mIsExoPlayerInitialized = playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
when (playbackState) { when (playbackState) {
Player.STATE_READY -> videoPrepared() Player.STATE_READY -> videoPrepared()
Player.STATE_ENDED -> videoCompleted() Player.STATE_ENDED -> videoCompleted()
@ -264,7 +366,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
} }
}) })
mExoPlayer!!.addVideoListener(object : VideoListener { mExoPlayer!!.addVideoListener(object : SimpleExoPlayer.VideoListener {
override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) { override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
mVideoSize.x = width mVideoSize.x = width
mVideoSize.y = height mVideoSize.y = height
@ -275,108 +377,162 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
}) })
} }
private fun launchVideoPlayer() {
listener?.launchViewVideoIntent(mMedium.path)
}
private fun toggleFullscreen() { private fun toggleFullscreen() {
listener?.fragmentClicked() listener?.fragmentClicked()
} }
private fun initTimeHolder() { private fun checkExtendedDetails() {
val res = resources if (mConfig.showExtendedDetails) {
val left = 0 mView.video_details.apply {
val top = 0 beInvisible() // make it invisible so we can measure it, but not show yet
var right = 0 text = getMediumExtendedDetails(mMedium)
var bottom = 0 onGlobalLayout {
if (isAdded) {
if (hasNavBar()) { val realY = getExtendedDetailsY(height)
if (res.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { if (realY > 0) {
bottom += context!!.navigationBarHeight y = realY
} else { beVisibleIf(text.isNotEmpty())
right += context!!.navigationBarWidth alpha = if (!mConfig.hideExtendedDetails || !mIsFullscreen) 1f else 0f
bottom += context!!.navigationBarHeight }
}
}
} }
} else {
mView.video_details.beGone()
} }
}
if (context!!.config.bottomActions) { private fun initTimeHolder() {
var right = 0
var bottom = context!!.navigationBarHeight
if (mConfig.bottomActions) {
bottom += resources.getDimension(R.dimen.bottom_actions_height).toInt() bottom += resources.getDimension(R.dimen.bottom_actions_height).toInt()
} }
mTimeHolder!!.setPadding(left, top, right, bottom) if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && activity?.hasNavBar() == true) {
right += activity!!.navigationBarWidth
mCurrTimeView = mView!!.video_curr_time
mSeekBar = mView!!.video_seekbar
mSeekBar!!.setOnSeekBarChangeListener(this)
if (mIsFullscreen) {
mTimeHolder!!.beInvisible()
} }
}
private fun hasNavBar(): Boolean { (mTimeHolder.layoutParams as RelativeLayout.LayoutParams).apply {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { bottomMargin = bottom
val display = context!!.windowManager.defaultDisplay rightMargin = right
val realDisplayMetrics = DisplayMetrics()
display.getRealMetrics(realDisplayMetrics)
val realHeight = realDisplayMetrics.heightPixels
val realWidth = realDisplayMetrics.widthPixels
val displayMetrics = DisplayMetrics()
display.getMetrics(displayMetrics)
val displayHeight = displayMetrics.heightPixels
val displayWidth = displayMetrics.widthPixels
realWidth - displayWidth > 0 || realHeight - displayHeight > 0
} else {
val hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey()
val hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)
!hasMenuKey && !hasBackKey
} }
mTimeHolder.beInvisibleIf(mIsFullscreen)
} }
private fun setupTimeHolder() { private fun checkIfPanorama() {
mSeekBar!!.max = mDuration try {
mView!!.video_duration.text = mDuration.getFormattedDuration() val fis = FileInputStream(File(mMedium.path))
setupTimer() fis.use { fis ->
} context!!.parseFileChannel(mMedium.path, fis.channel, 0, 0, 0) {
mIsPanorama = true
private fun setupTimer() {
activity!!.runOnUiThread(object : Runnable {
override fun run() {
if (mExoPlayer != null && !mIsDragged && mIsPlaying) {
mCurrTime = (mExoPlayer!!.currentPosition / 1000).toInt()
mSeekBar!!.progress = mCurrTime
mCurrTimeView!!.text = mCurrTime.getFormattedDuration()
} }
mTimerHandler.postDelayed(this, 1000)
} }
}) } catch (ignored: Exception) {
} catch (ignored: OutOfMemoryError) {
}
} }
override fun onSaveInstanceState(outState: Bundle) { private fun openPanorama() {
super.onSaveInstanceState(outState) Intent(context, PanoramaVideoActivity::class.java).apply {
outState.putInt(PROGRESS, mCurrTime) putExtra(PATH, mMedium.path)
startActivity(this)
}
} }
private fun checkFullscreen() { private fun updateInstantSwitchWidths() {
if (activity == null) { val newWidth = resources.getDimension(R.dimen.instant_change_bar_width) + if (activity?.portrait == false) activity!!.navigationBarWidth else 0
mView.instant_prev_item.layoutParams.width = newWidth.toInt()
mView.instant_next_item.layoutParams.width = newWidth.toInt()
}
override fun fullscreenToggled(isFullscreen: Boolean) {
mIsFullscreen = isFullscreen
val newAlpha = if (isFullscreen) 0f else 1f
if (!mIsFullscreen) {
mTimeHolder.beVisible()
}
mSeekBar.setOnSeekBarChangeListener(if (mIsFullscreen) null else this)
arrayOf(mView.video_curr_time, mView.video_duration).forEach {
it.isClickable = !mIsFullscreen
}
mTimeHolder.animate().alpha(newAlpha).start()
mView.video_details.apply {
if (mStoredShowExtendedDetails && isVisible()) {
animate().y(getExtendedDetailsY(height))
if (mStoredHideExtendedDetails) {
animate().alpha(newAlpha).start()
}
}
}
}
private fun getExtendedDetailsY(height: Int): Float {
val smallMargin = resources.getDimension(R.dimen.small_margin)
val fullscreenOffset = smallMargin + if (mIsFullscreen) 0 else context!!.navigationBarHeight
var actionsHeight = 0f
if (!mIsFullscreen) {
actionsHeight += resources.getDimension(R.dimen.video_player_play_pause_size)
if (mConfig.bottomActions) {
actionsHeight += resources.getDimension(R.dimen.bottom_actions_height)
}
}
return context!!.realScreenSize.y - height - actionsHeight - fullscreenOffset
}
private fun skip(forward: Boolean) {
if (mExoPlayer == null || mIsPanorama) {
return return
} }
var anim = android.R.anim.fade_in val curr = mExoPlayer!!.currentPosition
if (mIsFullscreen) { val twoPercents = Math.max((mExoPlayer!!.duration / 50).toInt(), MIN_SKIP_LENGTH)
anim = android.R.anim.fade_out val newProgress = if (forward) curr + twoPercents else curr - twoPercents
mSeekBar!!.setOnSeekBarChangeListener(null) val roundProgress = Math.round(newProgress / 1000f)
} else { val limitedProgress = Math.max(Math.min(mExoPlayer!!.duration.toInt(), roundProgress), 0)
mSeekBar!!.setOnSeekBarChangeListener(this) setPosition(limitedProgress)
if (!mIsPlaying) {
togglePlayPause()
}
}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (mExoPlayer != null && fromUser) {
setPosition(progress)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
if (mExoPlayer == null)
return
mExoPlayer!!.playWhenReady = false
mIsDragged = true
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
if (mIsPanorama) {
openPanorama()
return
} }
AnimationUtils.loadAnimation(activity, anim).apply { if (mExoPlayer == null)
duration = 150 return
fillAfter = true
mTimeHolder?.startAnimation(this) if (mIsPlaying) {
mExoPlayer!!.playWhenReady = true
} else {
togglePlayPause()
} }
mIsDragged = false
} }
private fun togglePlayPause() { private fun togglePlayPause() {
@ -384,7 +540,6 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
return return
mIsPlaying = !mIsPlaying mIsPlaying = !mIsPlaying
mHidePauseHandler.removeCallbacksAndMessages(null)
if (mIsPlaying) { if (mIsPlaying) {
playVideo() playVideo()
} else { } else {
@ -397,20 +552,39 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
return return
} }
if (mView!!.video_preview.isVisible()) { if (mView.video_preview.isVisible()) {
mView!!.video_preview.beGone() mView.video_preview.beGone()
initExoPlayer() initExoPlayer()
} }
if (videoEnded()) { val wasEnded = videoEnded()
setProgress(0) if (wasEnded) {
setPosition(0)
} }
if (mStoredRememberLastVideoPosition) {
setLastVideoSavedPosition()
clearLastVideoSavedProgress()
}
if (!wasEnded || !mConfig.loopVideos) {
mPlayPauseButton.setImageResource(R.drawable.ic_pause_outline)
}
if (!mWasVideoStarted) {
mView.video_play_outline.beGone()
mPlayPauseButton.beVisible()
}
mWasVideoStarted = true
mIsPlaying = true mIsPlaying = true
mExoPlayer?.playWhenReady = true mExoPlayer?.playWhenReady = true
mView!!.video_play_outline.setImageResource(R.drawable.ic_pause)
activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
schedulePlayPauseFadeOut() }
private fun clearLastVideoSavedProgress() {
mStoredLastVideoPosition = 0
mStoredLastVideoPath = ""
} }
private fun pauseVideo() { private fun pauseVideo() {
@ -423,46 +597,35 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
mExoPlayer?.playWhenReady = false mExoPlayer?.playWhenReady = false
} }
mView?.video_play_outline?.setImageResource(R.drawable.ic_play) mPlayPauseButton.setImageResource(R.drawable.ic_play_outline)
mView?.video_play_outline?.alpha = PLAY_PAUSE_VISIBLE_ALPHA
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
schedulePlayPauseFadeOut()
} }
private fun schedulePlayPauseFadeOut() { private fun videoEnded(): Boolean {
mHidePauseHandler.removeCallbacksAndMessages(null) val currentPos = mExoPlayer?.currentPosition ?: 0
mHidePauseHandler.postDelayed({ val duration = mExoPlayer?.duration ?: 0
mView!!.video_play_outline.animate().alpha(0f).start() return currentPos != 0L && currentPos >= duration
}, HIDE_PAUSE_DELAY)
} }
private fun videoEnded() = mExoPlayer?.currentPosition ?: 0 >= mExoPlayer?.duration ?: 0 private fun setPosition(seconds: Int) {
mExoPlayer?.seekTo(seconds * 1000L)
private fun setProgress(seconds: Int) { mSeekBar.progress = seconds
mExoPlayer!!.seekTo(seconds * 1000L) mCurrTimeView.text = seconds.getFormattedDuration()
mSeekBar!!.progress = seconds
mCurrTimeView!!.text = seconds.getFormattedDuration()
} }
private fun setupVideoDuration() { private fun setupVideoDuration() {
try { mDuration = mMedium.path.getVideoDuration()
val retriever = MediaMetadataRetriever()
retriever.setDataSource(medium.path)
mDuration = Math.round(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toInt() / 1000f)
} catch (ignored: Exception) {
}
setupTimeHolder() setupTimeHolder()
setProgress(0) setPosition(0)
} }
private fun videoPrepared() { private fun videoPrepared() {
if (mDuration == 0) { if (mDuration == 0) {
mDuration = (mExoPlayer!!.duration / 1000).toInt() mDuration = (mExoPlayer!!.duration / 1000).toInt()
setupTimeHolder() setupTimeHolder()
setProgress(mCurrTime) setPosition(mCurrTime)
if (mIsFragmentVisible && (context!!.config.autoplayVideos)) { if (mIsFragmentVisible && (mConfig.autoplayVideos)) {
playVideo() playVideo()
} }
} }
@ -474,23 +637,24 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
} }
mCurrTime = (mExoPlayer!!.duration / 1000).toInt() mCurrTime = (mExoPlayer!!.duration / 1000).toInt()
if (listener?.videoEnded() == false && context!!.config.loopVideos) { if (listener?.videoEnded() == false && mConfig.loopVideos) {
playVideo() playVideo()
} else { } else {
mSeekBar!!.progress = mSeekBar!!.max mSeekBar.progress = mSeekBar.max
mCurrTimeView!!.text = mDuration.getFormattedDuration() mCurrTimeView.text = mDuration.getFormattedDuration()
pauseVideo() pauseVideo()
} }
} }
private fun cleanup() { private fun cleanup() {
pauseVideo() pauseVideo()
mCurrTimeView?.text = 0.getFormattedDuration()
releaseExoPlayer() releaseExoPlayer()
mSeekBar?.progress = 0
mTimerHandler.removeCallbacksAndMessages(null) if (mWasFragmentInit) {
mHidePauseHandler.removeCallbacksAndMessages(null) mCurrTimeView.text = 0.getFormattedDuration()
mTextureView = null mSeekBar.progress = 0
mTimerHandler.removeCallbacksAndMessages(null)
}
} }
private fun releaseExoPlayer() { private fun releaseExoPlayer() {
@ -509,33 +673,28 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) { override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
Thread { Thread {
mExoPlayer?.setVideoSurface(Surface(mTextureView!!.surfaceTexture)) mExoPlayer?.setVideoSurface(Surface(mTextureView.surfaceTexture))
}.start() }.start()
} }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private fun setVideoSize() { private fun setVideoSize() {
if (activity == null || mTextureView == null) if (activity == null || mConfig.openVideosOnSeparateScreen) {
return return
}
val videoProportion = mVideoSize.x.toFloat() / mVideoSize.y.toFloat() val videoProportion = mVideoSize.x.toFloat() / mVideoSize.y.toFloat()
val display = activity!!.windowManager.defaultDisplay val display = activity!!.windowManager.defaultDisplay
val screenWidth: Int val screenWidth: Int
val screenHeight: Int val screenHeight: Int
if (isJellyBean1Plus()) { val realMetrics = DisplayMetrics()
val realMetrics = DisplayMetrics() display.getRealMetrics(realMetrics)
display.getRealMetrics(realMetrics) screenWidth = realMetrics.widthPixels
screenWidth = realMetrics.widthPixels screenHeight = realMetrics.heightPixels
screenHeight = realMetrics.heightPixels
} else {
screenWidth = display.width
screenHeight = display.height
}
val screenProportion = screenWidth.toFloat() / screenHeight.toFloat() val screenProportion = screenWidth.toFloat() / screenHeight.toFloat()
mTextureView!!.layoutParams.apply { mTextureView.layoutParams.apply {
if (videoProportion > screenProportion) { if (videoProportion > screenProportion) {
width = screenWidth width = screenWidth
height = (screenWidth.toFloat() / videoProportion).toInt() height = (screenWidth.toFloat() / videoProportion).toInt()
@ -543,92 +702,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
width = (videoProportion * screenHeight.toFloat()).toInt() width = (videoProportion * screenHeight.toFloat()).toInt()
height = screenHeight height = screenHeight
} }
mTextureView!!.layoutParams = this mTextureView.layoutParams = this
} }
} }
private fun checkExtendedDetails() {
if (context!!.config.showExtendedDetails) {
mView!!.video_details.apply {
beInvisible() // make it invisible so we can measure it, but not show yet
text = getMediumExtendedDetails(medium)
onGlobalLayout {
if (isAdded) {
val realY = getExtendedDetailsY(height)
if (realY > 0) {
y = realY
beVisibleIf(text.isNotEmpty())
alpha = if (!context!!.config.hideExtendedDetails || !mIsFullscreen) 1f else 0f
}
}
}
}
} else {
mView!!.video_details.beGone()
}
}
private fun skip(forward: Boolean) {
if (mExoPlayer == null) {
return
}
val curr = mExoPlayer!!.currentPosition
val twoPercents = Math.max((mExoPlayer!!.duration / 50).toInt(), MIN_SKIP_LENGTH)
val newProgress = if (forward) curr + twoPercents else curr - twoPercents
val roundProgress = Math.round(newProgress / 1000f)
val limitedProgress = Math.max(Math.min(mExoPlayer!!.duration.toInt(), roundProgress), 0)
setProgress(limitedProgress)
if (!mIsPlaying) {
togglePlayPause()
}
}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (mExoPlayer != null && fromUser) {
setProgress(progress)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
if (mExoPlayer == null)
return
mExoPlayer!!.playWhenReady = false
mIsDragged = true
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
if (mExoPlayer == null)
return
if (!mIsPlaying) {
togglePlayPause()
} else {
mExoPlayer!!.playWhenReady = true
}
mIsDragged = false
}
override fun fullscreenToggled(isFullscreen: Boolean) {
mIsFullscreen = isFullscreen
checkFullscreen()
mView!!.video_details.apply {
if (mStoredShowExtendedDetails && isVisible()) {
animate().y(getExtendedDetailsY(height))
if (mStoredHideExtendedDetails) {
animate().alpha(if (isFullscreen) 0f else 1f).start()
}
}
}
}
private fun getExtendedDetailsY(height: Int): Float {
val smallMargin = resources.getDimension(R.dimen.small_margin)
val timeHolderHeight = mTimeHolder!!.height - context!!.navigationBarHeight.toFloat()
val fullscreenOffset = context!!.navigationBarHeight.toFloat() - smallMargin
return context!!.usableScreenSize.y - height + if (mIsFullscreen) fullscreenOffset else -(timeHolderHeight + smallMargin)
}
} }

View file

@ -0,0 +1,119 @@
package com.simplemobiletools.gallery.pro.fragments
import android.provider.MediaStore
import android.view.MotionEvent
import androidx.fragment.app.Fragment
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.models.Medium
import java.io.File
abstract class ViewPagerFragment : Fragment() {
var listener: FragmentListener? = null
protected var mTouchDownTime = 0L
protected var mTouchDownX = 0f
protected var mTouchDownY = 0f
protected var mCloseDownThreshold = 100f
protected var mIgnoreCloseDown = false
abstract fun fullscreenToggled(isFullscreen: Boolean)
interface FragmentListener {
fun fragmentClicked()
fun videoEnded(): Boolean
fun goToPrevItem()
fun goToNextItem()
fun launchViewVideoIntent(path: String)
}
fun getMediumExtendedDetails(medium: Medium): String {
val file = File(medium.path)
if (!file.exists()) {
return ""
}
val path = "${file.parent.trimEnd('/')}/"
val exif = android.media.ExifInterface(medium.path)
val details = StringBuilder()
val detailsFlag = context!!.config.extendedDetails
if (detailsFlag and EXT_NAME != 0) {
medium.name.let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_PATH != 0) {
path.let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_SIZE != 0) {
file.length().formatSize().let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_RESOLUTION != 0) {
file.absolutePath.getResolution()?.formatAsResolution().let { if (it?.isNotEmpty() == true) details.appendln(it) }
}
if (detailsFlag and EXT_LAST_MODIFIED != 0) {
getFileLastModified(file).let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_DATE_TAKEN != 0) {
path.getExifDateTaken(exif).let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_CAMERA_MODEL != 0) {
path.getExifCameraModel(exif).let { if (it.isNotEmpty()) details.appendln(it) }
}
if (detailsFlag and EXT_EXIF_PROPERTIES != 0) {
path.getExifProperties(exif).let { if (it.isNotEmpty()) details.appendln(it) }
}
return details.toString().trim()
}
fun getPathToLoad(medium: Medium) = if (medium.path.startsWith(OTG_PATH)) medium.path.getOTGPublicPath(context!!) else medium.path
private fun getFileLastModified(file: File): String {
val projection = arrayOf(MediaStore.Images.Media.DATE_MODIFIED)
val uri = MediaStore.Files.getContentUri("external")
val selection = "${MediaStore.MediaColumns.DATA} = ?"
val selectionArgs = arrayOf(file.absolutePath)
val cursor = context!!.contentResolver.query(uri, projection, selection, selectionArgs, null)
cursor?.use {
return if (cursor.moveToFirst()) {
val dateModified = cursor.getLongValue(MediaStore.Images.Media.DATE_MODIFIED) * 1000L
dateModified.formatDate()
} else {
file.lastModified().formatDate()
}
}
return ""
}
protected fun handleEvent(event: MotionEvent) {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mTouchDownTime = System.currentTimeMillis()
mTouchDownX = event.x
mTouchDownY = event.y
}
MotionEvent.ACTION_POINTER_DOWN -> mIgnoreCloseDown = true
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
val diffX = mTouchDownX - event.x
val diffY = mTouchDownY - event.y
val downGestureDuration = System.currentTimeMillis() - mTouchDownTime
if (!mIgnoreCloseDown && Math.abs(diffY) > Math.abs(diffX) && diffY < -mCloseDownThreshold && downGestureDuration < MAX_CLOSE_DOWN_GESTURE_DURATION) {
activity?.supportFinishAfterTransition()
}
mIgnoreCloseDown = false
}
}
}
}

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.helpers package com.simplemobiletools.gallery.pro.helpers
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
@ -8,8 +8,8 @@ import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.helpers.BaseConfig import com.simplemobiletools.commons.helpers.BaseConfig
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED
import com.simplemobiletools.commons.helpers.SORT_DESCENDING import com.simplemobiletools.commons.helpers.SORT_DESCENDING
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.models.AlbumCover import com.simplemobiletools.gallery.pro.models.AlbumCover
import java.util.* import java.util.*
class Config(context: Context) : BaseConfig(context) { class Config(context: Context) : BaseConfig(context) {
@ -59,6 +59,22 @@ class Config(context: Context) : BaseConfig(context) {
fun hasCustomGrouping(path: String) = prefs.contains(GROUP_FOLDER_PREFIX + path.toLowerCase()) fun hasCustomGrouping(path: String) = prefs.contains(GROUP_FOLDER_PREFIX + path.toLowerCase())
fun saveFolderViewType(path: String, value: Int) {
if (path.isEmpty()) {
viewTypeFiles = value
} else {
prefs.edit().putInt(VIEW_TYPE_PREFIX + path.toLowerCase(), value).apply()
}
}
fun getFolderViewType(path: String) = prefs.getInt(VIEW_TYPE_PREFIX + path.toLowerCase(), viewTypeFiles)
fun removeFolderViewType(path: String) {
prefs.edit().remove(VIEW_TYPE_PREFIX + path.toLowerCase()).apply()
}
fun hasCustomViewType(path: String) = prefs.contains(VIEW_TYPE_PREFIX + path.toLowerCase())
var wasHideFolderTooltipShown: Boolean var wasHideFolderTooltipShown: Boolean
get() = prefs.getBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, false) get() = prefs.getBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, false)
set(wasShown) = prefs.edit().putBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, wasShown).apply() set(wasShown) = prefs.edit().putBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, wasShown).apply()
@ -88,7 +104,10 @@ class Config(context: Context) : BaseConfig(context) {
fun addPinnedFolders(paths: Set<String>) { fun addPinnedFolders(paths: Set<String>) {
val currPinnedFolders = HashSet<String>(pinnedFolders) val currPinnedFolders = HashSet<String>(pinnedFolders)
currPinnedFolders.addAll(paths) currPinnedFolders.addAll(paths)
pinnedFolders = currPinnedFolders pinnedFolders = currPinnedFolders.filter { it.isNotEmpty() }.toHashSet()
if (paths.contains(RECYCLE_BIN)) {
showRecycleBinLast = false
}
} }
fun removePinnedFolders(paths: Set<String>) { fun removePinnedFolders(paths: Set<String>) {
@ -104,7 +123,7 @@ class Config(context: Context) : BaseConfig(context) {
fun addExcludedFolders(paths: Set<String>) { fun addExcludedFolders(paths: Set<String>) {
val currExcludedFolders = HashSet<String>(excludedFolders) val currExcludedFolders = HashSet<String>(excludedFolders)
currExcludedFolders.addAll(paths) currExcludedFolders.addAll(paths)
excludedFolders = currExcludedFolders excludedFolders = currExcludedFolders.filter { it.isNotEmpty() }.toHashSet()
} }
fun removeExcludedFolder(path: String) { fun removeExcludedFolder(path: String) {
@ -114,23 +133,21 @@ class Config(context: Context) : BaseConfig(context) {
} }
var excludedFolders: MutableSet<String> var excludedFolders: MutableSet<String>
get() = prefs.getStringSet(EXCLUDED_FOLDERS, getDataFolder()) get() = prefs.getStringSet(EXCLUDED_FOLDERS, HashSet())
set(excludedFolders) = prefs.edit().remove(EXCLUDED_FOLDERS).putStringSet(EXCLUDED_FOLDERS, excludedFolders).apply() set(excludedFolders) = prefs.edit().remove(EXCLUDED_FOLDERS).putStringSet(EXCLUDED_FOLDERS, excludedFolders).apply()
private fun getDataFolder(): Set<String> {
val folders = HashSet<String>()
val dataFolder = context.externalCacheDir?.parentFile?.parent?.trimEnd('/') ?: ""
if (dataFolder.endsWith("data"))
folders.add(dataFolder)
return folders
}
fun addIncludedFolder(path: String) { fun addIncludedFolder(path: String) {
val currIncludedFolders = HashSet<String>(includedFolders) val currIncludedFolders = HashSet<String>(includedFolders)
currIncludedFolders.add(path) currIncludedFolders.add(path)
includedFolders = currIncludedFolders includedFolders = currIncludedFolders
} }
fun addIncludedFolders(paths: Set<String>) {
val currIncludedFolders = HashSet<String>(includedFolders)
currIncludedFolders.addAll(paths)
includedFolders = currIncludedFolders.filter { it.isNotEmpty() }.toHashSet()
}
fun removeIncludedFolder(path: String) { fun removeIncludedFolder(path: String) {
val currIncludedFolders = HashSet<String>(includedFolders) val currIncludedFolders = HashSet<String>(includedFolders)
currIncludedFolders.remove(path) currIncludedFolders.remove(path)
@ -143,7 +160,7 @@ class Config(context: Context) : BaseConfig(context) {
var autoplayVideos: Boolean var autoplayVideos: Boolean
get() = prefs.getBoolean(AUTOPLAY_VIDEOS, false) get() = prefs.getBoolean(AUTOPLAY_VIDEOS, false)
set(autoplay) = prefs.edit().putBoolean(AUTOPLAY_VIDEOS, autoplay).apply() set(autoplayVideos) = prefs.edit().putBoolean(AUTOPLAY_VIDEOS, autoplayVideos).apply()
var animateGifs: Boolean var animateGifs: Boolean
get() = prefs.getBoolean(ANIMATE_GIFS, false) get() = prefs.getBoolean(ANIMATE_GIFS, false)
@ -157,6 +174,10 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getBoolean(CROP_THUMBNAILS, true) get() = prefs.getBoolean(CROP_THUMBNAILS, true)
set(cropThumbnails) = prefs.edit().putBoolean(CROP_THUMBNAILS, cropThumbnails).apply() set(cropThumbnails) = prefs.edit().putBoolean(CROP_THUMBNAILS, cropThumbnails).apply()
var showThumbnailVideoDuration: Boolean
get() = prefs.getBoolean(SHOW_THUMBNAIL_VIDEO_DURATION, false)
set(showThumbnailVideoDuration) = prefs.edit().putBoolean(SHOW_THUMBNAIL_VIDEO_DURATION, showThumbnailVideoDuration).apply()
var screenRotation: Int var screenRotation: Int
get() = prefs.getInt(SCREEN_ROTATION, ROTATE_BY_SYSTEM_SETTING) get() = prefs.getInt(SCREEN_ROTATION, ROTATE_BY_SYSTEM_SETTING)
set(screenRotation) = prefs.edit().putInt(SCREEN_ROTATION, screenRotation).apply() set(screenRotation) = prefs.edit().putInt(SCREEN_ROTATION, screenRotation).apply()
@ -165,13 +186,17 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getBoolean(LOOP_VIDEOS, false) get() = prefs.getBoolean(LOOP_VIDEOS, false)
set(loop) = prefs.edit().putBoolean(LOOP_VIDEOS, loop).apply() set(loop) = prefs.edit().putBoolean(LOOP_VIDEOS, loop).apply()
var openVideosOnSeparateScreen: Boolean
get() = prefs.getBoolean(OPEN_VIDEOS_ON_SEPARATE_SCREEN, false)
set(openVideosOnSeparateScreen) = prefs.edit().putBoolean(OPEN_VIDEOS_ON_SEPARATE_SCREEN, openVideosOnSeparateScreen).apply()
var displayFileNames: Boolean var displayFileNames: Boolean
get() = prefs.getBoolean(DISPLAY_FILE_NAMES, false) get() = prefs.getBoolean(DISPLAY_FILE_NAMES, false)
set(display) = prefs.edit().putBoolean(DISPLAY_FILE_NAMES, display).apply() set(display) = prefs.edit().putBoolean(DISPLAY_FILE_NAMES, display).apply()
var blackBackground: Boolean var blackBackground: Boolean
get() = prefs.getBoolean(DARK_BACKGROUND, false) get() = prefs.getBoolean(BLACK_BACKGROUND, false)
set(darkBackground) = prefs.edit().putBoolean(DARK_BACKGROUND, darkBackground).apply() set(blackBackground) = prefs.edit().putBoolean(BLACK_BACKGROUND, blackBackground).apply()
var filterMedia: Int var filterMedia: Int
get() = prefs.getInt(FILTER_MEDIA, TYPE_IMAGES or TYPE_VIDEOS or TYPE_GIFS or TYPE_RAWS or TYPE_SVGS) get() = prefs.getInt(FILTER_MEDIA, TYPE_IMAGES or TYPE_VIDEOS or TYPE_GIFS or TYPE_RAWS or TYPE_SVGS)
@ -181,10 +206,6 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getInt(getDirectoryColumnsField(), getDefaultDirectoryColumnCount()) get() = prefs.getInt(getDirectoryColumnsField(), getDefaultDirectoryColumnCount())
set(dirColumnCnt) = prefs.edit().putInt(getDirectoryColumnsField(), dirColumnCnt).apply() set(dirColumnCnt) = prefs.edit().putInt(getDirectoryColumnsField(), dirColumnCnt).apply()
var oneFingerZoom: Boolean
get() = prefs.getBoolean(ONE_FINGER_ZOOM, false)
set(oneFingerZoom) = prefs.edit().putBoolean(ONE_FINGER_ZOOM, oneFingerZoom).apply()
var allowInstantChange: Boolean var allowInstantChange: Boolean
get() = prefs.getBoolean(ALLOW_INSTANT_CHANGE, false) get() = prefs.getBoolean(ALLOW_INSTANT_CHANGE, false)
set(allowInstantChange) = prefs.edit().putBoolean(ALLOW_INSTANT_CHANGE, allowInstantChange).apply() set(allowInstantChange) = prefs.edit().putBoolean(ALLOW_INSTANT_CHANGE, allowInstantChange).apply()
@ -266,10 +287,6 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getInt(SLIDESHOW_INTERVAL, SLIDESHOW_DEFAULT_INTERVAL) get() = prefs.getInt(SLIDESHOW_INTERVAL, SLIDESHOW_DEFAULT_INTERVAL)
set(slideshowInterval) = prefs.edit().putInt(SLIDESHOW_INTERVAL, slideshowInterval).apply() set(slideshowInterval) = prefs.edit().putInt(SLIDESHOW_INTERVAL, slideshowInterval).apply()
var slideshowIncludePhotos: Boolean
get() = prefs.getBoolean(SLIDESHOW_INCLUDE_PHOTOS, true)
set(slideshowIncludePhotos) = prefs.edit().putBoolean(SLIDESHOW_INCLUDE_PHOTOS, slideshowIncludePhotos).apply()
var slideshowIncludeVideos: Boolean var slideshowIncludeVideos: Boolean
get() = prefs.getBoolean(SLIDESHOW_INCLUDE_VIDEOS, false) get() = prefs.getBoolean(SLIDESHOW_INCLUDE_VIDEOS, false)
set(slideshowIncludeVideos) = prefs.edit().putBoolean(SLIDESHOW_INCLUDE_VIDEOS, slideshowIncludeVideos).apply() set(slideshowIncludeVideos) = prefs.edit().putBoolean(SLIDESHOW_INCLUDE_VIDEOS, slideshowIncludeVideos).apply()
@ -362,6 +379,18 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getBoolean(BOTTOM_ACTIONS, true) get() = prefs.getBoolean(BOTTOM_ACTIONS, true)
set(bottomActions) = prefs.edit().putBoolean(BOTTOM_ACTIONS, bottomActions).apply() set(bottomActions) = prefs.edit().putBoolean(BOTTOM_ACTIONS, bottomActions).apply()
var rememberLastVideoPosition: Boolean
get() = prefs.getBoolean(REMEMBER_LAST_VIDEO_POSITION, false)
set(rememberLastVideoPosition) = prefs.edit().putBoolean(REMEMBER_LAST_VIDEO_POSITION, rememberLastVideoPosition).apply()
var lastVideoPath: String
get() = prefs.getString(LAST_VIDEO_PATH, "")
set(lastVideoPath) = prefs.edit().putString(LAST_VIDEO_PATH, lastVideoPath).apply()
var lastVideoPosition: Int
get() = prefs.getInt(LAST_VIDEO_POSITION, 0)
set(lastVideoPosition) = prefs.edit().putInt(LAST_VIDEO_POSITION, lastVideoPosition).apply()
var visibleBottomActions: Int var visibleBottomActions: Int
get() = prefs.getInt(VISIBLE_BOTTOM_ACTIONS, DEFAULT_BOTTOM_ACTIONS) get() = prefs.getInt(VISIBLE_BOTTOM_ACTIONS, DEFAULT_BOTTOM_ACTIONS)
set(visibleBottomActions) = prefs.edit().putInt(VISIBLE_BOTTOM_ACTIONS, visibleBottomActions).apply() set(visibleBottomActions) = prefs.edit().putInt(VISIBLE_BOTTOM_ACTIONS, visibleBottomActions).apply()
@ -371,10 +400,12 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getStringSet(EVER_SHOWN_FOLDERS, getEverShownFolders()) get() = prefs.getStringSet(EVER_SHOWN_FOLDERS, getEverShownFolders())
set(everShownFolders) = prefs.edit().putStringSet(EVER_SHOWN_FOLDERS, everShownFolders).apply() set(everShownFolders) = prefs.edit().putStringSet(EVER_SHOWN_FOLDERS, everShownFolders).apply()
fun getEverShownFolders() = hashSetOf( private fun getEverShownFolders() = hashSetOf(
internalStoragePath, internalStoragePath,
Environment.DIRECTORY_DCIM, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath,
Environment.DIRECTORY_PICTURES Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath,
"${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath}/Screenshots"
) )
var showRecycleBinAtFolders: Boolean var showRecycleBinAtFolders: Boolean
@ -388,4 +419,52 @@ class Config(context: Context) : BaseConfig(context) {
var lastBinCheck: Long var lastBinCheck: Long
get() = prefs.getLong(LAST_BIN_CHECK, 0L) get() = prefs.getLong(LAST_BIN_CHECK, 0L)
set(lastBinCheck) = prefs.edit().putLong(LAST_BIN_CHECK, lastBinCheck).apply() set(lastBinCheck) = prefs.edit().putLong(LAST_BIN_CHECK, lastBinCheck).apply()
var showHighestQuality: Boolean
get() = prefs.getBoolean(SHOW_HIGHEST_QUALITY, false)
set(showHighestQuality) = prefs.edit().putBoolean(SHOW_HIGHEST_QUALITY, showHighestQuality).apply()
var showRecycleBinLast: Boolean
get() = prefs.getBoolean(SHOW_RECYCLE_BIN_LAST, false)
set(showRecycleBinLast) = prefs.edit().putBoolean(SHOW_RECYCLE_BIN_LAST, showRecycleBinLast).apply()
var allowDownGesture: Boolean
get() = prefs.getBoolean(ALLOW_DOWN_GESTURE, true)
set(allowDownGesture) = prefs.edit().putBoolean(ALLOW_DOWN_GESTURE, allowDownGesture).apply()
var lastEditorCropAspectRatio: Int
get() = prefs.getInt(LAST_EDITOR_CROP_ASPECT_RATIO, ASPECT_RATIO_FREE)
set(lastEditorCropAspectRatio) = prefs.edit().putInt(LAST_EDITOR_CROP_ASPECT_RATIO, lastEditorCropAspectRatio).apply()
var lastEditorCropOtherAspectRatioX: Int
get() = prefs.getInt(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X, 2)
set(lastEditorCropOtherAspectRatioX) = prefs.edit().putInt(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X, lastEditorCropOtherAspectRatioX).apply()
var lastEditorCropOtherAspectRatioY: Int
get() = prefs.getInt(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y, 1)
set(lastEditorCropOtherAspectRatioY) = prefs.edit().putInt(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y, lastEditorCropOtherAspectRatioY).apply()
var groupDirectSubfolders: Boolean
get() = prefs.getBoolean(GROUP_DIRECT_SUBFOLDERS, false)
set(groupDirectSubfolders) = prefs.edit().putBoolean(GROUP_DIRECT_SUBFOLDERS, groupDirectSubfolders).apply()
var showWidgetFolderName: Boolean
get() = prefs.getBoolean(SHOW_WIDGET_FOLDER_NAME, true)
set(showWidgetFolderName) = prefs.edit().putBoolean(SHOW_WIDGET_FOLDER_NAME, showWidgetFolderName).apply()
var allowOneToOneZoom: Boolean
get() = prefs.getBoolean(ALLOW_ONE_TO_ONE_ZOOM, false)
set(allowOneToOneZoom) = prefs.edit().putBoolean(ALLOW_ONE_TO_ONE_ZOOM, allowOneToOneZoom).apply()
var lastEditorDrawColor: Int
get() = prefs.getInt(LAST_EDITOR_DRAW_COLOR, primaryColor)
set(lastEditorDrawColor) = prefs.edit().putInt(LAST_EDITOR_DRAW_COLOR, lastEditorDrawColor).apply()
var lastEditorBrushSize: Int
get() = prefs.getInt(LAST_EDITOR_BRUSH_SIZE, 50)
set(lastEditorBrushSize) = prefs.edit().putInt(LAST_EDITOR_BRUSH_SIZE, lastEditorBrushSize).apply()
var showNotch: Boolean
get() = prefs.getBoolean(SHOW_NOTCH, true)
set(showNotch) = prefs.edit().putBoolean(SHOW_NOTCH, showNotch).apply()
} }

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.helpers package com.simplemobiletools.gallery.pro.helpers
import com.simplemobiletools.commons.helpers.MONTH_SECONDS import com.simplemobiletools.commons.helpers.MONTH_SECONDS
@ -6,17 +6,21 @@ import com.simplemobiletools.commons.helpers.MONTH_SECONDS
const val DIRECTORY_SORT_ORDER = "directory_sort_order" const val DIRECTORY_SORT_ORDER = "directory_sort_order"
const val SORT_FOLDER_PREFIX = "sort_folder_" const val SORT_FOLDER_PREFIX = "sort_folder_"
const val GROUP_FOLDER_PREFIX = "group_folder_" const val GROUP_FOLDER_PREFIX = "group_folder_"
const val VIEW_TYPE_PREFIX = "view_type_folder_"
const val SHOW_HIDDEN_MEDIA = "show_hidden_media" const val SHOW_HIDDEN_MEDIA = "show_hidden_media"
const val TEMPORARILY_SHOW_HIDDEN = "temporarily_show_hidden" const val TEMPORARILY_SHOW_HIDDEN = "temporarily_show_hidden"
const val IS_THIRD_PARTY_INTENT = "is_third_party_intent" const val IS_THIRD_PARTY_INTENT = "is_third_party_intent"
const val AUTOPLAY_VIDEOS = "autoplay_videos" const val AUTOPLAY_VIDEOS = "autoplay_videos"
const val REMEMBER_LAST_VIDEO_POSITION = "remember_last_video_position"
const val LOOP_VIDEOS = "loop_videos" const val LOOP_VIDEOS = "loop_videos"
const val OPEN_VIDEOS_ON_SEPARATE_SCREEN = "open_videos_on_separate_screen"
const val ANIMATE_GIFS = "animate_gifs" const val ANIMATE_GIFS = "animate_gifs"
const val MAX_BRIGHTNESS = "max_brightness" const val MAX_BRIGHTNESS = "max_brightness"
const val CROP_THUMBNAILS = "crop_thumbnails" const val CROP_THUMBNAILS = "crop_thumbnails"
const val SHOW_THUMBNAIL_VIDEO_DURATION = "show_thumbnail_video_duration"
const val SCREEN_ROTATION = "screen_rotation" const val SCREEN_ROTATION = "screen_rotation"
const val DISPLAY_FILE_NAMES = "display_file_names" const val DISPLAY_FILE_NAMES = "display_file_names"
const val DARK_BACKGROUND = "dark_background" const val BLACK_BACKGROUND = "dark_background"
const val PINNED_FOLDERS = "pinned_folders" const val PINNED_FOLDERS = "pinned_folders"
const val FILTER_MEDIA = "filter_media" const val FILTER_MEDIA = "filter_media"
const val DIR_COLUMN_CNT = "dir_column_cnt" const val DIR_COLUMN_CNT = "dir_column_cnt"
@ -43,7 +47,6 @@ const val VIEW_TYPE_FILES = "view_type_files"
const val SHOW_EXTENDED_DETAILS = "show_extended_details" const val SHOW_EXTENDED_DETAILS = "show_extended_details"
const val EXTENDED_DETAILS = "extended_details" const val EXTENDED_DETAILS = "extended_details"
const val HIDE_EXTENDED_DETAILS = "hide_extended_details" const val HIDE_EXTENDED_DETAILS = "hide_extended_details"
const val ONE_FINGER_ZOOM = "one_finger_zoom"
const val ALLOW_INSTANT_CHANGE = "allow_instant_change" const val ALLOW_INSTANT_CHANGE = "allow_instant_change"
const val DO_EXTRA_CHECK = "do_extra_check" const val DO_EXTRA_CHECK = "do_extra_check"
const val WAS_NEW_APP_SHOWN = "was_new_app_shown_clock" const val WAS_NEW_APP_SHOWN = "was_new_app_shown_clock"
@ -51,6 +54,8 @@ const val LAST_FILEPICKER_PATH = "last_filepicker_path"
const val WAS_OTG_HANDLED = "was_otg_handled" const val WAS_OTG_HANDLED = "was_otg_handled"
const val TEMP_SKIP_DELETE_CONFIRMATION = "temp_skip_delete_confirmation" const val TEMP_SKIP_DELETE_CONFIRMATION = "temp_skip_delete_confirmation"
const val BOTTOM_ACTIONS = "bottom_actions" const val BOTTOM_ACTIONS = "bottom_actions"
const val LAST_VIDEO_PATH = "last_video_path"
const val LAST_VIDEO_POSITION = "last_video_position"
const val VISIBLE_BOTTOM_ACTIONS = "visible_bottom_actions" const val VISIBLE_BOTTOM_ACTIONS = "visible_bottom_actions"
const val WERE_FAVORITES_PINNED = "were_favorites_pinned" const val WERE_FAVORITES_PINNED = "were_favorites_pinned"
const val WAS_RECYCLE_BIN_PINNED = "was_recycle_bin_pinned" const val WAS_RECYCLE_BIN_PINNED = "was_recycle_bin_pinned"
@ -58,13 +63,24 @@ const val USE_RECYCLE_BIN = "use_recycle_bin"
const val GROUP_BY = "group_by" const val GROUP_BY = "group_by"
const val EVER_SHOWN_FOLDERS = "ever_shown_folders" const val EVER_SHOWN_FOLDERS = "ever_shown_folders"
const val SHOW_RECYCLE_BIN_AT_FOLDERS = "show_recycle_bin_at_folders" const val SHOW_RECYCLE_BIN_AT_FOLDERS = "show_recycle_bin_at_folders"
const val SHOW_RECYCLE_BIN_LAST = "show_recycle_bin_last"
const val ALLOW_ZOOMING_IMAGES = "allow_zooming_images" const val ALLOW_ZOOMING_IMAGES = "allow_zooming_images"
const val WAS_SVG_SHOWING_HANDLED = "was_svg_showing_handled" const val WAS_SVG_SHOWING_HANDLED = "was_svg_showing_handled"
const val LAST_BIN_CHECK = "last_bin_check" const val LAST_BIN_CHECK = "last_bin_check"
const val SHOW_HIGHEST_QUALITY = "show_highest_quality"
const val ALLOW_DOWN_GESTURE = "allow_down_gesture"
const val LAST_EDITOR_CROP_ASPECT_RATIO = "last_editor_crop_aspect_ratio"
const val LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X = "last_editor_crop_other_aspect_ratio_x"
const val LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y = "last_editor_crop_other_aspect_ratio_y"
const val GROUP_DIRECT_SUBFOLDERS = "group_direct_subfolders"
const val SHOW_WIDGET_FOLDER_NAME = "show_widget_folder_name"
const val ALLOW_ONE_TO_ONE_ZOOM = "allow_one_to_one_zoom"
const val LAST_EDITOR_DRAW_COLOR = "last_editor_draw_color"
const val LAST_EDITOR_BRUSH_SIZE = "last_editor_brush_size"
const val SHOW_NOTCH = "show_notch"
// slideshow // slideshow
const val SLIDESHOW_INTERVAL = "slideshow_interval" const val SLIDESHOW_INTERVAL = "slideshow_interval"
const val SLIDESHOW_INCLUDE_PHOTOS = "slideshow_include_photos"
const val SLIDESHOW_INCLUDE_VIDEOS = "slideshow_include_videos" const val SLIDESHOW_INCLUDE_VIDEOS = "slideshow_include_videos"
const val SLIDESHOW_INCLUDE_GIFS = "slideshow_include_gifs" const val SLIDESHOW_INCLUDE_GIFS = "slideshow_include_gifs"
const val SLIDESHOW_RANDOM_ORDER = "slideshow_random_order" const val SLIDESHOW_RANDOM_ORDER = "slideshow_random_order"
@ -73,17 +89,26 @@ const val SLIDESHOW_MOVE_BACKWARDS = "slideshow_move_backwards"
const val SLIDESHOW_LOOP = "loop_slideshow" const val SLIDESHOW_LOOP = "loop_slideshow"
const val SLIDESHOW_DEFAULT_INTERVAL = 5 const val SLIDESHOW_DEFAULT_INTERVAL = 5
const val SLIDESHOW_SCROLL_DURATION = 500L const val SLIDESHOW_SCROLL_DURATION = 500L
const val SLIDESHOW_START_ON_ENTER = "slideshow_start_on_enter"
const val NOMEDIA = ".nomedia" const val NOMEDIA = ".nomedia"
const val FAVORITES = "favorites" const val FAVORITES = "favorites"
const val RECYCLE_BIN = "recycle_bin" const val RECYCLE_BIN = "recycle_bin"
const val SHOW_FAVORITES = "show_favorites" const val SHOW_FAVORITES = "show_favorites"
const val SHOW_RECYCLE_BIN = "show_recycle_bin" const val SHOW_RECYCLE_BIN = "show_recycle_bin"
const val SHOW_NEXT_ITEM = "show_next_item"
const val SHOW_PREV_ITEM = "show_prev_item"
const val GO_TO_NEXT_ITEM = "go_to_next_item"
const val GO_TO_PREV_ITEM = "go_to_prev_item"
const val MAX_COLUMN_COUNT = 20 const val MAX_COLUMN_COUNT = 20
const val SHOW_TEMP_HIDDEN_DURATION = 300000L const val SHOW_TEMP_HIDDEN_DURATION = 300000L
const val CLICK_MAX_DURATION = 150 const val CLICK_MAX_DURATION = 150
const val CLICK_MAX_DISTANCE = 100
const val MAX_CLOSE_DOWN_GESTURE_DURATION = 300
const val DRAG_THRESHOLD = 8 const val DRAG_THRESHOLD = 8
const val MONTH_MILLISECONDS = MONTH_SECONDS * 1000L const val MONTH_MILLISECONDS = MONTH_SECONDS * 1000L
const val MIN_SKIP_LENGTH = 2000
const val HIDE_SYSTEM_UI_DELAY = 500L
const val DIRECTORY = "directory" const val DIRECTORY = "directory"
const val MEDIUM = "medium" const val MEDIUM = "medium"
@ -149,5 +174,20 @@ const val BOTTOM_ACTION_SHOW_ON_MAP = 256
const val BOTTOM_ACTION_TOGGLE_VISIBILITY = 512 const val BOTTOM_ACTION_TOGGLE_VISIBILITY = 512
const val BOTTOM_ACTION_RENAME = 1024 const val BOTTOM_ACTION_RENAME = 1024
const val BOTTOM_ACTION_SET_AS = 2048 const val BOTTOM_ACTION_SET_AS = 2048
const val BOTTOM_ACTION_COPY = 4096
const val DEFAULT_BOTTOM_ACTIONS = BOTTOM_ACTION_TOGGLE_FAVORITE or BOTTOM_ACTION_EDIT or BOTTOM_ACTION_SHARE or BOTTOM_ACTION_DELETE const val DEFAULT_BOTTOM_ACTIONS = BOTTOM_ACTION_TOGGLE_FAVORITE or BOTTOM_ACTION_EDIT or BOTTOM_ACTION_SHARE or BOTTOM_ACTION_DELETE
// aspect ratios used at the editor for cropping
const val ASPECT_RATIO_FREE = 0
const val ASPECT_RATIO_ONE_ONE = 1
const val ASPECT_RATIO_FOUR_THREE = 2
const val ASPECT_RATIO_SIXTEEN_NINE = 3
const val ASPECT_RATIO_OTHER = 4
// some constants related to zooming videos
const val MIN_VIDEO_ZOOM_SCALE = 1f
const val MAX_VIDEO_ZOOM_SCALE = 5f
const val ZOOM_MODE_NONE = 0
const val ZOOM_MODE_DRAG = 1
const val ZOOM_MODE_ZOOM = 2

View file

@ -1,7 +1,7 @@
package com.simplemobiletools.gallery.helpers package com.simplemobiletools.gallery.pro.helpers
import android.graphics.Bitmap import android.graphics.Bitmap
import com.simplemobiletools.gallery.models.FilterItem import com.simplemobiletools.gallery.pro.models.FilterItem
import java.util.* import java.util.*
class FilterThumbnailsManager { class FilterThumbnailsManager {

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.helpers package com.simplemobiletools.gallery.pro.helpers
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Matrix import android.graphics.Matrix
@ -8,8 +8,9 @@ import java.security.MessageDigest
class GlideRotateTransformation(val rotateRotationAngle: Int) : BitmapTransformation() { class GlideRotateTransformation(val rotateRotationAngle: Int) : BitmapTransformation() {
override fun transform(pool: BitmapPool, bitmap: Bitmap, outWidth: Int, outHeight: Int): Bitmap { override fun transform(pool: BitmapPool, bitmap: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
if (rotateRotationAngle % 360 == 0) if (rotateRotationAngle % 360 == 0) {
return bitmap return bitmap
}
val matrix = Matrix() val matrix = Matrix()
matrix.postRotate(rotateRotationAngle.toFloat()) matrix.postRotate(rotateRotationAngle.toFloat())

View file

@ -0,0 +1,27 @@
package com.simplemobiletools.gallery.pro.helpers
import java.io.UnsupportedEncodingException
import java.nio.ByteBuffer
import java.nio.charset.Charset
// file taken from the https://github.com/sannies/mp4parser project, used at determining if a video is a panoramic one
object IsoTypeReader {
fun readUInt32(bb: ByteBuffer): Long {
var i = bb.int.toLong()
if (i < 0) {
i += 1L shl 32
}
return i
}
fun read4cc(bb: ByteBuffer): String? {
val codeBytes = ByteArray(4)
bb.get(codeBytes)
return try {
String(codeBytes, Charset.forName("ISO-8859-1"))
} catch (e: UnsupportedEncodingException) {
null
}
}
}

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.helpers package com.simplemobiletools.gallery.pro.helpers
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
@ -7,33 +7,39 @@ import android.provider.MediaStore
import android.text.format.DateFormat import android.text.format.DateFormat
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import com.simplemobiletools.gallery.models.ThumbnailSection import com.simplemobiletools.gallery.pro.models.ThumbnailSection
import java.io.File import java.io.File
import java.util.* import java.util.*
class MediaFetcher(val context: Context) { class MediaFetcher(val context: Context) {
var shouldStop = false var shouldStop = false
fun getFilesFrom(curPath: String, isPickImage: Boolean, isPickVideo: Boolean, getProperDateTaken: Boolean, favoritePaths: ArrayList<String>): ArrayList<Medium> { fun getFilesFrom(curPath: String, isPickImage: Boolean, isPickVideo: Boolean, getProperDateTaken: Boolean, favoritePaths: ArrayList<String>,
getVideoDurations: Boolean, sortMedia: Boolean = true): ArrayList<Medium> {
val filterMedia = context.config.filterMedia val filterMedia = context.config.filterMedia
if (filterMedia == 0) { if (filterMedia == 0) {
return ArrayList() return ArrayList()
} }
val curMedia = ArrayList<Medium>() val curMedia = ArrayList<Medium>()
if (curPath.startsWith(OTG_PATH)) { if (curPath.startsWith(OTG_PATH, true)) {
val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia, favoritePaths) if (context.hasOTGConnected()) {
curMedia.addAll(newMedia) val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia, favoritePaths, getVideoDurations)
curMedia.addAll(newMedia)
}
} else { } else {
val newMedia = getMediaInFolder(curPath, isPickImage, isPickVideo, filterMedia, getProperDateTaken, favoritePaths) val newMedia = getMediaInFolder(curPath, isPickImage, isPickVideo, filterMedia, getProperDateTaken, favoritePaths, getVideoDurations)
curMedia.addAll(newMedia) curMedia.addAll(newMedia)
} }
sortMedia(curMedia, context.config.getFileSorting(curPath)) if (sortMedia) {
sortMedia(curMedia, context.config.getFileSorting(curPath))
}
return curMedia return curMedia
} }
@ -122,7 +128,7 @@ class MediaFetcher(val context: Context) {
val foldersToIgnore = arrayListOf("/storage/emulated/legacy") val foldersToIgnore = arrayListOf("/storage/emulated/legacy")
val config = context.config val config = context.config
val includedFolders = config.includedFolders val includedFolders = config.includedFolders
var foldersToScan = config.everShownFolders.toMutableList() as ArrayList var foldersToScan = config.everShownFolders.filter { it == FAVORITES || it == RECYCLE_BIN || context.getDoesFilePathExist(it) }.toMutableList() as ArrayList
cursor.use { cursor.use {
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
@ -167,7 +173,7 @@ class MediaFetcher(val context: Context) {
} }
private fun getMediaInFolder(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int, getProperDateTaken: Boolean, private fun getMediaInFolder(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int, getProperDateTaken: Boolean,
favoritePaths: ArrayList<String>): ArrayList<Medium> { favoritePaths: ArrayList<String>, getVideoDurations: Boolean): ArrayList<Medium> {
val media = ArrayList<Medium>() val media = ArrayList<Medium>()
val deletedMedia = if (folder == RECYCLE_BIN) { val deletedMedia = if (folder == RECYCLE_BIN) {
@ -176,27 +182,27 @@ class MediaFetcher(val context: Context) {
ArrayList() ArrayList()
} }
val doExtraCheck = context.config.doExtraCheck
val showHidden = context.config.shouldShowHidden
val dateTakens = if (getProperDateTaken && folder != FAVORITES && folder != RECYCLE_BIN) getFolderDateTakens(folder) else HashMap()
val files = when (folder) { val files = when (folder) {
FAVORITES -> favoritePaths.map { File(it) }.toTypedArray() FAVORITES -> favoritePaths.filter { showHidden || !it.contains("/.") }.map { File(it) }.toTypedArray()
RECYCLE_BIN -> deletedMedia.map { File(it.path) }.toTypedArray() RECYCLE_BIN -> deletedMedia.map { File(it.path) }.toTypedArray()
else -> File(folder).listFiles() ?: return media else -> File(folder).listFiles() ?: return media
} }
val doExtraCheck = context.config.doExtraCheck
val showHidden = context.config.shouldShowHidden
val dateTakens = if (getProperDateTaken) getFolderDateTakens(folder) else HashMap()
for (file in files) { for (file in files) {
if (shouldStop) { if (shouldStop) {
break break
} }
val filename = file.name val path = file.absolutePath
val isImage = filename.isImageFast() val isImage = path.isImageFast()
val isVideo = if (isImage) false else filename.isVideoFast() val isVideo = if (isImage) false else path.isVideoFast()
val isGif = if (isImage || isVideo) false else filename.isGif() val isGif = if (isImage || isVideo) false else path.isGif()
val isRaw = if (isImage || isVideo || isGif) false else filename.isRawFast() val isRaw = if (isImage || isVideo || isGif) false else path.isRawFast()
val isSvg = if (isImage || isVideo || isGif || isRaw) false else filename.isSvg() val isSvg = if (isImage || isVideo || isGif || isRaw) false else path.isSvg()
if (!isImage && !isVideo && !isGif && !isRaw && !isSvg) if (!isImage && !isVideo && !isGif && !isRaw && !isSvg)
continue continue
@ -216,6 +222,7 @@ class MediaFetcher(val context: Context) {
if (isSvg && filterMedia and TYPE_SVGS == 0) if (isSvg && filterMedia and TYPE_SVGS == 0)
continue continue
val filename = file.name
if (!showHidden && filename.startsWith('.')) if (!showHidden && filename.startsWith('.'))
continue continue
@ -223,7 +230,6 @@ class MediaFetcher(val context: Context) {
if (size <= 0L || (doExtraCheck && !file.exists())) if (size <= 0L || (doExtraCheck && !file.exists()))
continue continue
val path = file.absolutePath
if (folder == RECYCLE_BIN) { if (folder == RECYCLE_BIN) {
deletedMedia.firstOrNull { it.path == path }?.apply { deletedMedia.firstOrNull { it.path == path }?.apply {
media.add(this) media.add(this)
@ -231,6 +237,7 @@ class MediaFetcher(val context: Context) {
} else { } else {
val lastModified = file.lastModified() val lastModified = file.lastModified()
var dateTaken = lastModified var dateTaken = lastModified
val videoDuration = if (getVideoDurations && isVideo) path.getVideoDuration() else 0
if (getProperDateTaken) { if (getProperDateTaken) {
dateTaken = dateTakens.remove(filename) ?: lastModified dateTaken = dateTakens.remove(filename) ?: lastModified
@ -245,14 +252,15 @@ class MediaFetcher(val context: Context) {
} }
val isFavorite = favoritePaths.contains(path) val isFavorite = favoritePaths.contains(path)
val medium = Medium(null, filename, path, file.parent, lastModified, dateTaken, size, type, isFavorite, 0L) val medium = Medium(null, filename, path, file.parent, lastModified, dateTaken, size, type, videoDuration, isFavorite, 0L)
media.add(medium) media.add(medium)
} }
} }
return media return media
} }
private fun getMediaOnOTG(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int, favoritePaths: ArrayList<String>): ArrayList<Medium> { private fun getMediaOnOTG(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int, favoritePaths: ArrayList<String>,
getVideoDurations: Boolean): ArrayList<Medium> {
val media = ArrayList<Medium>() val media = ArrayList<Medium>()
val files = context.getDocumentFile(folder)?.listFiles() ?: return media val files = context.getDocumentFile(folder)?.listFiles() ?: return media
val doExtraCheck = context.config.doExtraCheck val doExtraCheck = context.config.doExtraCheck
@ -307,8 +315,9 @@ class MediaFetcher(val context: Context) {
} }
val path = Uri.decode(file.uri.toString().replaceFirst("${context.config.OTGTreeUri}/document/${context.config.OTGPartition}%3A", OTG_PATH)) val path = Uri.decode(file.uri.toString().replaceFirst("${context.config.OTGTreeUri}/document/${context.config.OTGPartition}%3A", OTG_PATH))
val videoDuration = if (getVideoDurations) path.getVideoDuration() else 0
val isFavorite = favoritePaths.contains(path) val isFavorite = favoritePaths.contains(path)
val medium = Medium(null, filename, path, folder, dateModified, dateTaken, size, type, isFavorite, 0L) val medium = Medium(null, filename, path, folder, dateModified, dateTaken, size, type, videoDuration, isFavorite, 0L)
media.add(medium) media.add(medium)
} }
@ -344,6 +353,11 @@ class MediaFetcher(val context: Context) {
} }
fun sortMedia(media: ArrayList<Medium>, sorting: Int) { fun sortMedia(media: ArrayList<Medium>, sorting: Int) {
if (sorting and SORT_BY_RANDOM != 0) {
media.shuffle()
return
}
media.sortWith(Comparator { o1, o2 -> media.sortWith(Comparator { o1, o2 ->
o1 as Medium o1 as Medium
o2 as Medium o2 as Medium

View file

@ -0,0 +1,85 @@
package com.simplemobiletools.gallery.pro.helpers
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.RemoteViews
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.simplemobiletools.commons.extensions.setBackgroundColor
import com.simplemobiletools.commons.extensions.setText
import com.simplemobiletools.commons.extensions.setVisibleIf
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.activities.MediaActivity
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.models.Widget
class MyWidgetProvider : AppWidgetProvider() {
private fun setupAppOpenIntent(context: Context, views: RemoteViews, id: Int, widget: Widget) {
val intent = Intent(context, MediaActivity::class.java).apply {
putExtra(DIRECTORY, widget.folderPath)
}
val pendingIntent = PendingIntent.getActivity(context, widget.widgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
views.setOnClickPendingIntent(id, pendingIntent)
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
Thread {
val config = context.config
context.widgetsDB.getWidgets().filter { appWidgetIds.contains(it.widgetId) }.forEach {
val views = RemoteViews(context.packageName, R.layout.widget).apply {
setBackgroundColor(R.id.widget_holder, config.widgetBgColor)
setVisibleIf(R.id.widget_folder_name, config.showWidgetFolderName)
setTextColor(R.id.widget_folder_name, config.widgetTextColor)
setText(R.id.widget_folder_name, context.getFolderNameFromPath(it.folderPath))
}
val path = context.directoryDB.getDirectoryThumbnail(it.folderPath) ?: return@forEach
val options = RequestOptions()
.signature(path.getFileSignature())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
if (context.config.cropThumbnails) options.centerCrop() else options.fitCenter()
val density = context.resources.displayMetrics.density
val appWidgetOptions = appWidgetManager.getAppWidgetOptions(appWidgetIds.first())
val width = appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
val height = appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
val widgetSize = (Math.max(width, height) * density).toInt()
try {
val image = Glide.with(context)
.asBitmap()
.load(path)
.apply(options)
.submit(widgetSize, widgetSize)
.get()
views.setImageViewBitmap(R.id.widget_imageview, image)
} catch (e: Exception) {
}
setupAppOpenIntent(context, views, R.id.widget_holder, it)
appWidgetManager.updateAppWidget(it.widgetId, views)
}
}.start()
}
override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
onUpdate(context, appWidgetManager, intArrayOf(appWidgetId))
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
super.onDeleted(context, appWidgetIds)
Thread {
appWidgetIds.forEach {
context.widgetsDB.deleteWidgetId(it)
}
}.start()
}
}

View file

@ -1,9 +1,9 @@
package com.simplemobiletools.gallery.helpers package com.simplemobiletools.gallery.pro.helpers
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder import com.davemorrissey.labs.subscaleview.ImageDecoder
import com.squareup.picasso.MemoryPolicy import com.squareup.picasso.MemoryPolicy
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso

View file

@ -1,16 +1,17 @@
package com.simplemobiletools.gallery.helpers package com.simplemobiletools.gallery.pro.helpers
import android.content.Context import android.content.Context
import android.graphics.* import android.graphics.*
import android.net.Uri import android.net.Uri
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder import com.davemorrissey.labs.subscaleview.ImageRegionDecoder
class PicassoRegionDecoder : ImageRegionDecoder { class PicassoRegionDecoder : ImageRegionDecoder {
private var decoder: BitmapRegionDecoder? = null private var decoder: BitmapRegionDecoder? = null
private val decoderLock = Any() private val decoderLock = Any()
override fun init(context: Context, uri: Uri): Point { override fun init(context: Context, uri: Uri): Point {
val inputStream = context.contentResolver.openInputStream(uri) val newUri = Uri.parse(uri.toString().replace("%", "%25").replace("#", "%23"))
val inputStream = context.contentResolver.openInputStream(newUri)
decoder = BitmapRegionDecoder.newInstance(inputStream, false) decoder = BitmapRegionDecoder.newInstance(inputStream, false)
return Point(decoder!!.width, decoder!!.height) return Point(decoder!!.width, decoder!!.height)
} }

View file

@ -1,11 +1,11 @@
package com.simplemobiletools.gallery.interfaces package com.simplemobiletools.gallery.pro.interfaces
import android.arch.persistence.room.Dao import androidx.room.Dao
import android.arch.persistence.room.Insert import androidx.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE import androidx.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query import androidx.room.Query
import com.simplemobiletools.gallery.helpers.RECYCLE_BIN import com.simplemobiletools.gallery.pro.helpers.RECYCLE_BIN
import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.pro.models.Directory
@Dao @Dao
interface DirectoryDao { interface DirectoryDao {
@ -29,4 +29,7 @@ interface DirectoryDao {
@Query("DELETE FROM directories WHERE path = \'$RECYCLE_BIN\' COLLATE NOCASE") @Query("DELETE FROM directories WHERE path = \'$RECYCLE_BIN\' COLLATE NOCASE")
fun deleteRecycleBin() fun deleteRecycleBin()
@Query("SELECT thumbnail FROM directories WHERE path = :path")
fun getDirectoryThumbnail(path: String): String?
} }

View file

@ -1,6 +1,6 @@
package com.simplemobiletools.gallery.interfaces package com.simplemobiletools.gallery.pro.interfaces
import com.simplemobiletools.gallery.models.Directory import com.simplemobiletools.gallery.pro.models.Directory
import java.io.File import java.io.File
interface DirectoryOperationsListener { interface DirectoryOperationsListener {

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.interfaces package com.simplemobiletools.gallery.pro.interfaces
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem

View file

@ -1,24 +1,24 @@
package com.simplemobiletools.gallery.interfaces package com.simplemobiletools.gallery.pro.interfaces
import android.arch.persistence.room.Dao import androidx.room.Dao
import android.arch.persistence.room.Delete import androidx.room.Delete
import android.arch.persistence.room.Insert import androidx.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE import androidx.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query import androidx.room.Query
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.models.Medium
@Dao @Dao
interface MediumDao { interface MediumDao {
@Query("SELECT filename, full_path, parent_path, last_modified, date_taken, size, type, is_favorite, deleted_ts FROM media WHERE deleted_ts = 0 AND parent_path = :path COLLATE NOCASE") @Query("SELECT filename, full_path, parent_path, last_modified, date_taken, size, type, video_duration, is_favorite, deleted_ts FROM media WHERE deleted_ts = 0 AND parent_path = :path COLLATE NOCASE")
fun getMediaFromPath(path: String): List<Medium> fun getMediaFromPath(path: String): List<Medium>
@Query("SELECT filename, full_path, parent_path, last_modified, date_taken, size, type, is_favorite, deleted_ts FROM media WHERE deleted_ts = 0 AND is_favorite = 1") @Query("SELECT filename, full_path, parent_path, last_modified, date_taken, size, type, video_duration, is_favorite, deleted_ts FROM media WHERE deleted_ts = 0 AND is_favorite = 1")
fun getFavorites(): List<Medium> fun getFavorites(): List<Medium>
@Query("SELECT full_path FROM media WHERE deleted_ts = 0 AND is_favorite = 1") @Query("SELECT full_path FROM media WHERE deleted_ts = 0 AND is_favorite = 1")
fun getFavoritePaths(): List<String> fun getFavoritePaths(): List<String>
@Query("SELECT filename, full_path, parent_path, last_modified, date_taken, size, type, is_favorite, deleted_ts FROM media WHERE deleted_ts != 0") @Query("SELECT filename, full_path, parent_path, last_modified, date_taken, size, type, video_duration, is_favorite, deleted_ts FROM media WHERE deleted_ts != 0")
fun getDeletedMedia(): List<Medium> fun getDeletedMedia(): List<Medium>
@Insert(onConflict = REPLACE) @Insert(onConflict = REPLACE)
@ -33,7 +33,7 @@ interface MediumDao {
@Query("DELETE FROM media WHERE full_path = :path COLLATE NOCASE") @Query("DELETE FROM media WHERE full_path = :path COLLATE NOCASE")
fun deleteMediumPath(path: String) fun deleteMediumPath(path: String)
@Query("DELETE FROM media WHERE deleted_ts < :timestmap") @Query("DELETE FROM media WHERE deleted_ts < :timestmap AND deleted_ts != 0")
fun deleteOldRecycleBinItems(timestmap: Long) fun deleteOldRecycleBinItems(timestmap: Long)
@Query("UPDATE OR REPLACE media SET filename = :newFilename, full_path = :newFullPath, parent_path = :newParentPath WHERE full_path = :oldPath COLLATE NOCASE") @Query("UPDATE OR REPLACE media SET filename = :newFilename, full_path = :newFullPath, parent_path = :newParentPath WHERE full_path = :oldPath COLLATE NOCASE")
@ -42,8 +42,8 @@ interface MediumDao {
@Query("UPDATE media SET is_favorite = :isFavorite WHERE full_path = :path COLLATE NOCASE") @Query("UPDATE media SET is_favorite = :isFavorite WHERE full_path = :path COLLATE NOCASE")
fun updateFavorite(path: String, isFavorite: Boolean) fun updateFavorite(path: String, isFavorite: Boolean)
@Query("UPDATE media SET deleted_ts = :deletedTS WHERE full_path = :path COLLATE NOCASE") @Query("UPDATE OR REPLACE media SET full_path = :newPath, deleted_ts = :deletedTS WHERE full_path = :oldPath COLLATE NOCASE")
fun updateDeleted(path: String, deletedTS: Long) fun updateDeleted(newPath: String, deletedTS: Long, oldPath: String)
@Query("UPDATE media SET date_taken = :dateTaken WHERE full_path = :path COLLATE NOCASE") @Query("UPDATE media SET date_taken = :dateTaken WHERE full_path = :path COLLATE NOCASE")
fun updateFavoriteDateTaken(path: String, dateTaken: Long) fun updateFavoriteDateTaken(path: String, dateTaken: Long)

View file

@ -0,0 +1,19 @@
package com.simplemobiletools.gallery.pro.interfaces
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.simplemobiletools.gallery.pro.models.Widget
@Dao
interface WidgetsDao {
@Query("SELECT * FROM widgets")
fun getWidgets(): List<Widget>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(widget: Widget): Long
@Query("DELETE FROM widgets WHERE widget_id = :widgetId")
fun deleteWidgetId(widgetId: Int)
}

View file

@ -1,3 +1,3 @@
package com.simplemobiletools.gallery.models package com.simplemobiletools.gallery.pro.models
data class AlbumCover(val path: String, val tmb: String) data class AlbumCover(val path: String, val tmb: String)

View file

@ -1,20 +1,16 @@
package com.simplemobiletools.gallery.models package com.simplemobiletools.gallery.pro.models
import android.arch.persistence.room.ColumnInfo import androidx.room.*
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
import com.simplemobiletools.commons.extensions.formatDate import com.simplemobiletools.commons.extensions.formatDate
import com.simplemobiletools.commons.extensions.formatSize import com.simplemobiletools.commons.extensions.formatSize
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED
import com.simplemobiletools.commons.helpers.SORT_BY_NAME import com.simplemobiletools.commons.helpers.SORT_BY_NAME
import com.simplemobiletools.commons.helpers.SORT_BY_PATH import com.simplemobiletools.commons.helpers.SORT_BY_PATH
import com.simplemobiletools.commons.helpers.SORT_BY_SIZE import com.simplemobiletools.commons.helpers.SORT_BY_SIZE
import com.simplemobiletools.gallery.helpers.FAVORITES import com.simplemobiletools.gallery.pro.helpers.FAVORITES
import com.simplemobiletools.gallery.helpers.RECYCLE_BIN import com.simplemobiletools.gallery.pro.helpers.RECYCLE_BIN
import java.io.Serializable
@Entity(tableName = "directories", indices = [Index(value = "path", unique = true)]) @Entity(tableName = "directories", indices = [Index(value = ["path"], unique = true)])
data class Directory( data class Directory(
@PrimaryKey(autoGenerate = true) var id: Long?, @PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "path") var path: String, @ColumnInfo(name = "path") var path: String,
@ -24,12 +20,14 @@ data class Directory(
@ColumnInfo(name = "last_modified") var modified: Long, @ColumnInfo(name = "last_modified") var modified: Long,
@ColumnInfo(name = "date_taken") var taken: Long, @ColumnInfo(name = "date_taken") var taken: Long,
@ColumnInfo(name = "size") var size: Long, @ColumnInfo(name = "size") var size: Long,
@ColumnInfo(name = "location") val location: Int, @ColumnInfo(name = "location") var location: Int,
@ColumnInfo(name = "media_types") var types: Int) : Serializable { @ColumnInfo(name = "media_types") var types: Int,
companion object { // used with "Group direct subfolders" enabled
private const val serialVersionUID = -6553345863555455L @Ignore var subfoldersCount: Int = 0,
} @Ignore var subfoldersMediaCount: Int = 0) {
constructor() : this(null, "", "", "", 0, 0L, 0L, 0L, 0, 0, 0, 0)
fun getBubbleText(sorting: Int) = when { fun getBubbleText(sorting: Int) = when {
sorting and SORT_BY_NAME != 0 -> name sorting and SORT_BY_NAME != 0 -> name

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.models package com.simplemobiletools.gallery.pro.models
import android.graphics.Bitmap import android.graphics.Bitmap
import com.zomato.photofilters.imageprocessors.Filter import com.zomato.photofilters.imageprocessors.Filter

View file

@ -1,9 +1,9 @@
package com.simplemobiletools.gallery.models package com.simplemobiletools.gallery.pro.models
import android.arch.persistence.room.ColumnInfo import androidx.room.ColumnInfo
import android.arch.persistence.room.Entity import androidx.room.Entity
import android.arch.persistence.room.Index import androidx.room.Index
import android.arch.persistence.room.PrimaryKey import androidx.room.PrimaryKey
import com.simplemobiletools.commons.extensions.formatDate import com.simplemobiletools.commons.extensions.formatDate
import com.simplemobiletools.commons.extensions.formatSize import com.simplemobiletools.commons.extensions.formatSize
import com.simplemobiletools.commons.extensions.getFilenameExtension import com.simplemobiletools.commons.extensions.getFilenameExtension
@ -11,11 +11,11 @@ import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED
import com.simplemobiletools.commons.helpers.SORT_BY_NAME import com.simplemobiletools.commons.helpers.SORT_BY_NAME
import com.simplemobiletools.commons.helpers.SORT_BY_PATH import com.simplemobiletools.commons.helpers.SORT_BY_PATH
import com.simplemobiletools.commons.helpers.SORT_BY_SIZE import com.simplemobiletools.commons.helpers.SORT_BY_SIZE
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.helpers.*
import java.io.Serializable import java.io.Serializable
import java.util.* import java.util.*
@Entity(tableName = "media", indices = [(Index(value = "full_path", unique = true))]) @Entity(tableName = "media", indices = [(Index(value = ["full_path"], unique = true))])
data class Medium( data class Medium(
@PrimaryKey(autoGenerate = true) var id: Long?, @PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "filename") var name: String, @ColumnInfo(name = "filename") var name: String,
@ -25,6 +25,7 @@ data class Medium(
@ColumnInfo(name = "date_taken") var taken: Long, @ColumnInfo(name = "date_taken") var taken: Long,
@ColumnInfo(name = "size") val size: Long, @ColumnInfo(name = "size") val size: Long,
@ColumnInfo(name = "type") val type: Int, @ColumnInfo(name = "type") val type: Int,
@ColumnInfo(name = "video_duration") val videoDuration: Int,
@ColumnInfo(name = "is_favorite") var isFavorite: Boolean, @ColumnInfo(name = "is_favorite") var isFavorite: Boolean,
@ColumnInfo(name = "deleted_ts") var deletedTS: Long) : Serializable, ThumbnailItem() { @ColumnInfo(name = "deleted_ts") var deletedTS: Long) : Serializable, ThumbnailItem() {

View file

@ -0,0 +1,5 @@
package com.simplemobiletools.gallery.pro.models
import android.graphics.Color
data class PaintOptions(var color: Int = Color.BLACK, var strokeWidth: Float = 5f)

View file

@ -0,0 +1,3 @@
package com.simplemobiletools.gallery.pro.models
open class ThumbnailItem

View file

@ -1,3 +1,3 @@
package com.simplemobiletools.gallery.models package com.simplemobiletools.gallery.pro.models
data class ThumbnailSection(val title: String) : ThumbnailItem() data class ThumbnailSection(val title: String) : ThumbnailItem()

View file

@ -0,0 +1,12 @@
package com.simplemobiletools.gallery.pro.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "widgets", indices = [(Index(value = ["widget_id"], unique = true))])
data class Widget(
@PrimaryKey(autoGenerate = true) var id: Int?,
@ColumnInfo(name = "widget_id") var widgetId: Int,
@ColumnInfo(name = "folder_path") var folderPath: String)

View file

@ -1,13 +1,13 @@
package com.simplemobiletools.gallery.receivers package com.simplemobiletools.gallery.pro.receivers
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.REFRESH_PATH import com.simplemobiletools.commons.helpers.REFRESH_PATH
import com.simplemobiletools.gallery.extensions.galleryDB import com.simplemobiletools.gallery.pro.extensions.galleryDB
import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.models.Medium import com.simplemobiletools.gallery.pro.models.Medium
import java.io.File import java.io.File
class RefreshMediaReceiver : BroadcastReceiver() { class RefreshMediaReceiver : BroadcastReceiver() {
@ -16,7 +16,7 @@ class RefreshMediaReceiver : BroadcastReceiver() {
Thread { Thread {
val medium = Medium(null, path.getFilenameFromPath(), path, path.getParentPath(), System.currentTimeMillis(), System.currentTimeMillis(), val medium = Medium(null, path.getFilenameFromPath(), path, path.getParentPath(), System.currentTimeMillis(), System.currentTimeMillis(),
File(path).length(), getFileType(path), false, 0L) File(path).length(), getFileType(path), 0, false, 0L)
context.galleryDB.MediumDao().insert(medium) context.galleryDB.MediumDao().insert(medium)
}.start() }.start()
} }

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.svg package com.simplemobiletools.gallery.pro.svg
import com.bumptech.glide.load.Options import com.bumptech.glide.load.Options
import com.bumptech.glide.load.ResourceDecoder import com.bumptech.glide.load.ResourceDecoder

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.svg package com.simplemobiletools.gallery.pro.svg
import android.graphics.drawable.PictureDrawable import android.graphics.drawable.PictureDrawable
import com.bumptech.glide.load.Options import com.bumptech.glide.load.Options

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.svg package com.simplemobiletools.gallery.pro.svg
import android.content.Context import android.content.Context
import android.graphics.drawable.PictureDrawable import android.graphics.drawable.PictureDrawable

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.svg package com.simplemobiletools.gallery.pro.svg
import android.graphics.drawable.PictureDrawable import android.graphics.drawable.PictureDrawable
import android.widget.ImageView import android.widget.ImageView

View file

@ -0,0 +1,147 @@
package com.simplemobiletools.gallery.pro.views
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.models.PaintOptions
import java.util.*
class EditorDrawCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) {
private var mCurX = 0f
private var mCurY = 0f
private var mStartX = 0f
private var mStartY = 0f
private var mColor = 0
private var mWasMultitouch = false
private var mPaths = LinkedHashMap<Path, PaintOptions>()
private var mPaint = Paint()
private var mPath = Path()
private var mPaintOptions = PaintOptions()
private var backgroundBitmap: Bitmap? = null
init {
mColor = context.config.primaryColor
mPaint.apply {
color = mColor
style = Paint.Style.STROKE
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
strokeWidth = 40f
isAntiAlias = true
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.save()
if (backgroundBitmap != null) {
canvas.drawBitmap(backgroundBitmap!!, 0f, 0f, null)
}
for ((key, value) in mPaths) {
changePaint(value)
canvas.drawPath(key, mPaint)
}
changePaint(mPaintOptions)
canvas.drawPath(mPath, mPaint)
canvas.restore()
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val x = event.x
val y = event.y
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
mWasMultitouch = false
mStartX = x
mStartY = y
actionDown(x, y)
}
MotionEvent.ACTION_MOVE -> {
if (event.pointerCount == 1 && !mWasMultitouch) {
actionMove(x, y)
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> actionUp()
MotionEvent.ACTION_POINTER_DOWN -> mWasMultitouch = true
}
invalidate()
return true
}
private fun actionDown(x: Float, y: Float) {
mPath.reset()
mPath.moveTo(x, y)
mCurX = x
mCurY = y
}
private fun actionMove(x: Float, y: Float) {
mPath.quadTo(mCurX, mCurY, (x + mCurX) / 2, (y + mCurY) / 2)
mCurX = x
mCurY = y
}
private fun actionUp() {
if (!mWasMultitouch) {
mPath.lineTo(mCurX, mCurY)
// draw a dot on click
if (mStartX == mCurX && mStartY == mCurY) {
mPath.lineTo(mCurX, mCurY + 2)
mPath.lineTo(mCurX + 1, mCurY + 2)
mPath.lineTo(mCurX + 1, mCurY)
}
}
mPaths[mPath] = mPaintOptions
mPath = Path()
mPaintOptions = PaintOptions(mPaintOptions.color, mPaintOptions.strokeWidth)
}
private fun changePaint(paintOptions: PaintOptions) {
mPaint.color = paintOptions.color
mPaint.strokeWidth = paintOptions.strokeWidth
}
fun updateColor(newColor: Int) {
mPaintOptions.color = newColor
}
fun updateBrushSize(newBrushSize: Int) {
mPaintOptions.strokeWidth = resources.getDimension(R.dimen.full_brush_size) * (newBrushSize / 100f)
}
fun updateBackgroundBitmap(bitmap: Bitmap) {
backgroundBitmap = bitmap
invalidate()
}
fun getBitmap(): Bitmap {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
canvas.drawColor(Color.WHITE)
draw(canvas)
return bitmap
}
fun undo() {
if (mPaths.isEmpty()) {
return
}
val lastKey = mPaths.keys.lastOrNull()
mPaths.remove(lastKey)
invalidate()
}
}

View file

@ -1,12 +1,13 @@
package com.simplemobiletools.gallery.views package com.simplemobiletools.gallery.pro.views
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RelativeLayout import android.widget.RelativeLayout
import com.simplemobiletools.gallery.helpers.CLICK_MAX_DURATION import com.simplemobiletools.gallery.pro.helpers.CLICK_MAX_DISTANCE
import com.simplemobiletools.gallery.helpers.DRAG_THRESHOLD import com.simplemobiletools.gallery.pro.helpers.CLICK_MAX_DURATION
import com.simplemobiletools.gallery.pro.helpers.DRAG_THRESHOLD
// handle only one finger clicks, pass other events to the parent view and ignore it when received again // handle only one finger clicks, pass other events to the parent view and ignore it when received again
class InstantItemSwitch(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) { class InstantItemSwitch(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) {
@ -40,7 +41,9 @@ class InstantItemSwitch(context: Context, attrs: AttributeSet) : RelativeLayout(
mTouchDownTime = System.currentTimeMillis() mTouchDownTime = System.currentTimeMillis()
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
if (System.currentTimeMillis() - mTouchDownTime < CLICK_MAX_DURATION) { val diffX = mTouchDownX - event.x
val diffY = mTouchDownY - event.y
if (Math.abs(diffX) < CLICK_MAX_DISTANCE && Math.abs(diffY) < CLICK_MAX_DISTANCE && System.currentTimeMillis() - mTouchDownTime < CLICK_MAX_DURATION) {
performClick() performClick()
} }
} }

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.helpers package com.simplemobiletools.gallery.pro.views
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
@ -10,9 +10,12 @@ import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import com.simplemobiletools.gallery.R import com.simplemobiletools.commons.extensions.onGlobalLayout
import com.simplemobiletools.gallery.activities.ViewPagerActivity import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.extensions.audioManager import com.simplemobiletools.gallery.pro.extensions.audioManager
import com.simplemobiletools.gallery.pro.helpers.CLICK_MAX_DISTANCE
import com.simplemobiletools.gallery.pro.helpers.CLICK_MAX_DURATION
import com.simplemobiletools.gallery.pro.helpers.DRAG_THRESHOLD
// allow horizontal swipes through the layout, else it can cause glitches at zoomed in images // allow horizontal swipes through the layout, else it can cause glitches at zoomed in images
class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) { class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) {
@ -23,6 +26,7 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
private var mTouchDownValue = -1 private var mTouchDownValue = -1
private var mTempBrightness = 0 private var mTempBrightness = 0
private var mLastTouchY = 0f private var mLastTouchY = 0f
private var mViewHeight = 0
private var mIsBrightnessScroll = false private var mIsBrightnessScroll = false
private var mPassTouches = false private var mPassTouches = false
private var dragThreshold = DRAG_THRESHOLD * context.resources.displayMetrics.density private var dragThreshold = DRAG_THRESHOLD * context.resources.displayMetrics.density
@ -30,8 +34,8 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
private var mSlideInfoText = "" private var mSlideInfoText = ""
private var mSlideInfoFadeHandler = Handler() private var mSlideInfoFadeHandler = Handler()
private var mParentView: ViewGroup? = null private var mParentView: ViewGroup? = null
private var activity: Activity? = null
private lateinit var activity: Activity
private lateinit var slideInfoView: TextView private lateinit var slideInfoView: TextView
private lateinit var callback: (Float, Float) -> Unit private lateinit var callback: (Float, Float) -> Unit
@ -42,6 +46,9 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
mParentView = parentView mParentView = parentView
mIsBrightnessScroll = isBrightness mIsBrightnessScroll = isBrightness
mSlideInfoText = activity.getString(if (isBrightness) R.string.brightness else R.string.volume) mSlideInfoText = activity.getString(if (isBrightness) R.string.brightness else R.string.volume)
onGlobalLayout {
mViewHeight = height
}
} }
override fun dispatchTouchEvent(ev: MotionEvent): Boolean { override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
@ -55,7 +62,7 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
} }
override fun onTouchEvent(event: MotionEvent): Boolean { override fun onTouchEvent(event: MotionEvent): Boolean {
if (mPassTouches) { if (mPassTouches && activity == null) {
return false return false
} }
@ -78,7 +85,7 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
val diffY = mTouchDownY - event.y val diffY = mTouchDownY - event.y
if (Math.abs(diffY) > dragThreshold && Math.abs(diffY) > Math.abs(diffX)) { if (Math.abs(diffY) > dragThreshold && Math.abs(diffY) > Math.abs(diffX)) {
var percent = ((diffY / ViewPagerActivity.screenHeight) * 100).toInt() * 3 var percent = ((diffY / mViewHeight) * 100).toInt() * 3
percent = Math.min(100, Math.max(-100, percent)) percent = Math.min(100, Math.max(-100, percent))
if ((percent == 100 && event.y > mLastTouchY) || (percent == -100 && event.y < mLastTouchY)) { if ((percent == 100 && event.y > mLastTouchY) || (percent == -100 && event.y < mLastTouchY)) {
@ -100,7 +107,9 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
mLastTouchY = event.y mLastTouchY = event.y
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
if (System.currentTimeMillis() - mTouchDownTime < CLICK_MAX_DURATION) { val diffX = mTouchDownX - event.x
val diffY = mTouchDownY - event.y
if (Math.abs(diffX) < CLICK_MAX_DISTANCE && Math.abs(diffY) < CLICK_MAX_DISTANCE && System.currentTimeMillis() - mTouchDownTime < CLICK_MAX_DURATION) {
callback(event.rawX, event.rawY) callback(event.rawX, event.rawY)
} }
@ -112,11 +121,11 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
return true return true
} }
private fun getCurrentVolume() = activity.audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) private fun getCurrentVolume() = activity!!.audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
private fun getCurrentBrightness(): Int { private fun getCurrentBrightness(): Int {
return try { return try {
Settings.System.getInt(activity.contentResolver, Settings.System.SCREEN_BRIGHTNESS) Settings.System.getInt(activity!!.contentResolver, Settings.System.SCREEN_BRIGHTNESS)
} catch (e: Settings.SettingNotFoundException) { } catch (e: Settings.SettingNotFoundException) {
70 70
} }
@ -132,11 +141,11 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
private fun volumePercentChanged(percent: Int) { private fun volumePercentChanged(percent: Int) {
val stream = AudioManager.STREAM_MUSIC val stream = AudioManager.STREAM_MUSIC
val maxVolume = activity.audioManager.getStreamMaxVolume(stream) val maxVolume = activity!!.audioManager.getStreamMaxVolume(stream)
val percentPerPoint = 100 / maxVolume val percentPerPoint = 100 / maxVolume
val addPoints = percent / percentPerPoint val addPoints = percent / percentPerPoint
val newVolume = Math.min(maxVolume, Math.max(0, mTouchDownValue + addPoints)) val newVolume = Math.min(maxVolume, Math.max(0, mTouchDownValue + addPoints))
activity.audioManager.setStreamVolume(stream, newVolume, 0) activity!!.audioManager.setStreamVolume(stream, newVolume, 0)
val absolutePercent = ((newVolume / maxVolume.toFloat()) * 100).toInt() val absolutePercent = ((newVolume / maxVolume.toFloat()) * 100).toInt()
showValue(absolutePercent) showValue(absolutePercent)
@ -156,9 +165,9 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
val absolutePercent = ((newBrightness / maxBrightness) * 100).toInt() val absolutePercent = ((newBrightness / maxBrightness) * 100).toInt()
showValue(absolutePercent) showValue(absolutePercent)
val attributes = activity.window.attributes val attributes = activity!!.window.attributes
attributes.screenBrightness = absolutePercent / 100f attributes.screenBrightness = absolutePercent / 100f
activity.window.attributes = attributes activity!!.window.attributes = attributes
mSlideInfoFadeHandler.removeCallbacksAndMessages(null) mSlideInfoFadeHandler.removeCallbacksAndMessages(null)
mSlideInfoFadeHandler.postDelayed({ mSlideInfoFadeHandler.postDelayed({

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.views package com.simplemobiletools.gallery.pro.views
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Some files were not shown because too many files have changed in this diff Show more