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
*.aab
.gradle
/local.properties
/gradle.properties
@ -6,5 +7,5 @@
.DS_Store
/build
/captures
release.keystore
signing.properties
keystore.jks
keystore.properties

View file

@ -1,6 +1,226 @@
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)*
----------------------------

View file

@ -4,21 +4,39 @@
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>
<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>
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://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;">
<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_5.jpg" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_3.jpg" width="30%">
</div>
License

View file

@ -3,22 +3,31 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
compileSdkVersion 28
buildToolsVersion "28.0.2"
buildToolsVersion "28.0.3"
defaultConfig {
applicationId "com.simplemobiletools.gallery"
minSdkVersion 16
applicationId "com.simplemobiletools.gallery.pro"
minSdkVersion 21
targetSdkVersion 28
versionCode 198
versionName "4.6.2"
versionCode 228
versionName "6.5.0"
multiDexEnabled true
setProperty("archivesBaseName", "gallery")
}
signingConfigs {
release
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
@ -40,49 +49,36 @@ android {
checkReleaseBuilds false
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
exclude 'META-INF/library_release.kotlin_module'
}
}
dependencies {
implementation 'com.simplemobiletools:commons:4.7.14'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.7.0'
implementation 'com.android.support:multidex:1.0.3'
implementation 'com.simplemobiletools:commons:5.7.6'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'it.sephiroth.android.exif:library:1.0.1'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.android.exoplayer:exoplayer-core:2.8.4'
implementation 'com.google.vr:sdk-panowidget:1.150.0'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.16'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.2'
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 'info.androidhive:imagefilters:1.0.7'
implementation 'com.squareup.picasso:picasso:2.71828'
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 "android.arch.persistence.room:compiler:1.1.1"
implementation "android.arch.persistence.room:runtime:1.1.1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
//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
kapt 'androidx.room:room-compiler:2.0.0'
implementation 'androidx.room:room-runtime:2.0.0'
annotationProcessor 'androidx.room:room-compiler:2.0.0'
}

View file

@ -2,7 +2,7 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.simplemobiletools.gallery"
package="com.simplemobiletools.gallery.pro"
android:installLocation="auto">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@ -32,6 +32,14 @@
android:name=".activities.MainActivity"
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>
<action android:name="android.intent.action.PICK"/>
<category android:name="android.intent.category.DEFAULT"/>
@ -115,7 +123,17 @@
android:configChanges="orientation|keyboardHidden|screenSize"/>
<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:theme="@style/FullScreenTheme"/>
@ -202,8 +220,17 @@
</intent-filter>
</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
android:name="android.support.v4.content.FileProvider"
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
@ -220,6 +247,18 @@
</intent-filter>
</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
android:name=".activities.SplashActivity.Red"
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.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.content.Intent
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.graphics.Color
import android.graphics.Point
import android.media.ExifInterface
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import android.widget.RelativeLayout
import androidx.recyclerview.widget.LinearLayoutManager
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.simplemobiletools.commons.dialogs.ColorPickerDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.commons.helpers.REAL_FILE_PATH
import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.FiltersAdapter
import com.simplemobiletools.gallery.dialogs.ResizeDialog
import com.simplemobiletools.gallery.dialogs.SaveAsDialog
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.extensions.openEditor
import com.simplemobiletools.gallery.helpers.FilterThumbnailsManager
import com.simplemobiletools.gallery.models.FilterItem
import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.FiltersAdapter
import com.simplemobiletools.gallery.pro.dialogs.OtherAspectRatioDialog
import com.simplemobiletools.gallery.pro.dialogs.ResizeDialog
import com.simplemobiletools.gallery.pro.dialogs.SaveAsDialog
import com.simplemobiletools.gallery.pro.extensions.config
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.zomato.photofilters.FilterPack
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_editor_actions_filter.*
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 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_Y = "aspectY"
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
private val PRIMARY_ACTION_NONE = 0
private val PRIMARY_ACTION_FILTER = 1
private val PRIMARY_ACTION_CROP_ROTATE = 2
private val PRIMARY_ACTION_DRAW = 3
private val CROP_ROTATE_NONE = 0
private val CROP_ROTATE_ASPECT_RATIO = 1
@ -71,13 +77,17 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
private lateinit var saveUri: Uri
private var resizeWidth = 0
private var resizeHeight = 0
private var drawColor = 0
private var lastOtherAspectRatio: Pair<Int, Int>? = null
private var currPrimaryAction = PRIMARY_ACTION_NONE
private var currCropRotateAction = CROP_ROTATE_NONE
private var currAspectRatio = ASPECT_RATIO_FREE
private var isCropIntent = false
private var isEditingWithThirdParty = false
private var initialBitmap: Bitmap? = null
private var isSharingBitmap = false
private var wasDrawCanvasPositioned = false
private var oldExif: ExifInterface? = null
private var filterInitialBitmap: Bitmap? = null
override fun onCreate(savedInstanceState: Bundle?) {
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() {
if (intent.data == null) {
toast(R.string.invalid_image_path)
@ -133,37 +171,25 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
loadDefaultImageView()
setupBottomActions()
}
override fun onResume() {
super.onResume()
isEditingWithThirdParty = false
}
if (config.lastEditorCropAspectRatio == ASPECT_RATIO_OTHER) {
if (config.lastEditorCropOtherAspectRatioX == 0) {
config.lastEditorCropOtherAspectRatioX = 1
}
override fun onStop() {
super.onStop()
if (isEditingWithThirdParty) {
finish()
if (config.lastEditorCropOtherAspectRatioY == 0) {
config.lastEditorCropOtherAspectRatioY = 1
}
lastOtherAspectRatio = Pair(config.lastEditorCropOtherAspectRatioX, config.lastEditorCropOtherAspectRatioY)
}
}
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
updateAspectRatio(config.lastEditorCropAspectRatio)
}
private fun loadDefaultImageView() {
default_image_view.beVisible()
crop_image_view.beGone()
editor_draw_canvas.beGone()
val options = RequestOptions()
.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 {
val currentFilter = getFiltersAdapter()?.getCurrentFilter()
if (initialBitmap == null) {
if (filterInitialBitmap == null) {
loadCropImageView()
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 {
applyFilter(currentFilter)
}
} else {
initialBitmap = bitmap
filterInitialBitmap = bitmap
}
if (isCropIntent) {
bottom_primary_filter.beGone()
bottom_primary_draw.beGone()
}
return false
@ -202,6 +229,7 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
private fun loadCropImageView() {
default_image_view.beGone()
editor_draw_canvas.beGone()
crop_image_view.apply {
beVisible()
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() {
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()) {
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 {
val currentFilter = getFiltersAdapter()?.getCurrentFilter() ?: return
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 setupBottomActions() {
setupPrimaryActionButtons()
setupCropRotateActionButtons()
setupAspectRatioButtons()
setupDrawButtons()
}
private fun setupPrimaryActionButtons() {
@ -260,6 +424,10 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
bottom_primary_crop_rotate.setOnClickListener {
bottomCropRotateClicked()
}
bottom_primary_draw.setOnClickListener {
bottomDrawClicked()
}
}
private fun bottomFilterClicked() {
@ -280,6 +448,15 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
updatePrimaryActionButtons()
}
private fun bottomDrawClicked() {
currPrimaryAction = if (currPrimaryAction == PRIMARY_ACTION_DRAW) {
PRIMARY_ACTION_NONE
} else {
PRIMARY_ACTION_DRAW
}
updatePrimaryActionButtons()
}
private fun setupCropRotateActionButtons() {
bottom_rotate.setOnClickListener {
crop_image_view.rotateImage(90)
@ -328,34 +505,83 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
bottom_aspect_ratio_sixteen_nine.setOnClickListener {
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()
}
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() {
if (crop_image_view.isGone() && currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE) {
loadCropImageView()
} else if (default_image_view.isGone() && currPrimaryAction == PRIMARY_ACTION_FILTER) {
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)
}
val currentPrimaryActionButton = when (currPrimaryAction) {
PRIMARY_ACTION_FILTER -> bottom_primary_filter
PRIMARY_ACTION_CROP_ROTATE -> bottom_primary_crop_rotate
PRIMARY_ACTION_DRAW -> bottom_primary_draw
else -> null
}
currentPrimaryActionButton?.applyColorFilter(config.primaryColor)
currentPrimaryActionButton?.applyColorFilter(getAdjustedPrimaryColor())
bottom_editor_filter_actions.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_FILTER)
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) {
Thread {
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 {
val filterThumbnailsManager = FilterThumbnailsManager()
filterThumbnailsManager.clearThumbs()
@ -394,12 +620,13 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
}
private fun applyFilter(filterItem: FilterItem) {
val newBitmap = Bitmap.createBitmap(initialBitmap)
val newBitmap = Bitmap.createBitmap(filterInitialBitmap)
default_image_view.setImageBitmap(filterItem.filter.processFilter(newBitmap))
}
private fun updateAspectRatio(aspectRatio: Int) {
currAspectRatio = aspectRatio
config.lastEditorCropAspectRatio = aspectRatio
updateAspectRatioButtons()
crop_image_view.apply {
@ -409,7 +636,8 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
val newAspectRatio = when (aspectRatio) {
ASPECT_RATIO_ONE_ONE -> Pair(1, 1)
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)
@ -418,7 +646,7 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
}
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)
}
@ -426,10 +654,11 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
ASPECT_RATIO_FREE -> bottom_aspect_ratio_free
ASPECT_RATIO_ONE_ONE -> bottom_aspect_ratio_one_one
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() {
@ -442,7 +671,14 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
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() {
@ -480,15 +716,22 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
override fun onCropImageComplete(view: CropImageView, result: CropImageView.CropResult) {
if (result.error == null) {
val bitmap = result.bitmap
if (isSharingBitmap) {
isSharingBitmap = false
shareBitmap(bitmap)
return
}
if (isCropIntent) {
if (saveUri.scheme == "file") {
saveBitmapToFile(result.bitmap, saveUri.path, true)
saveBitmapToFile(bitmap, saveUri.path, true)
} else {
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
try {
val stream = ByteArrayOutputStream()
result.bitmap.compress(CompressFormat.JPEG, 100, stream)
bitmap.compress(CompressFormat.JPEG, 100, stream)
inputStream = ByteArrayInputStream(stream.toByteArray())
outputStream = contentResolver.openOutputStream(saveUri)
inputStream.copyTo(outputStream)
@ -506,12 +749,12 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
}
} else if (saveUri.scheme == "file") {
SaveAsDialog(this, saveUri.path, true) {
saveBitmapToFile(result.bitmap, it, true)
saveBitmapToFile(bitmap, it, true)
}
} else if (saveUri.scheme == "content") {
val filePathGetter = getNewFilePath()
SaveAsDialog(this, filePathGetter.first, filePathGetter.second) {
saveBitmapToFile(result.bitmap, it, true)
saveBitmapToFile(bitmap, it, true)
}
} else {
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) {
if (showSavingToast) {
toast(R.string.saving)
@ -572,13 +816,22 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
} else {
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)
scanFinalPath(file.absolutePath)
out.close()
}
private fun editWith() {
openEditor(uri.toString())
openEditor(uri.toString(), 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.view.Menu
@ -6,9 +6,9 @@ import android.view.MenuItem
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.ManageFoldersAdapter
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.ManageFoldersAdapter
import com.simplemobiletools.gallery.pro.extensions.config
import kotlinx.android.synthetic.main.activity_manage_folders.*
class ExcludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
@ -20,7 +20,7 @@ class ExcludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
private fun updateFolders() {
val folders = ArrayList<String>()
config.excludedFolders.mapTo(folders, { it })
config.excludedFolders.mapTo(folders) { it }
manage_folders_placeholder.apply {
text = getString(R.string.excluded_activity_placeholder)
beVisibleIf(folders.isEmpty())
@ -49,7 +49,7 @@ class ExcludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
}
private fun addFolder() {
FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden) {
FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden, false, true) {
config.lastFilepickerPath = it
config.addExcludedFolder(it)
updateFolders()

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities
package com.simplemobiletools.gallery.pro.activities
import android.os.Bundle
import android.view.Menu
@ -6,11 +6,11 @@ import android.view.MenuItem
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.ManageHiddenFoldersAdapter
import com.simplemobiletools.gallery.extensions.addNoMedia
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.extensions.getNoMediaFolders
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.ManageHiddenFoldersAdapter
import com.simplemobiletools.gallery.pro.extensions.addNoMedia
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.extensions.getNoMediaFolders
import kotlinx.android.synthetic.main.activity_manage_folders.*
class HiddenFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
@ -53,7 +53,7 @@ class HiddenFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
}
private fun addFolder() {
FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden) {
FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden, false, true) {
config.lastFilepickerPath = it
Thread {
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.view.Menu
@ -7,9 +7,9 @@ import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.scanPathRecursively
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.ManageFoldersAdapter
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.ManageFoldersAdapter
import com.simplemobiletools.gallery.pro.extensions.config
import kotlinx.android.synthetic.main.activity_manage_folders.*
class IncludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
@ -21,7 +21,7 @@ class IncludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
private fun updateFolders() {
val folders = ArrayList<String>()
config.includedFolders.mapTo(folders, { it })
config.includedFolders.mapTo(folders) { it }
manage_folders_placeholder.apply {
text = getString(R.string.included_activity_placeholder)
beVisibleIf(folders.isEmpty())
@ -50,7 +50,7 @@ class IncludedFoldersActivity : SimpleActivity(), RefreshRecyclerViewListener {
}
private fun addFolder() {
FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden) {
FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden, false, true) {
config.lastFilepickerPath = it
config.addIncludedFolder(it)
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.SearchManager
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.provider.MediaStore
import android.support.v7.widget.GridLayoutManager
import android.view.Menu
import android.view.MenuItem
import android.view.ViewGroup
import android.widget.FrameLayout
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.FilePickerDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.models.Release
import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.BuildConfig
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.DirectoryAdapter
import com.simplemobiletools.gallery.databases.GalleryDatabase
import com.simplemobiletools.gallery.dialogs.ChangeSortingDialog
import com.simplemobiletools.gallery.dialogs.FilterMediaDialog
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.interfaces.DirectoryDao
import com.simplemobiletools.gallery.interfaces.DirectoryOperationsListener
import com.simplemobiletools.gallery.interfaces.MediumDao
import com.simplemobiletools.gallery.models.AlbumCover
import com.simplemobiletools.gallery.models.Directory
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.DirectoryAdapter
import com.simplemobiletools.gallery.pro.databases.GalleryDatabase
import com.simplemobiletools.gallery.pro.dialogs.ChangeSortingDialog
import com.simplemobiletools.gallery.pro.dialogs.ChangeViewTypeDialog
import com.simplemobiletools.gallery.pro.dialogs.FilterMediaDialog
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.interfaces.DirectoryDao
import com.simplemobiletools.gallery.pro.interfaces.DirectoryOperationsListener
import com.simplemobiletools.gallery.pro.interfaces.MediumDao
import com.simplemobiletools.gallery.pro.models.AlbumCover
import com.simplemobiletools.gallery.pro.models.Directory
import com.simplemobiletools.gallery.pro.models.Medium
import kotlinx.android.synthetic.main.activity_main.*
import java.io.*
import java.util.*
@ -60,11 +64,16 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
private var mIsPasswordProtectionPending = false
private var mWasProtectionHandled = false
private var mShouldStopFetching = false
private var mIsSearchOpen = false
private var mLatestMediaId = 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 mTempShowHiddenHandler = Handler()
private var mZoomListener: MyRecyclerView.MyZoomListener? = null
private var mSearchMenuItem: MenuItem? = null
private var mDirs = ArrayList<Directory>()
private var mStoredAnimateGifs = true
private var mStoredCropThumbnails = true
@ -110,7 +119,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
showFilterMediaDialog()
}
mIsPasswordProtectionPending = config.appPasswordProtectionOn
mIsPasswordProtectionPending = config.isAppPasswordProtectionOn
setupLatestMediaId()
// notify some users about the Clock app
@ -119,10 +128,6 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
NewAppDialog(this, NEW_APP_PACKAGE, "Simple Clock")
}*/
if (!config.wasOTGHandled && hasPermission(PERMISSION_WRITE_STORAGE)) {
checkOTGInclusion()
}
if (!config.wereFavoritesPinned) {
config.addPinnedFolders(hashSetOf(FAVORITES))
config.wereFavoritesPinned = true
@ -140,6 +145,8 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
config.filterMedia += TYPE_SVGS
}
}
updateWidgets()
}
override fun onStart() {
@ -213,6 +220,8 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
override fun onStop() {
super.onStop()
mSearchMenuItem?.collapseActionView()
if (config.temporarilyShowHidden || config.tempSkipDeleteConfirmation) {
mTempShowHiddenHandler.postDelayed({
config.temporarilyShowHidden = false
@ -230,7 +239,24 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
config.tempSkipDeleteConfirmation = false
mTempShowHiddenHandler.removeCallbacksAndMessages(null)
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 {
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
setupSearch(this)
}
}
menu.findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden
menu.findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden
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() {
if (config.tempFolderPath.isNotEmpty()) {
val newFolder = File(config.tempFolderPath)
@ -309,8 +375,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
Thread {
if (hasOTGConnected()) {
runOnUiThread {
handleOTGPermission {
config.addIncludedFolder(OTG_PATH)
ConfirmationDialog(this, getString(R.string.usb_detected), positive = R.string.ok, negative = 0) {
handleOTGPermission {
config.addIncludedFolder(OTG_PATH)
}
}
}
config.wasOTGHandled = true
@ -321,6 +389,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
private fun tryLoadGallery() {
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
if (!config.wasOTGHandled && hasPermission(PERMISSION_WRITE_STORAGE)) {
checkOTGInclusion()
}
if (config.showAll) {
showAllMedia()
} else {
@ -387,17 +459,11 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
}
private fun changeViewType() {
val items = arrayListOf(
RadioItem(VIEW_TYPE_GRID, getString(R.string.grid)),
RadioItem(VIEW_TYPE_LIST, getString(R.string.list)))
RadioGroupDialog(this, items, config.viewTypeFolders) {
config.viewTypeFolders = it as Int
ChangeViewTypeDialog(this, true) {
invalidateOptionsMenu()
setupLayoutManager()
val dirs = getCurrentlyDisplayedDirs()
directories_grid.adapter = null
setupAdapter(dirs)
setupAdapter(mDirs)
}
}
@ -420,16 +486,31 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
}
override fun deleteFolders(folders: ArrayList<File>) {
val fileDirItems = folders.map { FileDirItem(it.absolutePath, it.name, true) } as ArrayList<FileDirItem>
fileDirItems.forEach {
toast(String.format(getString(R.string.deleting_folder), it.name), Toast.LENGTH_LONG)
val fileDirItems = folders.asSequence().filter { it.isDirectory }.map { FileDirItem(it.absolutePath, it.name, true) }.toMutableList() as ArrayList<FileDirItem>
when {
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) {
val pathsToDelete = ArrayList<String>()
val filter = config.filterMedia
val showHidden = config.shouldShowHidden
fileDirItems.filter { it.isDirectory }.forEach {
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) {
@ -469,10 +550,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
private fun setupGridLayoutManager() {
val layoutManager = directories_grid.layoutManager as MyGridLayoutManager
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)
} else {
layoutManager.orientation = GridLayoutManager.VERTICAL
layoutManager.orientation = RecyclerView.VERTICAL
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() {
val layoutManager = directories_grid.layoutManager as MyGridLayoutManager
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)
mZoomListener = null
}
private fun createNewFolder() {
FilePickerDialog(this, internalStoragePath, false, config.shouldShowHidden) {
FilePickerDialog(this, internalStoragePath, false, config.shouldShowHidden, false, true) {
CreateNewFolderDialog(this, it) {
config.tempFolderPath = it
Thread {
@ -597,14 +678,22 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == PICK_MEDIA && resultData != null) {
val resultIntent = Intent()
var resultUri: Uri? = null
if (mIsThirdPartyIntent) {
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)
else -> fillIntentPath(resultData, resultIntent)
}
}
if (resultUri != null) {
resultIntent.data = resultUri
resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
setResult(Activity.RESULT_OK, resultIntent)
finish()
} else if (requestCode == PICK_WALLPAPER) {
@ -615,22 +704,25 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
super.onActivityResult(requestCode, resultCode, resultData)
}
private fun fillExtraOutput(resultData: Intent) {
val path = resultData.data.path
private fun fillExtraOutput(resultData: Intent): Uri? {
val file = File(resultData.data.path)
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
try {
val output = intent.extras.get(MediaStore.EXTRA_OUTPUT) as Uri
inputStream = FileInputStream(File(path))
inputStream = FileInputStream(file)
outputStream = contentResolver.openOutputStream(output)
inputStream.copyTo(outputStream)
} catch (e: SecurityException) {
showErrorToast(e)
} catch (ignored: FileNotFoundException) {
return getFilePublicUri(file, BuildConfig.APPLICATION_ID)
} finally {
inputStream?.close()
outputStream?.close()
}
return null
}
private fun fillPickedPaths(resultData: Intent, resultIntent: Intent) {
@ -678,9 +770,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
}
private fun gotDirectories(newDirs: ArrayList<Directory>) {
// if hidden item showing is disabled but all Favorite items are hidden, hide the Favorites folder
mIsGettingDirs = false
mShouldStopFetching = false
// if hidden item showing is disabled but all Favorite items are hidden, hide the Favorites folder
if (!config.shouldShowHidden) {
val favoritesFolder = newDirs.firstOrNull { it.areFavorites() }
if (favoritesFolder != null && favoritesFolder.tmb.getFilenameFromPath().startsWith('.')) {
@ -694,10 +787,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
runOnUiThread {
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_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
@ -707,9 +800,11 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
val hiddenString = getString(R.string.hidden)
val albumCovers = config.parseAlbumCovers()
val includedFolders = config.includedFolders
val tempFolderPath = config.tempFolderPath
val isSortingAscending = config.directorySorting and SORT_DESCENDING == 0
val getProperDateTaken = config.directorySorting and SORT_BY_DATE_TAKEN != 0
val favoritePaths = getFavoritePaths()
val dirPathsToRemove = ArrayList<String>()
try {
for (directory in dirs) {
@ -717,15 +812,18 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
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()) {
if (directory.path != tempFolderPath) {
dirPathsToRemove.add(directory.path)
}
directory
} else {
createDirectoryFromMedia(directory.path, curMedia, albumCovers, hiddenString, includedFolders, isSortingAscending)
}
// we are looping through the already displayed folders looking for changes, do not do anything if nothing changed
if (directory == newDir) {
if (directory.copy(subfoldersCount = 0, subfoldersMediaCount = 0) == newDir) {
continue
}
@ -739,7 +837,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
types = newDir.types
}
showSortedDirs(dirs)
setupAdapter(dirs)
// update directories and media files in the local db, delete invalid items
updateDBDirectory(directory, mDirectoryDao)
@ -751,7 +849,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
if (!curMedia.contains(it)) {
val path = (it as? Medium)?.path
if (path != null) {
mMediumDao.deleteMediumPath(path)
deleteDBPath(mMediumDao, path)
}
}
}
@ -760,9 +858,18 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} 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()
foldersToScan.add(FAVORITES)
if (config.showRecycleBinAtFolders) {
if (config.useRecycleBin && config.showRecycleBinAtFolders) {
foldersToScan.add(RECYCLE_BIN)
} else {
foldersToScan.remove(RECYCLE_BIN)
@ -778,7 +885,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
return
}
val newMedia = mediaFetcher.getFilesFrom(folder, getImagesOnly, getVideosOnly, getProperDateTaken, favoritePaths)
val newMedia = mediaFetcher.getFilesFrom(folder, getImagesOnly, getVideosOnly, getProperDateTaken, favoritePaths, false)
if (newMedia.isEmpty()) {
continue
}
@ -794,7 +901,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
val newDir = createDirectoryFromMedia(folder, newMedia, albumCovers, hiddenString, includedFolders, isSortingAscending)
dirs.add(newDir)
showSortedDirs(dirs)
setupAdapter(dirs)
mDirectoryDao.insert(newDir)
if (folder != RECYCLE_BIN) {
mMediumDao.insertAll(newMedia)
@ -818,6 +925,11 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
} catch (e: Exception) {
config.everShownFolders = HashSet()
}
mDirs = dirs.clone() as ArrayList<Directory>
if (mDirs.size > 55) {
excludeSpamFolders()
}
}
private fun checkPlaceholderVisibility(dirs: ArrayList<Directory>) {
@ -826,15 +938,6 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
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,
includedFolders: MutableSet<String>, isSortingAscending: Boolean): Directory {
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 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 dateTaken = if (isSortingAscending) Math.min(firstItem.taken, lastItem.taken) else Math.max(firstItem.taken, lastItem.taken)
val size = curMedia.sumByLong { it.size }
val mediaTypes = curMedia.getDirMediaTypes()
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 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) {
initZoomListener()
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) {
val path = (it as Directory).path
if (path != config.tempFolderPath) {
itemClicked(path)
DirectoryAdapter(this, dirsToShow, this, directories_grid, isPickIntent(intent) || isGetAnyContentIntent(intent), fastscroller) {
val clickedDir = it as Directory
val path = clickedDir.path
if (clickedDir.subfoldersCount == 1 || !config.groupDirectSubfolders) {
if (path != config.tempFolderPath) {
itemClicked(path)
}
} else {
mCurrentPathPrefix = path
mOpenedSubfolders.add(path)
setupAdapter(mDirs, "")
}
}.apply {
setupZoomListener(mZoomListener)
directories_grid.adapter = this
runOnUiThread {
directories_grid.adapter = this
setupScrollDirection()
}
}
measureRecyclerViewContent(dirsToShow)
} 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 {
measureRecyclerViewContent(this)
}
setupScrollDirection()
// recyclerview sometimes becomes empty at init/update, triggering an invisible refresh like this seems to work fine
directories_grid.postDelayed({
directories_grid.scrollBy(0, 0)
}, 500)
}
private fun setupScrollDirection() {
@ -915,7 +1034,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
invalidDirs.add(it)
} else if (it.path != config.tempFolderPath) {
val children = if (it.path.startsWith(OTG_PATH)) getOTGFolderChildrenNames(it.path) else File(it.path).list()?.asList()
val hasMediaFile = children?.any { it.isMediaFile() } ?: false
val hasMediaFile = children?.any { it?.isMediaFile() == true } ?: false
if (!hasMediaFile) {
invalidDirs.add(it)
}
@ -938,7 +1057,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
if (invalidDirs.isNotEmpty()) {
dirs.removeAll(invalidDirs)
showSortedDirs(dirs)
setupAdapter(dirs)
invalidDirs.forEach {
mDirectoryDao.deleteDirPath(it.path)
}
@ -959,7 +1078,7 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
}
private fun checkLastMediaChanged() {
if (isActivityDestroyed()) {
if (isDestroyed) {
return
}
@ -981,18 +1100,64 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
}, LAST_MEDIA_CHECK_PERIOD)
}
private fun checkRecycleBinItems() {
if (config.useRecycleBin && config.lastBinCheck < System.currentTimeMillis() - DAY_SECONDS * 1000) {
config.lastBinCheck = System.currentTimeMillis()
Handler().postDelayed({
Thread {
mMediumDao.deleteOldRecycleBinItems(System.currentTimeMillis() - MONTH_MILLISECONDS)
try {
mMediumDao.deleteOldRecycleBinItems(System.currentTimeMillis() - MONTH_MILLISECONDS)
} catch (e: Exception) {
}
}.start()
}, 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() {
getDirectories()
}
@ -1012,55 +1177,11 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener {
private fun checkWhatsNewDialog() {
arrayListOf<Release>().apply {
add(Release(46, R.string.release_46))
add(Release(47, R.string.release_47))
add(Release(49, R.string.release_49))
add(Release(50, R.string.release_50))
add(Release(51, R.string.release_51))
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))
add(Release(213, R.string.release_213))
add(Release(217, R.string.release_217))
add(Release(220, R.string.release_220))
add(Release(221, R.string.release_221))
add(Release(225, R.string.release_225))
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.SearchManager
@ -9,42 +9,39 @@ import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
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.MenuItem
import android.view.ViewGroup
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.request.RequestOptions
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.commons.helpers.REQUEST_EDIT_IMAGE
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.MediaAdapter
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.dialogs.ChangeGroupingDialog
import com.simplemobiletools.gallery.dialogs.ChangeSortingDialog
import com.simplemobiletools.gallery.dialogs.ExcludeFolderDialog
import com.simplemobiletools.gallery.dialogs.FilterMediaDialog
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.interfaces.DirectoryDao
import com.simplemobiletools.gallery.interfaces.MediaOperationsListener
import com.simplemobiletools.gallery.interfaces.MediumDao
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem
import com.simplemobiletools.gallery.models.ThumbnailSection
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.MediaAdapter
import com.simplemobiletools.gallery.pro.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.pro.databases.GalleryDatabase
import com.simplemobiletools.gallery.pro.dialogs.*
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.interfaces.DirectoryDao
import com.simplemobiletools.gallery.pro.interfaces.MediaOperationsListener
import com.simplemobiletools.gallery.pro.interfaces.MediumDao
import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import com.simplemobiletools.gallery.pro.models.ThumbnailSection
import kotlinx.android.synthetic.main.activity_media.*
import java.io.File
import java.io.IOException
@ -116,6 +113,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
media_empty_text.setOnClickListener {
showFilterMediaDialog()
}
updateWidgets()
}
override fun onStart() {
@ -154,7 +153,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
media_horizontal_fastscroller.allowBubbleDisplay = config.showInfoBubble
media_vertical_fastscroller.allowBubbleDisplay = config.showInfoBubble
media_refresh_layout.isEnabled = config.enablePullToRefresh
tryloadGallery()
tryLoadGallery()
invalidateOptionsMenu()
media_empty_text_label.setTextColor(config.textColor)
media_empty_text.setTextColor(getAdjustedPrimaryColor())
@ -191,6 +190,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
if (config.showAll && !isChangingConfigurations) {
config.temporarilyShowHidden = false
config.tempSkipDeleteConfirmation = false
GalleryDatabase.destroyInstance()
}
mTempShowHiddenHandler.removeCallbacksAndMessages(null)
@ -219,10 +219,10 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden
findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden
findItem(R.id.increase_column_count).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID && config.mediaColumnCnt < MAX_COLUMN_COUNT
findItem(R.id.reduce_column_count).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID && config.mediaColumnCnt > 1
findItem(R.id.toggle_filename).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID
val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath)
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 = viewType == VIEW_TYPE_GRID
}
setupSearch(menu)
@ -248,6 +248,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
R.id.stop_showing_hidden -> tryToggleTemporarilyShowHidden()
R.id.increase_column_count -> increaseColumnCount()
R.id.reduce_column_count -> reduceColumnCount()
R.id.slideshow -> startSlideshow()
R.id.settings -> launchSettings()
R.id.about -> launchAbout()
else -> return super.onOptionsItemSelected(item)
@ -255,6 +256,19 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
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() {
config.apply {
mStoredAnimateGifs = animateGifs
@ -306,23 +320,26 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
private fun searchQueryChanged(text: String) {
Thread {
val filtered = mMedia.filter { it is Medium && it.name.contains(text, true) } as ArrayList
filtered.sortBy { it is Medium && !it.name.startsWith(text, true) }
val grouped = MediaFetcher(applicationContext).groupMedia(filtered as ArrayList<Medium>, mPath)
runOnUiThread {
getMediaAdapter()?.updateMedia(grouped)
measureRecyclerViewContent(grouped)
try {
val filtered = mMedia.filter { it is Medium && it.name.contains(text, true) } as ArrayList
filtered.sortBy { it is Medium && !it.name.startsWith(text, true) }
val grouped = MediaFetcher(applicationContext).groupMedia(filtered as ArrayList<Medium>, mPath)
runOnUiThread {
getMediaAdapter()?.updateMedia(grouped)
measureRecyclerViewContent(grouped)
}
} catch (ignored: Exception) {
}
}.start()
}
private fun tryloadGallery() {
private fun tryLoadGallery() {
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
val dirName = when {
mPath == FAVORITES -> getString(R.string.favorites)
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('/')
else -> getHumanizedFilename(mPath)
}
@ -348,7 +365,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
initZoomListener()
val fastscroller = if (config.scrollHorizontally) media_horizontal_fastscroller else media_vertical_fastscroller
MediaAdapter(this, mMedia.clone() as ArrayList<ThumbnailItem>, this, mIsGetImageIntent || mIsGetVideoIntent || mIsGetAnyIntent,
mAllowPickingMultiple, media_grid, fastscroller) {
mAllowPickingMultiple, mPath, media_grid, fastscroller) {
if (it is Medium) {
itemClicked(it.path)
}
@ -366,7 +383,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
}
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.beGoneIf(allowHorizontalScroll)
@ -397,7 +415,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
}
private fun checkLastMediaChanged() {
if (isActivityDestroyed()) {
if (isDestroyed) {
return
}
@ -474,12 +492,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
}
private fun changeViewType() {
val items = arrayListOf(
RadioItem(VIEW_TYPE_GRID, getString(R.string.grid)),
RadioItem(VIEW_TYPE_LIST, getString(R.string.list)))
RadioGroupDialog(this, items, config.viewTypeFiles) {
config.viewTypeFiles = it as Int
ChangeViewTypeDialog(this, false, mPath) {
invalidateOptionsMenu()
setupLayoutManager()
media_grid.adapter = null
@ -545,7 +558,9 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
}
mIsGettingMedia = true
if (!mLoadedInitialPhotos) {
if (mLoadedInitialPhotos) {
startAsyncTask()
} else {
getCachedMedia(mPath, mIsGetVideoIntent, mIsGetImageIntent, mMediumDao) {
if (it.isEmpty()) {
runOnUiThread {
@ -556,9 +571,6 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
}
startAsyncTask()
}
} else {
media_refresh_layout.isRefreshing = true
startAsyncTask()
}
mLoadedInitialPhotos = true
@ -568,7 +580,15 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
mCurrAsyncTask?.stopFetching()
mCurrAsyncTask = GetMediaAsynctask(applicationContext, mPath, mIsGetImageIntent, mIsGetVideoIntent, mShowAll) {
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()
}
@ -619,7 +639,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
}
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()
} else {
setupListLayoutManager()
@ -629,10 +650,10 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
private fun setupGridLayoutManager() {
val layoutManager = media_grid.layoutManager as MyGridLayoutManager
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)
} else {
layoutManager.orientation = GridLayoutManager.VERTICAL
layoutManager.orientation = RecyclerView.VERTICAL
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 hasSections = config.getFolderGrouping(pathToCheck) and GROUP_BY_NONE == 0 && !config.scrollHorizontally
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
?: 0
val thumbnailHeight = if (hasSections) layoutManager.getChildAt(1)?.height ?: 0 else layoutManager.getChildAt(0)?.height ?: 0
var fullHeight = 0
var curSectionItems = 0
@ -696,7 +716,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
}
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
mZoomListener = object : MyRecyclerView.MyZoomListener {
override fun zoomIn() {
@ -721,7 +742,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
private fun setupListLayoutManager() {
val layoutManager = media_grid.layoutManager as MyGridLayoutManager
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)
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
checkLastMediaChanged()
mMedia = media
@ -814,7 +835,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
media_empty_text.beVisibleIf(media.isEmpty() && !isFromCache)
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_horizontal_fastscroller.beVisibleIf(media_grid.isVisible() && allowHorizontalScroll)
setupAdapter()
@ -832,11 +854,15 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
}
override fun tryDeleteFiles(fileDirItems: ArrayList<FileDirItem>) {
val filtered = fileDirItems.filter { it.path.isMediaFile() } as ArrayList
val deletingItems = resources.getQuantityString(R.plurals.deleting_items, filtered.size, filtered.size)
toast(deletingItems)
val filtered = fileDirItems.filter { File(it.path).isFile && it.path.isMediaFile() } as ArrayList
if (filtered.isEmpty()) {
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) {
if (it) {
deleteFilteredFiles(filtered)
@ -845,6 +871,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
}
}
} else {
val deletingItems = resources.getQuantityString(R.plurals.deleting_items, filtered.size, filtered.size)
toast(deletingItems)
deleteFilteredFiles(filtered)
}
}
@ -861,8 +889,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener {
Thread {
val useRecycleBin = config.useRecycleBin
filtered.forEach {
if (!useRecycleBin) {
mMediumDao.deleteMediumPath(it.path)
if (it.path.startsWith(recycleBinPath) || !useRecycleBin) {
deleteDBPath(mMediumDao, it.path)
}
}
}.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.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.VrPanoramaView
import com.simplemobiletools.commons.extensions.beVisible
import com.simplemobiletools.commons.extensions.onGlobalLayout
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.PATH
import kotlinx.android.synthetic.main.activity_panorama.*
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.PATH
import kotlinx.android.synthetic.main.activity_panorama_photo.*
open class PanoramaActivity : SimpleActivity() {
open class PanoramaPhotoActivity : SimpleActivity() {
private val CARDBOARD_DISPLAY_MODE = 3
private var isFullScreen = false
private var isFullscreen = false
private var isExploreEnabled = true
private var isRendering = false
@ -30,8 +31,10 @@ open class PanoramaActivity : SimpleActivity() {
useDynamicTheme = false
requestWindowFeature(Window.FEATURE_NO_TITLE)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_panorama)
setContentView(R.layout.activity_panorama_photo)
supportActionBar?.hide()
checkNotchSupport()
setupButtonMargins()
cardboard.setOnClickListener {
@ -61,6 +64,8 @@ open class PanoramaActivity : SimpleActivity() {
if (config.blackBackground) {
updateStatusbarColor(Color.BLACK)
}
window.statusBarColor = resources.getColor(R.color.circle_black_background)
}
override fun onPause() {
@ -97,11 +102,6 @@ open class PanoramaActivity : SimpleActivity() {
loadImageFromBitmap(bitmap, options)
setFlingingEnabled(true)
setPureTouchTracking(true)
setEventListener(object : VrPanoramaEventListener() {
override fun onClick() {
handleClick()
}
})
// add custom buttons so we can position them and toggle visibility as desired
setFullscreenButtonEnabled(false)
@ -112,6 +112,12 @@ open class PanoramaActivity : SimpleActivity() {
setOnClickListener {
handleClick()
}
setEventListener(object : VrPanoramaEventListener() {
override fun onClick() {
handleClick()
}
})
}
}
}.start()
@ -120,7 +126,7 @@ open class PanoramaActivity : SimpleActivity() {
}
window.decorView.setOnSystemUiVisibilityChangeListener { visibility ->
isFullScreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0
isFullscreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0
toggleButtonVisibility()
}
}
@ -148,26 +154,30 @@ open class PanoramaActivity : SimpleActivity() {
}
private fun setupButtonMargins() {
val navBarHeight = navigationBarHeight
(cardboard.layoutParams as RelativeLayout.LayoutParams).apply {
bottomMargin = navigationBarHeight
bottomMargin = navBarHeight
rightMargin = navigationBarWidth
}
(explore.layoutParams as RelativeLayout.LayoutParams).bottomMargin = navigationBarHeight
cardboard.onGlobalLayout {
panorama_gradient_background.layoutParams.height = navBarHeight + cardboard.height
}
}
private fun toggleButtonVisibility() {
cardboard.animate().alpha(if (isFullScreen) 0f else 1f)
cardboard.isClickable = !isFullScreen
explore.animate().alpha(if (isFullScreen) 0f else 1f)
explore.isClickable = !isFullScreen
arrayOf(cardboard, explore, panorama_gradient_background).forEach {
it.animate().alpha(if (isFullscreen) 0f else 1f)
it.isClickable = !isFullscreen
}
}
private fun handleClick() {
isFullScreen = !isFullScreen
isFullscreen = !isFullscreen
toggleButtonVisibility()
if (isFullScreen) {
if (isFullscreen) {
hideSystemUI(false)
} else {
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

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities
package com.simplemobiletools.gallery.pro.activities
import android.content.Intent
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.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.commons.helpers.REAL_FILE_PATH
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.fragments.PhotoFragment
import com.simplemobiletools.gallery.fragments.VideoFragment
import com.simplemobiletools.gallery.fragments.ViewPagerFragment
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.*
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.*
import com.simplemobiletools.gallery.pro.models.Medium
import kotlinx.android.synthetic.main.bottom_actions.*
import kotlinx.android.synthetic.main.fragment_holder.*
import java.io.File
import java.io.FileInputStream
open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentListener {
private var mMedium: Medium? = null
private var mIsFullScreen = false
private var mIsFromGallery = false
@ -38,7 +39,6 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_holder)
setTranslucentNavigation()
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
@ -52,7 +52,15 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
override fun 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) {
updateStatusbarColor(Color.BLACK)
}
@ -60,22 +68,36 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
private fun checkIntent(savedInstanceState: Bundle? = null) {
mUri = intent.data ?: return
if (intent.extras?.containsKey(REAL_FILE_PATH) == true) {
val realPath = intent.extras.getString(REAL_FILE_PATH)
sendViewPagerIntent(realPath)
finish()
var filename = getFilenameFromUri(mUri!!)
mIsFromGallery = intent.getBooleanExtra(IS_FROM_GALLERY, false)
if (mIsFromGallery && filename.isVideoFast() && config.openVideosOnSeparateScreen) {
launchVideoPlayer()
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") {
scanPathRecursively(mUri!!.path)
sendViewPagerIntent(mUri!!.path)
finish()
return
if (filename.contains('.')) {
scanPathRecursively(mUri!!.path)
sendViewPagerIntent(mUri!!.path)
finish()
return
}
} else {
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)
sendViewPagerIntent(path)
finish()
@ -83,10 +105,10 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
}
}
checkNotchSupport()
showSystemUI(true)
val bundle = Bundle()
val file = File(mUri.toString())
val filename = getFilenameFromUri(mUri!!)
val type = when {
filename.isVideoFast() -> TYPE_VIDEOS
filename.isGif() -> TYPE_GIFS
@ -95,7 +117,8 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
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
bundle.putSerializable(MEDIUM, mMedium)
@ -103,7 +126,7 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
mFragment = if (mIsVideo) VideoFragment() else PhotoFragment()
mFragment!!.listener = this
mFragment!!.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.fragment_placeholder, mFragment).commit()
supportFragmentManager.beginTransaction().replace(R.id.fragment_placeholder, mFragment!!).commit()
}
if (config.blackBackground) {
@ -118,6 +141,46 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
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?) {
super.onConfigurationChanged(newConfig)
initBottomActionsLayout()
@ -181,7 +244,8 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
}
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()
}
@ -214,8 +278,10 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList
showSystemUI(true)
}
val newAlpha = if (mIsFullScreen) 0f else 1f
top_shadow.animate().alpha(newAlpha).start()
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 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.app.Activity
@ -10,11 +10,10 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.isActivityDestroyed
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.pro.R
import com.theartofdev.edmodo.cropper.CropImageView
import kotlinx.android.synthetic.main.activity_set_wallpaper.*
import kotlinx.android.synthetic.main.bottom_set_wallpaper_actions.*
@ -113,7 +112,7 @@ class SetWallpaperActivity : SimpleActivity(), CropImageView.OnCropImageComplete
@SuppressLint("NewApi")
override fun onCropImageComplete(view: CropImageView?, result: CropImageView.CropResult) {
if (isActivityDestroyed())
if (isDestroyed)
return
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.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() {
override fun getAppIconIDs() = arrayListOf(
@ -27,4 +31,19 @@ open class SimpleActivity : BaseSimpleActivity() {
)
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 com.simplemobiletools.commons.activities.BaseSplashActivity
class SplashActivity : BaseSplashActivity() {
override fun getAppPackageName() = "-1"
override fun initActivity() {
startActivity(Intent(this, MainActivity::class.java))
finish()

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.activities
package com.simplemobiletools.gallery.pro.activities
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.ValueAnimator
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Activity
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.drawable.ColorDrawable
import android.media.ExifInterface
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.provider.MediaStore
import android.support.v4.view.ViewPager
import android.util.DisplayMetrics
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.WindowManager
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.RenameItemDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.MyPagerAdapter
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.dialogs.DeleteWithRememberDialog
import com.simplemobiletools.gallery.dialogs.SaveAsDialog
import com.simplemobiletools.gallery.dialogs.SlideshowDialog
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.fragments.PhotoFragment
import com.simplemobiletools.gallery.fragments.VideoFragment
import com.simplemobiletools.gallery.fragments.ViewPagerFragment
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem
import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.MyPagerAdapter
import com.simplemobiletools.gallery.pro.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.pro.dialogs.DeleteWithRememberDialog
import com.simplemobiletools.gallery.pro.dialogs.SaveAsDialog
import com.simplemobiletools.gallery.pro.dialogs.SlideshowDialog
import com.simplemobiletools.gallery.pro.extensions.*
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.*
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.bottom_actions.*
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.util.*
class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, ViewPagerFragment.FragmentListener {
private val REQUEST_VIEW_VIDEO = 1
private var mPath = ""
private var mDirectory = ""
private var mIsFullScreen = false
private var mPos = -1
private var mShowAll = false
private var mIsSlideshowActive = false
private var mRotationDegrees = 0
private var mPrevHashcode = 0
private var mSlideshowHandler = Handler()
@ -69,19 +61,18 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private var mSlideshowMoveBackwards = false
private var mSlideshowMedia = mutableListOf<Medium>()
private var mAreSlideShowMediaVisible = false
private var mIsOrientationLocked = false
private var mMediaFiles = ArrayList<Medium>()
private var mFavoritePaths = ArrayList<String>()
companion object {
var screenWidth = 0
var screenHeight = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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 }
handlePermission(PERMISSION_WRITE_STORAGE) {
@ -96,7 +87,6 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
initFavorites()
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun onResume() {
super.onResume()
if (!hasPermission(PERMISSION_WRITE_STORAGE)) {
@ -105,15 +95,12 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}
if (config.bottomActions) {
if (isLollipopPlus()) {
window.navigationBarColor = Color.TRANSPARENT
}
window.navigationBarColor = Color.TRANSPARENT
} else {
setTranslucentNavigation()
}
initBottomActions()
supportActionBar?.setBackgroundDrawable(resources.getDrawable(R.drawable.actionbar_gradient_background))
if (config.maxBrightness) {
val attributes = window.attributes
@ -121,12 +108,11 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
window.attributes = attributes
}
setupRotation()
setupOrientation()
invalidateOptionsMenu()
if (config.blackBackground) {
updateStatusbarColor(Color.BLACK)
}
supportActionBar?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
window.statusBarColor = Color.TRANSPARENT
}
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() {
measureScreen()
val uri = intent.data
if (uri != null) {
var cursor: Cursor? = null
@ -191,7 +250,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
if (intent.extras?.containsKey(IS_VIEW_INTENT) == true) {
if (isShowHiddenFlagNeeded()) {
if (!config.isPasswordProtectionOn) {
if (!config.isHiddenPasswordProtectionOn) {
config.temporarilyShowHidden = true
}
}
@ -214,9 +273,10 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
supportActionBar?.title = mPath.getFilenameFromPath()
view_pager.onGlobalLayout {
if (!isActivityDestroyed()) {
if (!isDestroyed) {
if (mMediaFiles.isNotEmpty()) {
gotMedia(mMediaFiles as ArrayList<ThumbnailItem>)
checkSlideshowOnEnter()
}
}
}
@ -232,7 +292,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
view_pager.onGlobalLayout {
Handler().postDelayed({
fragmentClicked()
}, 500)
}, HIDE_SYSTEM_UI_DELAY)
}
}
@ -246,8 +306,10 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
view_pager.adapter?.let {
(it as MyPagerAdapter).toggleFullscreen(mIsFullScreen)
checkSystemUI()
if (!bottom_actions.isGone()) {
bottom_actions.animate().alpha(if (mIsFullScreen) 0f else 1f).start()
val newAlpha = if (mIsFullScreen) 0f else 1f
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,
bottom_slideshow, bottom_show_on_map, bottom_toggle_file_visibility, bottom_rename).forEach {
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() {
@ -268,7 +348,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}.start()
}
private fun setupRotation() {
private fun setupOrientation() {
if (!mIsOrientationLocked) {
if (config.screenRotation == ROTATE_BY_DEVICE_ROTATION) {
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>) {
val pagerAdapter = MyPagerAdapter(this, supportFragmentManager, media)
if (!isActivityDestroyed()) {
if (!isDestroyed) {
view_pager.apply {
adapter = pagerAdapter
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() {
SlideshowDialog(this) {
startSlideshow()
@ -371,7 +385,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private fun startSlideshow() {
if (getMediaForSlideshow()) {
view_pager.onGlobalLayout {
if (!isActivityDestroyed()) {
if (!isDestroyed) {
hideSystemUI(true)
mSlideshowInterval = config.slideshowInterval
mSlideshowMoveBackwards = config.slideshowMoveBackwards
@ -421,7 +435,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
val dragOffset = dragPosition - oldDragPosition
oldDragPosition = dragPosition
try {
view_pager.fakeDragBy(dragOffset * (if (forward) 1f else -1f))
view_pager.fakeDragBy(dragOffset * (if (forward) -1f else 1f))
} catch (e: Exception) {
stopSlideshow()
}
@ -461,7 +475,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
if (mIsSlideshowActive) {
if (getCurrentMedium()!!.isImage() || getCurrentMedium()!!.isGIF()) {
mSlideshowHandler.postDelayed({
if (mIsSlideshowActive && !isActivityDestroyed()) {
if (mIsSlideshowActive && !isDestroyed) {
swipeToNextMedium()
}
}, mSlideshowInterval * 1000L)
@ -476,18 +490,9 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}
private fun getMediaForSlideshow(): Boolean {
mSlideshowMedia = mMediaFiles.toMutableList()
if (!config.slideshowIncludePhotos) {
mSlideshowMedia = mSlideshowMedia.filter { !it.isImage() } as MutableList
}
if (!config.slideshowIncludeVideos) {
mSlideshowMedia = mSlideshowMedia.filter { it.isImage() || it.isGIF() } as MutableList
}
if (!config.slideshowIncludeGIFs) {
mSlideshowMedia = mSlideshowMedia.filter { !it.isGIF() } as MutableList
}
mSlideshowMedia = mMediaFiles.filter {
it.isImage() || (config.slideshowIncludeVideos && it.isVideo() || (config.slideshowIncludeGIFs && it.isGIF()))
}.toMutableList()
if (config.slideshowRandomOrder) {
mSlideshowMedia.shuffle()
@ -507,13 +512,25 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}
}
private fun moveFileTo() {
handleDeletePasswordProtection {
copyMoveTo(false)
}
}
private fun copyMoveTo(isCopyOperation: Boolean) {
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()))
tryCopyMoveFilesTo(fileDirItems, isCopyOperation) {
config.tempFolderPath = ""
if (!isCopyOperation) {
refreshViewPager()
updateFavoritePaths(fileDirItems, it)
}
}
}
@ -534,10 +551,18 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}
private fun rotateImage(degrees: Int) {
mRotationDegrees = (mRotationDegrees + degrees) % 360
getCurrentFragment()?.let {
(it as? PhotoFragment)?.rotateImageViewBy(mRotationDegrees)
val currentPath = getCurrentPath()
if (needsStupidWritePermissions(currentPath)) {
handleSAFDialog(currentPath) {
rotateBy(degrees)
}
} else {
rotateBy(degrees)
}
}
private fun rotateBy(degrees: Int) {
getCurrentPhotoFragment()?.rotateImageViewBy(degrees)
supportInvalidateOptionsMenu()
}
@ -563,110 +588,20 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
val currPath = getCurrentPath()
SaveAsDialog(this, currPath, false) {
handleSAFDialog(it) {
toast(R.string.saving)
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()
}
}
}
private fun saveImageToFile(oldPath: String, newPath: String) {
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 getCurrentPhotoFragment() = getCurrentFragment() as? PhotoFragment
private fun isShowHiddenFlagNeeded(): Boolean {
val file = File(mPath)
@ -839,6 +774,11 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
bottom_set_as.setOnClickListener {
setAs(getCurrentPath())
}
bottom_copy.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_COPY != 0)
bottom_copy.setOnClickListener {
copyMoveTo(true)
}
}
private fun updateBottomActionIcons(medium: Medium?) {
@ -877,15 +817,17 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == REQUEST_EDIT_IMAGE) {
if (resultCode == Activity.RESULT_OK && resultData != null) {
mPos = -1
mPrevHashcode = 0
refreshViewPager()
}
} else if (requestCode == REQUEST_SET_AS) {
if (resultCode == Activity.RESULT_OK) {
toast(R.string.wallpaper_set_successfully)
if (requestCode == REQUEST_EDIT_IMAGE && resultCode == Activity.RESULT_OK && resultData != null) {
mPos = -1
mPrevHashcode = 0
refreshViewPager()
} else if (requestCode == REQUEST_SET_AS && resultCode == Activity.RESULT_OK) {
toast(R.string.wallpaper_set_successfully)
} else if (requestCode == REQUEST_VIEW_VIDEO && resultCode == Activity.RESULT_OK && resultData != null) {
if (resultData.getBooleanExtra(GO_TO_NEXT_ITEM, false)) {
goToNextItem()
} else if (resultData.getBooleanExtra(GO_TO_PREV_ITEM, false)) {
goToPrevItem()
}
}
super.onActivityResult(requestCode, resultCode, resultData)
@ -896,7 +838,11 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
return
}
if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) {
if (config.isDeletePasswordProtectionOn) {
handleDeletePasswordProtection {
deleteConfirmed()
}
} else if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) {
deleteConfirmed()
} else {
askConfirmDelete()
@ -904,8 +850,16 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
}
private fun askConfirmDelete() {
val message = if (config.useRecycleBin && !getCurrentMedium()!!.getIsInRecycleBin()) R.string.are_you_sure_recycle_bin else R.string.are_you_sure_delete
DeleteWithRememberDialog(this, getString(message)) {
val filename = "\"${getCurrentPath().getFilenameFromPath()}\""
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
deleteConfirmed()
}
@ -962,32 +916,19 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
measureScreen()
initBottomActionsLayout()
}
@SuppressLint("NewApi")
private fun measureScreen() {
val metrics = DisplayMetrics()
if (isJellyBean1Plus()) {
windowManager.defaultDisplay.getRealMetrics(metrics)
screenWidth = metrics.widthPixels
screenHeight = metrics.heightPixels
} else {
windowManager.defaultDisplay.getMetrics(metrics)
screenWidth = metrics.widthPixels
screenHeight = metrics.heightPixels
private fun refreshViewPager() {
if (config.getFileSorting(mDirectory) and SORT_BY_RANDOM == 0) {
GetMediaAsynctask(applicationContext, mDirectory, false, false, mShowAll) {
gotMedia(it)
}.execute()
}
}
private fun refreshViewPager() {
GetMediaAsynctask(applicationContext, mDirectory, false, false, mShowAll) {
gotMedia(it)
}.execute()
}
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) {
return
}
@ -1069,6 +1010,34 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
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() {
if (mIsFullScreen) {
hideSystemUI(true)
@ -1098,15 +1067,12 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View
private fun getCurrentPath() = getCurrentMedium()?.path ?: ""
private fun getCurrentFile() = File(getCurrentPath())
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) {
if (mPos != position) {
mPos = position
updateActionbarTitle()
mRotationDegrees = 0
invalidateOptionsMenu()
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.View
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.PropertiesDialog
import com.simplemobiletools.commons.dialogs.RenameItemDialog
import com.simplemobiletools.commons.dialogs.RenameItemsDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.dialogs.ExcludeFolderDialog
import com.simplemobiletools.gallery.dialogs.PickMediumDialog
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.interfaces.DirectoryOperationsListener
import com.simplemobiletools.gallery.models.AlbumCover
import com.simplemobiletools.gallery.models.Directory
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.dialogs.ExcludeFolderDialog
import com.simplemobiletools.gallery.pro.dialogs.PickMediumDialog
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.interfaces.DirectoryOperationsListener
import com.simplemobiletools.gallery.pro.models.AlbumCover
import com.simplemobiletools.gallery.pro.models.Directory
import kotlinx.android.synthetic.main.directory_item_list.view.*
import java.io.File
import java.util.*
import kotlin.collections.ArrayList
class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directory>, val listener: DirectoryOperationsListener?, recyclerView: MyRecyclerView,
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 animateGifs = config.animateGifs
private var cropThumbnails = config.cropThumbnails
private var groupDirectSubfolders = config.groupDirectSubfolders
private var currentDirectoriesHash = dirs.hashCode()
init {
@ -48,14 +47,6 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
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 {
val layoutType = if (isListViewType) R.layout.directory_item_list else R.layout.directory_item_grid
return createViewHolder(layoutType, parent)
@ -63,34 +54,35 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
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)
}
bindViewHolder(holder, position, view)
bindViewHolder(holder)
}
override fun getItemCount() = dirs.size
override fun prepareActionMode(menu: Menu) {
if (getSelectedPaths().isEmpty()) {
val selectedPaths = getSelectedPaths()
if (selectedPaths.isEmpty()) {
return
}
val selectedPaths = getSelectedPaths()
val isOneItemSelected = isOneItemSelected()
menu.apply {
findItem(R.id.cab_rename).isVisible = isOneItemSelected() && !selectedPaths.contains(FAVORITES) && !selectedPaths.contains(RECYCLE_BIN)
findItem(R.id.cab_change_cover_image).isVisible = isOneItemSelected()
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_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_recycle_bin).isVisible = isOneItemSelected && selectedPaths.first() == RECYCLE_BIN
findItem(R.id.cab_empty_disable_recycle_bin).isVisible = isOneItemSelected && selectedPaths.first() == RECYCLE_BIN
checkHideBtnVisibility(this)
checkPinBtnVisibility(this)
checkHideBtnVisibility(this, selectedPaths)
checkPinBtnVisibility(this, selectedPaths)
}
}
override fun actionItemPressed(id: Int) {
if (selectedPositions.isEmpty()) {
if (selectedKeys.isEmpty()) {
return
}
@ -105,7 +97,7 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
R.id.cab_unhide -> toggleFoldersVisibility(false)
R.id.cab_exclude -> tryExcludeFolder()
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_delete -> askConfirmDelete()
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 getItemSelectionKey(position: Int) = dirs.getOrNull(position)?.path?.hashCode()
override fun getItemKeyPosition(key: Int) = dirs.indexOfFirst { it.path.hashCode() == key }
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
if (!activity.isActivityDestroyed()) {
Glide.with(activity).clear(holder.itemView?.dir_thumbnail!!)
if (!activity.isDestroyed) {
Glide.with(activity).clear(holder.itemView.dir_thumbnail!!)
}
}
private fun checkHideBtnVisibility(menu: Menu) {
var hiddenCnt = 0
var unhiddenCnt = 0
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 checkHideBtnVisibility(menu: Menu, selectedPaths: ArrayList<String>) {
menu.findItem(R.id.cab_hide).isVisible = selectedPaths.any { !File(it).doesThisOrParentHaveNoMedia() }
menu.findItem(R.id.cab_unhide).isVisible = selectedPaths.any { File(it).doesThisOrParentHaveNoMedia() }
}
private fun checkPinBtnVisibility(menu: Menu) {
private fun checkPinBtnVisibility(menu: Menu, selectedPaths: ArrayList<String>) {
val pinnedFolders = config.pinnedFolders
var pinnedCnt = 0
var unpinnedCnt = 0
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
menu.findItem(R.id.cab_pin).isVisible = selectedPaths.any { !pinnedFolders.contains(it) }
menu.findItem(R.id.cab_unpin).isVisible = selectedPaths.any { pinnedFolders.contains(it) }
}
private fun showProperties() {
if (selectedPositions.size <= 1) {
val path = dirs[selectedPositions.first()].path
if (selectedKeys.size <= 1) {
val path = getFirstSelectedItemPath() ?: return
if (path != FAVORITES && path != RECYCLE_BIN) {
PropertiesDialog(activity, dirs[selectedPositions.first()].path, config.shouldShowHidden)
PropertiesDialog(activity, path, config.shouldShowHidden)
}
} else {
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() {
val firstDir = dirs[selectedPositions.first()]
val sourcePath = firstDir.path
val dir = File(sourcePath)
if (activity.isAStorageRootFolder(dir.absolutePath)) {
activity.toast(R.string.rename_folder_root)
return
}
if (selectedKeys.size == 1) {
val firstDir = getFirstSelectedItem() ?: return
val sourcePath = firstDir.path
val dir = File(sourcePath)
if (activity.isAStorageRootFolder(dir.absolutePath)) {
activity.toast(R.string.rename_folder_root)
return
}
RenameItemDialog(activity, dir.absolutePath) {
activity.runOnUiThread {
firstDir.apply {
path = it
name = it.getFilenameFromPath()
tmb = File(it, tmb.getFilenameFromPath()).absolutePath
RenameItemDialog(activity, dir.absolutePath) {
activity.runOnUiThread {
firstDir.apply {
path = it
name = it.getFilenameFromPath()
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 {
activity.galleryDB.DirectoryDao().updateDirectoryAfterRename(firstDir.tmb, firstDir.name, firstDir.path, sourcePath)
listener?.refreshItems()
}.start()
}
} else {
val paths = getSelectedPaths().filter { !activity.isAStorageRootFolder(it) } as ArrayList<String>
RenameItemsDialog(activity, paths) {
listener?.refreshItems()
}
}
}
@ -283,18 +266,6 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
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()
dirs = newDirs
@ -330,27 +301,38 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
private fun pinFolders(pin: Boolean) {
if (pin) {
config.addPinnedFolders(getSelectedPaths())
config.addPinnedFolders(getSelectedPaths().toHashSet())
} else {
config.removePinnedFolders(getSelectedPaths())
config.removePinnedFolders(getSelectedPaths().toHashSet())
}
currentDirectoriesHash = 0
pinnedFolders = config.pinnedFolders
listener?.recheckPinnedFolders()
notifyDataSetChanged()
finishActMode()
}
private fun moveFilesTo() {
activity.handleDeletePasswordProtection {
copyMoveTo(false)
}
}
private fun copyMoveTo(isCopyOperation: Boolean) {
val paths = ArrayList<String>()
val showHidden = activity.config.shouldShowHidden
selectedPositions.forEach {
val path = dirs[it].path
if (path.startsWith(OTG_PATH)) {
paths.addAll(getOTGFilePaths(path, showHidden))
} else if (path != FAVORITES) {
File(path).listFiles()?.filter {
!activity.getIsPathDirectory(it.absolutePath) && it.isMediaFile() && (showHidden || !it.name.startsWith('.'))
getSelectedPaths().forEach {
if (it.startsWith(OTG_PATH)) {
paths.addAll(getOTGFilePaths(it, showHidden))
} else if (it != FAVORITES) {
val filter = config.filterMedia
File(it).listFiles()?.filter {
!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 }
}
}
@ -365,8 +347,16 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
private fun getOTGFilePaths(path: String, showHidden: Boolean): ArrayList<String> {
val paths = ArrayList<String>()
activity.getOTGFolderChildren(path)?.forEach {
if (!it.isDirectory && it.name.isMediaFile() && (showHidden || !it.name.startsWith('.'))) {
val filter = config.filterMedia
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}:")
paths.add("$OTG_PATH$relativePath")
}
@ -375,81 +365,80 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
}
private fun askConfirmDelete() {
if (config.skipDeleteConfirmation) {
deleteFolders()
} 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) {
when {
config.isDeletePasswordProtectionOn -> activity.handleDeletePasswordProtection {
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() {
if (selectedPositions.isEmpty()) {
if (selectedKeys.isEmpty()) {
return
}
val folders = ArrayList<File>(selectedPositions.size)
val removeFolders = ArrayList<Directory>(selectedPositions.size)
var SAFPath = ""
selectedPositions.forEach {
if (dirs.size > it) {
val path = dirs[it].path
if (activity.needsStupidWritePermissions(path) && config.treeUri.isEmpty()) {
SAFPath = path
}
val selectedDirs = getSelectedItems()
selectedDirs.forEach {
val path = it.path
if (activity.needsStupidWritePermissions(path) && config.treeUri.isEmpty()) {
SAFPath = path
}
}
activity.handleSAFDialog(SAFPath) {
selectedPositions.sortedDescending().forEach {
val directory = dirs.getOrNull(it)
if (directory != null) {
if (directory.areFavorites() || directory.isRecycleBin()) {
if (directory.isRecycleBin()) {
tryEmptyRecycleBin(false)
} else {
Thread {
activity.galleryDB.MediumDao().clearFavorites()
listener?.refreshItems()
}.start()
}
if (selectedPositions.size == 1) {
finishActMode()
} else {
selectedPositions.remove(it)
toggleItemSelection(false, it)
}
val foldersToDelete = ArrayList<File>(selectedKeys.size)
selectedDirs.forEach {
if (it.areFavorites() || it.isRecycleBin()) {
if (it.isRecycleBin()) {
tryEmptyRecycleBin(false)
} else {
folders.add(File(directory.path))
removeFolders.add(directory)
Thread {
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) {
if (selectedPositions.size != 1)
if (selectedKeys.size != 1)
return
val path = dirs[selectedPositions.first()].path
val path = getFirstSelectedItemPath() ?: return
if (useDefault) {
val albumCovers = getAlbumCoversWithout(path)
@ -480,15 +469,15 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
listener?.refreshItems()
}
private fun getSelectedPaths(): HashSet<String> {
val paths = HashSet<String>(selectedPositions.size)
selectedPositions.forEach {
(dirs.getOrNull(it))?.apply {
paths.add(path)
}
}
return paths
}
private fun getSelectedItems() = dirs.filter { selectedKeys.contains(it.path.hashCode()) } as ArrayList<Directory>
private fun getSelectedPaths() = getSelectedItems().map { it.path } as ArrayList<String>
private fun getFirstSelectedItem() = getItemWithKey(selectedKeys.first())
private fun getFirstSelectedItemPath() = getFirstSelectedItem()?.path
private fun getItemWithKey(key: Int): Directory? = dirs.firstOrNull { it.path.hashCode() == key }
fun updateDirs(newDirs: 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) {
val isSelected = selectedKeys.contains(directory.path.hashCode())
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("/")}/"
photo_cnt.text = directory.mediaCnt.toString()
photo_cnt.text = directory.subfoldersMediaCount.toString()
val thumbnailType = when {
directory.tmb.isVideoFast() -> TYPE_VIDEOS
directory.tmb.isGif() -> TYPE_GIFS
@ -528,6 +518,11 @@ class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList<Directo
else -> TYPE_IMAGES
}
dir_check?.beVisibleIf(isSelected)
if (isSelected) {
dir_check.background?.applyColorFilter(primaryColor)
}
activity.loadImage(thumbnailType, directory.tmb, dir_thumbnail, scrollHorizontally, animateGifs, cropThumbnails)
dir_pin.beVisibleIf(pinnedFolders.contains(directory.path))
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.graphics.drawable.Drawable
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.interfaces.FilterAdapterListener
import com.simplemobiletools.gallery.models.FilterItem
import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.models.FilterItem
import kotlinx.android.synthetic.main.editor_filter_item.view.*
import java.util.*
class FiltersAdapter(val context: Context, val filterItems: ArrayList<FilterItem>, val itemClick: (Int) -> Unit) : RecyclerView.Adapter<FiltersAdapter.ViewHolder>(),
FilterAdapterListener {
class FiltersAdapter(val context: Context, val filterItems: ArrayList<FilterItem>, val itemClick: (Int) -> Unit) : RecyclerView.Adapter<FiltersAdapter.ViewHolder>() {
private var currentSelection = filterItems.first()
private var strokeBackground = context.resources.getDrawable(R.drawable.stroke_background)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindView(filterItems[position], strokeBackground)
holder.bindView(filterItems[position])
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
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 getCurrentFilter() = currentSelection
fun getCurrentFilter() = currentSelection
override fun setCurrentFilter(position: Int) {
private fun setCurrentFilter(position: Int) {
val filterItem = filterItems.getOrNull(position) ?: return
if (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) {
fun bindView(filterItem: FilterItem, strokeBackground: Drawable): View {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindView(filterItem: FilterItem): View {
itemView.apply {
editor_filter_item_label.text = filterItem.filter.name
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
} else {
null
}
setOnClickListener {
filterAdapterListener.setCurrentFilter(adapterPosition)
setCurrentFilter(adapterPosition)
}
}
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.View
@ -7,8 +7,8 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import kotlinx.android.synthetic.main.item_manage_folder.view.*
import java.util.*
@ -25,12 +25,6 @@ class ManageFoldersAdapter(activity: BaseSimpleActivity, var folders: ArrayList<
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) {
when (id) {
R.id.cab_remove -> removeSelection()
@ -41,20 +35,27 @@ class ManageFoldersAdapter(activity: BaseSimpleActivity, var folders: ArrayList<
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 onBindViewHolder(holder: ViewHolder, position: Int) {
val folder = folders[position]
val view = holder.bindView(folder, true, true) { itemView, adapterPosition ->
holder.bindView(folder, true, true) { itemView, adapterPosition ->
setupView(itemView, folder)
}
bindViewHolder(holder, position, view)
bindViewHolder(holder)
}
override fun getItemCount() = folders.size
private fun getSelectedItems() = folders.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<String>
private fun setupView(view: View, folder: String) {
view.apply {
manage_folder_holder?.isSelected = selectedKeys.contains(folder.hashCode())
manage_folder_title.apply {
text = folder
setTextColor(config.textColor)
@ -63,20 +64,20 @@ class ManageFoldersAdapter(activity: BaseSimpleActivity, var folders: ArrayList<
}
private fun removeSelection() {
val removeFolders = ArrayList<String>(selectedPositions.size)
val removeFolders = ArrayList<String>(selectedKeys.size)
val positions = getSelectedItemPositions()
selectedPositions.sortedDescending().forEach {
val folder = folders[it]
removeFolders.add(folder)
getSelectedItems().forEach {
removeFolders.add(it)
if (isShowingExcludedFolders) {
config.removeExcludedFolder(folder)
config.removeExcludedFolder(it)
} else {
config.removeIncludedFolder(folder)
config.removeIncludedFolder(it)
}
}
folders.removeAll(removeFolders)
removeSelectedItems()
removeSelectedItems(positions)
if (folders.isEmpty()) {
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.View
@ -8,9 +8,9 @@ import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.extensions.isPathOnSD
import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.extensions.removeNoMedia
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.extensions.removeNoMedia
import kotlinx.android.synthetic.main.item_manage_folder.view.*
import java.util.*
@ -27,12 +27,6 @@ class ManageHiddenFoldersAdapter(activity: BaseSimpleActivity, var folders: Arra
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) {
when (id) {
R.id.cab_unhide -> tryUnhideFolders()
@ -43,20 +37,27 @@ class ManageHiddenFoldersAdapter(activity: BaseSimpleActivity, var folders: Arra
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 onBindViewHolder(holder: ViewHolder, position: Int) {
val folder = folders[position]
val view = holder.bindView(folder, true, true) { itemView, adapterPosition ->
holder.bindView(folder, true, true) { itemView, adapterPosition ->
setupView(itemView, folder)
}
bindViewHolder(holder, position, view)
bindViewHolder(holder)
}
override fun getItemCount() = folders.size
private fun getSelectedItems() = folders.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<String>
private fun setupView(view: View, folder: String) {
view.apply {
manage_folder_holder?.isSelected = selectedKeys.contains(folder.hashCode())
manage_folder_title.apply {
text = folder
setTextColor(config.textColor)
@ -65,12 +66,12 @@ class ManageHiddenFoldersAdapter(activity: BaseSimpleActivity, var folders: Arra
}
private fun tryUnhideFolders() {
val removeFolders = ArrayList<String>(selectedPositions.size)
val removeFolders = ArrayList<String>(selectedKeys.size)
val sdCardPaths = ArrayList<String>()
selectedPositions.forEach {
if (activity.isPathOnSD(folders[it])) {
sdCardPaths.add(folders[it])
getSelectedItems().forEach {
if (activity.isPathOnSD(it)) {
sdCardPaths.add(it)
}
}
@ -84,14 +85,14 @@ class ManageHiddenFoldersAdapter(activity: BaseSimpleActivity, var folders: Arra
}
private fun unhideFolders(removeFolders: ArrayList<String>) {
selectedPositions.sortedDescending().forEach {
val folder = folders[it]
removeFolders.add(folder)
activity.removeNoMedia(folder)
val position = getSelectedItemPositions()
getSelectedItems().forEach {
removeFolders.add(it)
activity.removeNoMedia(it)
}
folders.removeAll(removeFolders)
removeSelectedItems()
removeSelectedItems(position)
if (folders.isEmpty()) {
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.Looper
import android.provider.MediaStore
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.bumptech.glide.Glide
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.dialogs.PropertiesDialog
import com.simplemobiletools.commons.dialogs.RenameItemDialog
import com.simplemobiletools.commons.dialogs.RenameItemsDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.OTG_PATH
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.dialogs.DeleteWithRememberDialog
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.VIEW_TYPE_LIST
import com.simplemobiletools.gallery.interfaces.MediaOperationsListener
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem
import com.simplemobiletools.gallery.models.ThumbnailSection
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.dialogs.DeleteWithRememberDialog
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL
import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_LIST
import com.simplemobiletools.gallery.pro.interfaces.MediaOperationsListener
import com.simplemobiletools.gallery.pro.models.Medium
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.thumbnail_section.view.*
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
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) {
private val INSTANT_LOAD_DURATION = 2000L
private val IMAGE_LOAD_DELAY = 100L
private val BATCH_SIZE = 100
private val ITEM_SECTION = 0
private val ITEM_MEDIUM = 1
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 rotatedImagePaths = ArrayList<String>()
private var loadImageInstantly = false
private var delayHandler = Handler(Looper.getMainLooper())
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 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 {
val layoutType = if (viewType == ITEM_SECTION) {
R.layout.thumbnail_section
@ -89,15 +80,15 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
visibleItemPaths.add(tmbItem.path)
}
val allowLongPress = !allowMultiplePicks && tmbItem is Medium
val view = holder.bindView(tmbItem, tmbItem is Medium, allowLongPress) { itemView, adapterPosition ->
val allowLongPress = (!isAGetIntent || allowMultiplePicks) && tmbItem is Medium
holder.bindView(tmbItem, tmbItem is Medium, allowLongPress) { itemView, adapterPosition ->
if (tmbItem is Medium) {
setupThumbnail(itemView, tmbItem)
} else {
setupSection(itemView, tmbItem as ThumbnailSection)
}
}
bindViewHolder(holder, position, view)
bindViewHolder(holder)
}
override fun getItemCount() = media.size
@ -112,19 +103,26 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
}
override fun prepareActionMode(menu: Menu) {
menu.apply {
findItem(R.id.cab_rename).isVisible = isOneItemSelected() && getSelectedMedia().firstOrNull()?.getIsInRecycleBin() == false
findItem(R.id.cab_open_with).isVisible = isOneItemSelected()
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) }
val selectedItems = getSelectedItems()
if (selectedItems.isEmpty()) {
return
}
checkHideBtnVisibility(this)
checkFavoriteBtnVisibility(this)
val isOneItemSelected = isOneItemSelected()
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) {
if (selectedPositions.isEmpty()) {
if (selectedKeys.isEmpty()) {
return
}
@ -139,12 +137,15 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
R.id.cab_remove_from_favorites -> toggleFavorites(false)
R.id.cab_restore_recycle_bin_files -> restoreFiles()
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_move_to -> copyMoveTo(false)
R.id.cab_move_to -> moveFilesTo()
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_set_as -> activity.setAs(getCurrentPath())
R.id.cab_set_as -> setAs()
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 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) {
super.onViewRecycled(holder)
if (!activity.isActivityDestroyed()) {
if (!activity.isDestroyed) {
val itemView = holder.itemView
visibleItemPaths.remove(itemView?.photo_name?.tag)
val tmb = itemView?.medium_thumbnail
visibleItemPaths.remove(itemView.medium_name?.tag)
val tmb = itemView.medium_thumbnail
if (tmb != null) {
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
private fun checkHideBtnVisibility(menu: Menu) {
var hiddenCnt = 0
var unhiddenCnt = 0
getSelectedMedia().forEach {
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 checkHideBtnVisibility(menu: Menu, selectedItems: ArrayList<Medium>) {
val isInRecycleBin = selectedItems.firstOrNull()?.getIsInRecycleBin() == true
menu.findItem(R.id.cab_hide).isVisible = !isInRecycleBin && selectedItems.any { !it.isHidden() }
menu.findItem(R.id.cab_unhide).isVisible = !isInRecycleBin && selectedItems.any { it.isHidden() }
}
private fun checkFavoriteBtnVisibility(menu: Menu) {
var favoriteCnt = 0
var nonFavoriteCnt = 0
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 checkFavoriteBtnVisibility(menu: Menu, selectedItems: ArrayList<Medium>) {
menu.findItem(R.id.cab_add_to_favorites).isVisible = selectedItems.any { !it.isFavorite }
menu.findItem(R.id.cab_remove_from_favorites).isVisible = selectedItems.any { it.isFavorite }
}
private fun confirmSelection() {
@ -202,8 +188,9 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
}
private fun showProperties() {
if (selectedPositions.size <= 1) {
PropertiesDialog(activity, (media[selectedPositions.first()] as Medium).path, config.shouldShowHidden)
if (selectedKeys.size <= 1) {
val path = getFirstSelectedItemPath() ?: return
PropertiesDialog(activity, path, config.shouldShowHidden)
} else {
val paths = getSelectedPaths()
PropertiesDialog(activity, paths, config.shouldShowHidden)
@ -211,27 +198,46 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
}
private fun renameFile() {
val oldPath = getCurrentPath()
RenameItemDialog(activity, oldPath) {
Thread {
activity.updateDBMediaPath(oldPath, it)
if (selectedKeys.size == 1) {
val oldPath = getFirstSelectedItemPath() ?: return
RenameItemDialog(activity, oldPath) {
Thread {
activity.updateDBMediaPath(oldPath, it)
activity.runOnUiThread {
enableInstantLoad()
listener?.refreshItems()
finishActMode()
}
}.start()
activity.runOnUiThread {
enableInstantLoad()
listener?.refreshItems()
finishActMode()
}
}.start()
}
} else {
RenameItemsDialog(activity, getSelectedPaths()) {
enableInstantLoad()
listener?.refreshItems()
finishActMode()
}
}
}
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) {
Thread {
getSelectedMedia().forEach {
getSelectedItems().forEach {
activity.toggleFileVisibility(it.path, hide)
}
activity.runOnUiThread {
@ -244,7 +250,7 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
private fun toggleFavorites(add: Boolean) {
Thread {
val mediumDao = activity.galleryDB.MediumDao()
getSelectedMedia().forEach {
getSelectedItems().forEach {
it.isFavorite = add
mediumDao.updateFavorite(it.path, add)
}
@ -263,19 +269,55 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
}
private fun shareMedia() {
if (selectedPositions.size == 1 && selectedPositions.first() != -1) {
activity.shareMediumPath(getSelectedMedia().first().path)
} else if (selectedPositions.size > 1) {
if (selectedKeys.size == 1 && selectedKeys.first() != -1) {
activity.shareMediumPath(getSelectedItems().first().path)
} else if (selectedKeys.size > 1) {
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) {
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())
} 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) {
config.tempFolderPath = ""
@ -283,60 +325,26 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
activity.applicationContext.rescanFolderMedia(fileDirItems.first().getParentPath())
if (!isCopyOperation) {
listener?.refreshItems()
activity.updateFavoritePaths(fileDirItems, it)
}
}
}
private fun fixDateTaken() {
activity.toast(R.string.fixing)
Thread {
try {
val operations = ArrayList<ContentProviderOperation>()
val mediumDao = activity.galleryDB.MediumDao()
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)
activity.fixDateTaken(getSelectedPaths()) {
listener?.refreshItems()
finishActMode()
}
}.start()
}
private fun checkDeleteConfirmation() {
if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) {
if (config.isDeletePasswordProtectionOn) {
activity.handleDeletePasswordProtection {
deleteFiles()
}
} else if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) {
deleteFiles()
} else {
askConfirmDelete()
@ -344,8 +352,15 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
}
private fun askConfirmDelete() {
val items = resources.getQuantityString(R.plurals.delete_items, selectedPositions.size, selectedPositions.size)
val isRecycleBin = getSelectedPaths().first().startsWith(activity.filesDir.absolutePath)
val itemsCnt = selectedKeys.size
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 question = String.format(resources.getString(baseString), items)
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() {
if (selectedPositions.isEmpty()) {
if (selectedKeys.isEmpty()) {
return
}
val fileDirItems = ArrayList<FileDirItem>(selectedPositions.size)
val removeMedia = ArrayList<Medium>(selectedPositions.size)
if (media.size <= selectedPositions.first()) {
finishActMode()
return
}
val SAFPath = (media[selectedPositions.first()] as Medium).path
val SAFPath = getSelectedPaths().firstOrNull { activity.isPathOnSD(it) } ?: getFirstSelectedItemPath() ?: return
activity.handleSAFDialog(SAFPath) {
selectedPositions.sortedDescending().forEach {
val thumbnailItem = media.getOrNull(it)
if (thumbnailItem is Medium) {
fileDirItems.add(FileDirItem(thumbnailItem.path, thumbnailItem.name))
removeMedia.add(thumbnailItem)
}
val fileDirItems = ArrayList<FileDirItem>(selectedKeys.size)
val removeMedia = ArrayList<Medium>(selectedKeys.size)
val positions = getSelectedItemPositions()
getSelectedItems().forEach {
fileDirItems.add(FileDirItem(it.path, it.name))
removeMedia.add(it)
}
media.removeAll(removeMedia)
listener?.tryDeleteFiles(fileDirItems)
removeSelectedItems()
removeSelectedItems(positions)
}
}
private fun getSelectedMedia(): List<Medium> {
val selectedMedia = ArrayList<Medium>(selectedPositions.size)
selectedPositions.forEach {
(media.getOrNull(it) as? Medium)?.apply {
selectedMedia.add(this)
}
}
return selectedMedia
}
private fun getSelectedItems() = media.filter { selectedKeys.contains((it as? Medium)?.path?.hashCode()) } as ArrayList<Medium>
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>) {
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)
private fun setupThumbnail(view: View, medium: Medium) {
val isSelected = selectedKeys.contains(medium.path.hashCode())
view.apply {
play_outline.beVisibleIf(medium.isVideo())
photo_name.beVisibleIf(displayFilenames || isListViewType)
photo_name.text = medium.name
photo_name.tag = medium.path
medium_name.beVisibleIf(displayFilenames || isListViewType)
medium_name.text = medium.name
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
if (hasOTGConnected && path.startsWith(OTG_PATH)) {
@ -448,20 +463,20 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList<Thumbnai
}
if (loadImageInstantly) {
activity.loadImage(medium.type, path, medium_thumbnail, scrollHorizontally, animateGifs, cropThumbnails)
activity.loadImage(medium.type, path, medium_thumbnail, scrollHorizontally, animateGifs, cropThumbnails, rotatedImagePaths)
} else {
medium_thumbnail.setImageDrawable(null)
medium_thumbnail.isHorizontalScrolling = scrollHorizontally
delayHandler.postDelayed({
val isVisible = visibleItemPaths.contains(medium.path)
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)
}
if (isListViewType) {
photo_name.setTextColor(textColor)
medium_name.setTextColor(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.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 com.simplemobiletools.gallery.activities.ViewPagerActivity
import com.simplemobiletools.gallery.fragments.PhotoFragment
import com.simplemobiletools.gallery.fragments.VideoFragment
import com.simplemobiletools.gallery.fragments.ViewPagerFragment
import com.simplemobiletools.gallery.helpers.MEDIUM
import com.simplemobiletools.gallery.models.Medium
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.PagerAdapter
import com.simplemobiletools.gallery.pro.activities.ViewPagerActivity
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) {
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.os.AsyncTask
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_TAKEN
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.extensions.getFavoritePaths
import com.simplemobiletools.gallery.helpers.FAVORITES
import com.simplemobiletools.gallery.helpers.MediaFetcher
import com.simplemobiletools.gallery.helpers.RECYCLE_BIN
import com.simplemobiletools.gallery.helpers.SHOW_ALL
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.extensions.getFavoritePaths
import com.simplemobiletools.gallery.pro.helpers.FAVORITES
import com.simplemobiletools.gallery.pro.helpers.MediaFetcher
import com.simplemobiletools.gallery.pro.helpers.RECYCLE_BIN
import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL
import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import java.util.*
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 getProperDateTaken = context.config.getFileSorting(pathToUse) and SORT_BY_DATE_TAKEN != 0
val favoritePaths = context.getFavoritePaths()
val getVideoDurations = context.config.showThumbnailVideoDuration
val media = if (showAll) {
val foldersToScan = mediaFetcher.getFoldersToScan().filter { it != RECYCLE_BIN && it != FAVORITES }
val media = ArrayList<Medium>()
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)
}
mediaFetcher.sortMedia(media, context.config.getFileSorting(SHOW_ALL))
media
} else {
mediaFetcher.getFilesFrom(mPath, isPickImage, isPickVideo, getProperDateTaken, favoritePaths)
mediaFetcher.getFilesFrom(mPath, isPickImage, isPickVideo, getProperDateTaken, favoritePaths, getVideoDurations)
}
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.support.v7.app.AlertDialog
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.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.dialog_change_grouping.view.*
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.support.v7.app.AlertDialog
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.commons.helpers.*
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.SHOW_ALL
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL
import kotlinx.android.synthetic.main.dialog_change_sorting.view.*
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_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_RANDOM != 0 -> sortingRadio.sorting_dialog_radio_random
else -> sortingRadio.sorting_dialog_radio_name
}
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_size -> SORT_BY_SIZE
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
}

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.support.v7.app.AlertDialog
import androidx.appcompat.app.AlertDialog
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.*
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.widget.RadioButton
import android.widget.RadioGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.getBasePath
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import kotlinx.android.synthetic.main.dialog_exclude_folder.view.*
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)
.setPositiveButton(R.string.ok, { dialog, which -> dialogConfirmed() })
.setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this)
}
activity.setupDialogStuff(view, this)
}
}
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.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.dialog_filter_media.view.*
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.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.dialog_manage_bottom_actions.view.*
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_rename.isChecked = actions and BOTTOM_ACTION_RENAME != 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)
@ -63,6 +64,8 @@ class ManageBottomActionsDialog(val activity: BaseSimpleActivity, val callback:
result += BOTTOM_ACTION_RENAME
if (manage_bottom_actions_set_as.isChecked)
result += BOTTOM_ACTION_SET_AS
if (manage_bottom_actions_copy.isChecked)
result += BOTTOM_ACTION_COPY
}
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.extensions.setupDialogStuff
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.helpers.*
import kotlinx.android.synthetic.main.dialog_manage_extended_details.view.*
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() }
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this)
}
activity.setupDialogStuff(view, this)
}
}
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 android.support.v7.widget.GridLayoutManager
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.beGoneIf
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.adapters.MediaAdapter
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.extensions.getCachedMedia
import com.simplemobiletools.gallery.helpers.SHOW_ALL
import com.simplemobiletools.gallery.helpers.VIEW_TYPE_GRID
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.MediaAdapter
import com.simplemobiletools.gallery.pro.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.extensions.getCachedMedia
import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL
import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_GRID
import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import kotlinx.android.synthetic.main.dialog_medium_picker.view.*
class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val callback: (path: String) -> Unit) {
var dialog: AlertDialog
var shownMedia = ArrayList<ThumbnailItem>()
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 {
(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
}
@ -53,7 +54,7 @@ class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val c
}
private fun showOtherFolder() {
PickDirectoryDialog(activity, path) {
PickDirectoryDialog(activity, path, true) {
callback(it)
dialog.dismiss()
}
@ -64,7 +65,7 @@ class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val c
return
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) {
callback(it.path)
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.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
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.*
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_path.setOnClickListener {
FilePickerDialog(activity, realPath, false, false, true) {
FilePickerDialog(activity, realPath, false, false, true, true) {
save_as_path.text = activity.humanizePath(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 androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.hideKeyboard
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.config
import com.simplemobiletools.gallery.helpers.SLIDESHOW_DEFAULT_INTERVAL
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.helpers.SLIDESHOW_DEFAULT_INTERVAL
import kotlinx.android.synthetic.main.dialog_slideshow.view.*
class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit) {
@ -29,11 +28,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit
activity.hideKeyboard(v)
}
include_photos_holder.setOnClickListener {
interval_value.clearFocus()
include_photos.toggle()
}
include_videos_holder.setOnClickListener {
interval_value.clearFocus()
include_videos.toggle()
@ -73,11 +67,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit
activity.setupDialogStuff(view, this) {
hideKeyboard()
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()
callback()
dismiss()
@ -90,7 +79,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit
val config = activity.config
view.apply {
interval_value.setText(config.slideshowInterval.toString())
include_photos.isChecked = config.slideshowIncludePhotos
include_videos.isChecked = config.slideshowIncludeVideos
include_gifs.isChecked = config.slideshowIncludeGIFs
random_order.isChecked = config.slideshowRandomOrder
@ -107,7 +95,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit
activity.config.apply {
slideshowInterval = interval.toInt()
slideshowIncludePhotos = view.include_photos.isChecked
slideshowIncludeVideos = view.include_videos.isChecked
slideshowIncludeGIFs = view.include_gifs.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.models.Medium
import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.models.Medium
fun ArrayList<Medium>.getDirMediaTypes(): Int {
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.Intent
import android.content.res.Configuration
@ -8,7 +10,6 @@ import android.database.sqlite.SQLiteException
import android.graphics.Point
import android.graphics.drawable.PictureDrawable
import android.media.AudioManager
import android.os.Build
import android.provider.MediaStore
import android.view.WindowManager
import android.widget.ImageView
@ -19,20 +20,28 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.SettingsActivity
import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.databases.GalleryDatabase
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.interfaces.DirectoryDao
import com.simplemobiletools.gallery.interfaces.MediumDao
import com.simplemobiletools.gallery.models.Directory
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem
import com.simplemobiletools.gallery.svg.SvgSoftwareLayerSetter
import com.simplemobiletools.gallery.views.MySquareImageView
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.activities.SettingsActivity
import com.simplemobiletools.gallery.pro.asynctasks.GetMediaAsynctask
import com.simplemobiletools.gallery.pro.databases.GalleryDatabase
import com.simplemobiletools.gallery.pro.helpers.*
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.ThumbnailItem
import com.simplemobiletools.gallery.pro.svg.SvgSoftwareLayerSetter
import com.simplemobiletools.gallery.pro.views.MySquareImageView
import pl.droidsonroids.gif.GifDrawable
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.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
get() = when {
navigationBarRight -> Point(realScreenSize.x - usableScreenSize.x, usableScreenSize.y)
navigationBarBottom -> Point(usableScreenSize.x, realScreenSize.y - usableScreenSize.y)
navigationBarRight -> Point(newNavigationBarHeight, usableScreenSize.y)
navigationBarBottom -> Point(usableScreenSize.x, newNavigationBarHeight)
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
get() {
val size = Point()
@ -59,8 +97,7 @@ val Context.usableScreenSize: Point
val Context.realScreenSize: Point
get() {
val size = Point()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
windowManager.defaultDisplay.getRealSize(size)
windowManager.defaultDisplay.getRealSize(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.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> {
val foundFolders = ArrayList<Directory>()
val pinnedFolders = config.pinnedFolders
@ -96,6 +141,14 @@ fun Context.movePinnedDirectoriesToFront(dirs: ArrayList<Directory>): ArrayList<
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
}
@ -104,6 +157,11 @@ fun Context.getSortedDirectories(source: ArrayList<Directory>): ArrayList<Direct
val sorting = config.directorySorting
val dirs = source.clone() as ArrayList<Directory>
if (sorting and SORT_BY_RANDOM != 0) {
dirs.shuffle()
return movePinnedDirectoriesToFront(dirs)
}
dirs.sortWith(Comparator { o1, o2 ->
o1 as Directory
o2 as Directory
@ -124,6 +182,120 @@ fun Context.getSortedDirectories(source: ArrayList<Directory>): ArrayList<Direct
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) {
Thread {
val folders = ArrayList<String>()
@ -174,7 +346,7 @@ fun Context.rescanFolderMediaSync(path: String) {
if (!newMedia.contains(it)) {
val mediumPath = (it as? Medium)?.path
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 {
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)
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 -> {
if (path.startsWith(OTG_PATH)) {
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
if (type == TYPE_IMAGES || type == TYPE_VIDEOS || type == TYPE_RAWS) {
if (type == TYPE_IMAGES && path.isPng()) {
loadPng(path, target, cropThumbnails)
loadPng(path, target, cropThumbnails, skipMemoryCacheAtPaths)
} else {
loadJpg(path, target, cropThumbnails)
loadJpg(path, target, cropThumbnails, skipMemoryCacheAtPaths)
}
} else if (type == TYPE_GIFS) {
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
} catch (e: Exception) {
loadJpg(path, target, cropThumbnails)
loadJpg(path, target, cropThumbnails, skipMemoryCacheAtPaths)
} catch (e: OutOfMemoryError) {
loadJpg(path, target, cropThumbnails)
loadJpg(path, target, cropThumbnails, skipMemoryCacheAtPaths)
}
} else if (type == TYPE_SVGS) {
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()
.signature(path.getFileSignature())
.skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.format(DecodeFormat.PREFER_ARGB_8888)
@ -272,9 +451,10 @@ fun Context.loadPng(path: String, target: MySquareImageView, cropThumbnails: Boo
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()
.signature(path.getFileSignature())
.skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
val builder = Glide.with(applicationContext)
@ -299,7 +479,7 @@ fun Context.loadSVG(path: String, target: MySquareImageView, cropThumbnails: Boo
.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 {
val directories = try {
directoryDao.getAll() as ArrayList<Directory>
@ -307,11 +487,11 @@ fun Context.getCachedDirectories(getVideosOnly: Boolean = false, getImagesOnly:
ArrayList<Directory>()
}
if (!config.showRecycleBinAtFolders) {
if (!config.showRecycleBinAtFolders || !config.useRecycleBin) {
directories.removeAll { it.isRecycleBin() }
}
val shouldShowHidden = config.shouldShowHidden
val shouldShowHidden = config.shouldShowHidden || forceShowHidden
val excludedPaths = config.excludedFolders
val includedPaths = config.includedFolders
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)
callback(grouped.clone() as ArrayList<ThumbnailItem>)
val recycleBinPath = filesDir.absolutePath
val mediaToDelete = ArrayList<Medium>()
media.filter { !getDoesFilePathExist(it.path) }.forEach {
if (it.path.startsWith(recycleBinPath)) {
mediumDao.deleteMediumPath(it.path.removePrefix(recycleBinPath))
deleteDBPath(mediumDao, it.path)
} else {
mediaToDelete.add(it)
}
@ -427,10 +606,83 @@ fun Context.getOTGFolderChildrenNames(path: String) = getOTGFolderChildren(path)
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> {
val media = mediumDao.getDeletedMedia() as ArrayList<Medium>
media.forEach {
it.path = File(filesDir.absolutePath, it.path).toString()
it.path = File(recycleBinPath, it.path.removePrefix(RECYCLE_BIN)).toString()
}
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
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 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.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.simplemobiletools.commons.helpers.OTG_PATH
import java.io.File
import java.io.IOException
fun String.getFileSignature(): ObjectKey {
fun String.getFileSignature() = ObjectKey(getFileKey())
fun String.getFileKey(): String {
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) }
@ -18,8 +21,14 @@ fun String.shouldFolderBeVisible(excludedPaths: MutableSet<String>, includedPath
val file = File(this)
return if (isEmpty()) {
false
} else if (!showHidden && file.isHidden) {
false
} else if (includedPaths.contains(this)) {
true
} else if (!showHidden && file.containsNoMedia()) {
false
} else if (excludedPaths.contains(this)) {
false
} else if (isThisOrParentIncluded(includedPaths)) {
true
} else if (isThisOrParentExcluded(excludedPaths)) {
@ -43,3 +52,14 @@ fun String.getDistinctPath(): String {
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.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.graphics.Point
import android.graphics.SurfaceTexture
import android.media.AudioManager
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.util.DisplayMetrics
import android.view.*
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.SeekBar
import android.widget.TextView
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.source.ExtractorMediaSource
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.upstream.ContentDataSource
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DataSpec
import com.google.android.exoplayer2.upstream.FileDataSource
import com.google.android.exoplayer2.video.VideoListener
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.isJellyBean1Plus
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.VideoActivity
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.helpers.MEDIUM
import com.simplemobiletools.gallery.helpers.MediaSideScroll
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.activities.PanoramaVideoActivity
import com.simplemobiletools.gallery.pro.activities.VideoActivity
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.helpers.Config
import com.simplemobiletools.gallery.pro.helpers.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 java.io.File
import java.io.FileInputStream
class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, SeekBar.OnSeekBarChangeListener {
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 mCurrTimeView: TextView? = null
private var mSeekBar: SeekBar? = null
private var mTimeHolder: View? = null
private var mView: View? = null
private var mIsFullscreen = false
private var mWasFragmentInit = false
private var mIsPanorama = false
private var mIsFragmentVisible = false
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 mVideoSize = Point(0, 0)
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 mStoredHideExtendedDetails = false
private var mStoredBottomActions = true
private var mStoredExtendedDetails = 0
private var mStoredRememberLastVideoPosition = false
private var mStoredLastVideoPath = ""
private var mStoredLastVideoPosition = 0
private lateinit var brightnessSideScroll: MediaSideScroll
private lateinit var volumeSideScroll: MediaSideScroll
lateinit var medium: Medium
private lateinit var mTimeHolder: View
private lateinit var mBrightnessSideScroll: MediaSideScroll
private lateinit var mVolumeSideScroll: MediaSideScroll
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? {
mConfig = context!!.config
mView = inflater.inflate(R.layout.pager_video_item, container, false).apply {
instant_prev_item.setOnClickListener { listener?.goToPrevItem() }
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
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()
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)
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
initTimeHolder()
checkIfPanorama()
setupPlayer()
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 {
mMedium.path.getVideoResolution()?.apply {
mVideoSize.x = x
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()
mView!!.video_surface.onGlobalLayout {
if (mIsFragmentVisible && context?.config?.autoplayVideos == true) {
playVideo()
}
if (mStoredRememberLastVideoPosition) {
setLastVideoSavedPosition()
}
updateInstantSwitchWidths()
return mView
}
override fun onResume() {
super.onResume()
activity!!.updateTextColors(mView!!.video_holder)
val config = context!!.config
val allowVideoGestures = config.allowVideoGestures
val allowInstantChange = config.allowInstantChange
mView!!.apply {
video_volume_controller.beVisibleIf(allowVideoGestures)
video_brightness_controller.beVisibleIf(allowVideoGestures)
mConfig = context!!.config // make sure we get a new config, in case the user changed something in the app settings
activity!!.updateTextColors(mView.video_holder)
val allowVideoGestures = mConfig.allowVideoGestures
val allowInstantChange = mConfig.allowInstantChange
mTextureView.beGoneIf(mConfig.openVideosOnSeparateScreen || mIsPanorama)
mView.apply {
video_surface_frame.beGoneIf(mTextureView.isGone())
video_volume_controller.beVisibleIf(allowVideoGestures && !mIsPanorama)
video_brightness_controller.beVisibleIf(allowVideoGestures && !mIsPanorama)
instant_prev_item.beVisibleIf(allowInstantChange)
instant_next_item.beVisibleIf(allowInstantChange)
}
if (config.showExtendedDetails != mStoredShowExtendedDetails || config.extendedDetails != mStoredExtendedDetails) {
checkExtendedDetails()
}
if (config.bottomActions != mStoredBottomActions) {
initTimeHolder()
}
mView!!.video_time_holder.setBackgroundResource(if (config.bottomActions) 0 else R.drawable.gradient_background)
checkExtendedDetails()
initTimeHolder()
storeStateVariables()
}
override fun onPause() {
super.onPause()
pauseVideo()
storeStateVariables()
pauseVideo()
if (mStoredRememberLastVideoPosition && mIsFragmentVisible && mWasVideoStarted) {
saveVideoProgress()
}
}
override fun onDestroy() {
@ -180,7 +235,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
}
mIsFragmentVisible = menuVisible
if (mWasFragmentInit && menuVisible && context?.config?.autoplayVideos == true) {
if (mWasFragmentInit && menuVisible && mConfig.autoplayVideos && !mConfig.openVideosOnSeparateScreen) {
playVideo()
}
}
@ -190,35 +245,85 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
setVideoSize()
initTimeHolder()
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() {
context!!.config.apply {
mConfig.apply {
mStoredShowExtendedDetails = showExtendedDetails
mStoredHideExtendedDetails = hideExtendedDetails
mStoredExtendedDetails = extendedDetails
mStoredBottomActions = bottomActions
mStoredRememberLastVideoPosition = rememberLastVideoPosition
mStoredLastVideoPath = lastVideoPath
mStoredLastVideoPosition = lastVideoPosition
}
}
private fun setupPlayer() {
if (activity == null)
if (activity == null || mConfig.openVideosOnSeparateScreen || mIsPanorama) {
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()
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() {
val isContentUri = medium.path.startsWith("content://")
val uri = if (isContentUri) Uri.parse(medium.path) else Uri.fromFile(File(medium.path))
val isContentUri = mMedium.path.startsWith("content://")
val uri = if (isContentUri) Uri.parse(mMedium.path) else Uri.fromFile(File(mMedium.path))
val dataSpec = DataSpec(uri)
val fileDataSource = if (isContentUri) ContentDataSource(context) else FileDataSource()
try {
@ -229,7 +334,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
val factory = DataSource.Factory { fileDataSource }
val audioSource = ExtractorMediaSource(fileDataSource.uri, factory, DefaultExtractorsFactory(), null, null)
mExoPlayer!!.audioStreamType = AudioManager.STREAM_MUSIC
mExoPlayer!!.audioStreamType = C.STREAM_TYPE_MUSIC
mExoPlayer!!.prepare(audioSource)
}
@ -241,9 +346,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {}
override fun onPlayerError(error: ExoPlaybackException?) {
mIsExoPlayerInitialized = false
}
override fun onPlayerError(error: ExoPlaybackException?) {}
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 onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
mIsExoPlayerInitialized = playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
when (playbackState) {
Player.STATE_READY -> videoPrepared()
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) {
mVideoSize.x = width
mVideoSize.y = height
@ -275,108 +377,162 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
})
}
private fun launchVideoPlayer() {
listener?.launchViewVideoIntent(mMedium.path)
}
private fun toggleFullscreen() {
listener?.fragmentClicked()
}
private fun initTimeHolder() {
val res = resources
val left = 0
val top = 0
var right = 0
var bottom = 0
if (hasNavBar()) {
if (res.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
bottom += context!!.navigationBarHeight
} else {
right += context!!.navigationBarWidth
bottom += context!!.navigationBarHeight
private fun checkExtendedDetails() {
if (mConfig.showExtendedDetails) {
mView.video_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 (!mConfig.hideExtendedDetails || !mIsFullscreen) 1f else 0f
}
}
}
}
} 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()
}
mTimeHolder!!.setPadding(left, top, right, bottom)
mCurrTimeView = mView!!.video_curr_time
mSeekBar = mView!!.video_seekbar
mSeekBar!!.setOnSeekBarChangeListener(this)
if (mIsFullscreen) {
mTimeHolder!!.beInvisible()
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && activity?.hasNavBar() == true) {
right += activity!!.navigationBarWidth
}
}
private fun hasNavBar(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
val display = context!!.windowManager.defaultDisplay
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.layoutParams as RelativeLayout.LayoutParams).apply {
bottomMargin = bottom
rightMargin = right
}
mTimeHolder.beInvisibleIf(mIsFullscreen)
}
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()
private fun checkIfPanorama() {
try {
val fis = FileInputStream(File(mMedium.path))
fis.use { fis ->
context!!.parseFileChannel(mMedium.path, fis.channel, 0, 0, 0) {
mIsPanorama = true
}
mTimerHandler.postDelayed(this, 1000)
}
})
} catch (ignored: Exception) {
} catch (ignored: OutOfMemoryError) {
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(PROGRESS, mCurrTime)
private fun openPanorama() {
Intent(context, PanoramaVideoActivity::class.java).apply {
putExtra(PATH, mMedium.path)
startActivity(this)
}
}
private fun checkFullscreen() {
if (activity == null) {
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) {
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
}
var anim = android.R.anim.fade_in
if (mIsFullscreen) {
anim = android.R.anim.fade_out
mSeekBar!!.setOnSeekBarChangeListener(null)
} else {
mSeekBar!!.setOnSeekBarChangeListener(this)
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()
}
}
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 {
duration = 150
fillAfter = true
mTimeHolder?.startAnimation(this)
if (mExoPlayer == null)
return
if (mIsPlaying) {
mExoPlayer!!.playWhenReady = true
} else {
togglePlayPause()
}
mIsDragged = false
}
private fun togglePlayPause() {
@ -384,7 +540,6 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
return
mIsPlaying = !mIsPlaying
mHidePauseHandler.removeCallbacksAndMessages(null)
if (mIsPlaying) {
playVideo()
} else {
@ -397,20 +552,39 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
return
}
if (mView!!.video_preview.isVisible()) {
mView!!.video_preview.beGone()
if (mView.video_preview.isVisible()) {
mView.video_preview.beGone()
initExoPlayer()
}
if (videoEnded()) {
setProgress(0)
val wasEnded = videoEnded()
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
mExoPlayer?.playWhenReady = true
mView!!.video_play_outline.setImageResource(R.drawable.ic_pause)
activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
schedulePlayPauseFadeOut()
}
private fun clearLastVideoSavedProgress() {
mStoredLastVideoPosition = 0
mStoredLastVideoPath = ""
}
private fun pauseVideo() {
@ -423,46 +597,35 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
mExoPlayer?.playWhenReady = false
}
mView?.video_play_outline?.setImageResource(R.drawable.ic_play)
mView?.video_play_outline?.alpha = PLAY_PAUSE_VISIBLE_ALPHA
mPlayPauseButton.setImageResource(R.drawable.ic_play_outline)
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
schedulePlayPauseFadeOut()
}
private fun schedulePlayPauseFadeOut() {
mHidePauseHandler.removeCallbacksAndMessages(null)
mHidePauseHandler.postDelayed({
mView!!.video_play_outline.animate().alpha(0f).start()
}, HIDE_PAUSE_DELAY)
private fun videoEnded(): Boolean {
val currentPos = mExoPlayer?.currentPosition ?: 0
val duration = mExoPlayer?.duration ?: 0
return currentPos != 0L && currentPos >= duration
}
private fun videoEnded() = mExoPlayer?.currentPosition ?: 0 >= mExoPlayer?.duration ?: 0
private fun setProgress(seconds: Int) {
mExoPlayer!!.seekTo(seconds * 1000L)
mSeekBar!!.progress = seconds
mCurrTimeView!!.text = seconds.getFormattedDuration()
private fun setPosition(seconds: Int) {
mExoPlayer?.seekTo(seconds * 1000L)
mSeekBar.progress = seconds
mCurrTimeView.text = seconds.getFormattedDuration()
}
private fun setupVideoDuration() {
try {
val retriever = MediaMetadataRetriever()
retriever.setDataSource(medium.path)
mDuration = Math.round(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toInt() / 1000f)
} catch (ignored: Exception) {
}
mDuration = mMedium.path.getVideoDuration()
setupTimeHolder()
setProgress(0)
setPosition(0)
}
private fun videoPrepared() {
if (mDuration == 0) {
mDuration = (mExoPlayer!!.duration / 1000).toInt()
setupTimeHolder()
setProgress(mCurrTime)
setPosition(mCurrTime)
if (mIsFragmentVisible && (context!!.config.autoplayVideos)) {
if (mIsFragmentVisible && (mConfig.autoplayVideos)) {
playVideo()
}
}
@ -474,23 +637,24 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
}
mCurrTime = (mExoPlayer!!.duration / 1000).toInt()
if (listener?.videoEnded() == false && context!!.config.loopVideos) {
if (listener?.videoEnded() == false && mConfig.loopVideos) {
playVideo()
} else {
mSeekBar!!.progress = mSeekBar!!.max
mCurrTimeView!!.text = mDuration.getFormattedDuration()
mSeekBar.progress = mSeekBar.max
mCurrTimeView.text = mDuration.getFormattedDuration()
pauseVideo()
}
}
private fun cleanup() {
pauseVideo()
mCurrTimeView?.text = 0.getFormattedDuration()
releaseExoPlayer()
mSeekBar?.progress = 0
mTimerHandler.removeCallbacksAndMessages(null)
mHidePauseHandler.removeCallbacksAndMessages(null)
mTextureView = null
if (mWasFragmentInit) {
mCurrTimeView.text = 0.getFormattedDuration()
mSeekBar.progress = 0
mTimerHandler.removeCallbacksAndMessages(null)
}
}
private fun releaseExoPlayer() {
@ -509,33 +673,28 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
Thread {
mExoPlayer?.setVideoSurface(Surface(mTextureView!!.surfaceTexture))
mExoPlayer?.setVideoSurface(Surface(mTextureView.surfaceTexture))
}.start()
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private fun setVideoSize() {
if (activity == null || mTextureView == null)
if (activity == null || mConfig.openVideosOnSeparateScreen) {
return
}
val videoProportion = mVideoSize.x.toFloat() / mVideoSize.y.toFloat()
val display = activity!!.windowManager.defaultDisplay
val screenWidth: Int
val screenHeight: Int
if (isJellyBean1Plus()) {
val realMetrics = DisplayMetrics()
display.getRealMetrics(realMetrics)
screenWidth = realMetrics.widthPixels
screenHeight = realMetrics.heightPixels
} else {
screenWidth = display.width
screenHeight = display.height
}
val realMetrics = DisplayMetrics()
display.getRealMetrics(realMetrics)
screenWidth = realMetrics.widthPixels
screenHeight = realMetrics.heightPixels
val screenProportion = screenWidth.toFloat() / screenHeight.toFloat()
mTextureView!!.layoutParams.apply {
mTextureView.layoutParams.apply {
if (videoProportion > screenProportion) {
width = screenWidth
height = (screenWidth.toFloat() / videoProportion).toInt()
@ -543,92 +702,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
width = (videoProportion * screenHeight.toFloat()).toInt()
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.res.Configuration
@ -8,8 +8,8 @@ import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.helpers.BaseConfig
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED
import com.simplemobiletools.commons.helpers.SORT_DESCENDING
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.models.AlbumCover
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.models.AlbumCover
import java.util.*
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 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
get() = prefs.getBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, false)
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>) {
val currPinnedFolders = HashSet<String>(pinnedFolders)
currPinnedFolders.addAll(paths)
pinnedFolders = currPinnedFolders
pinnedFolders = currPinnedFolders.filter { it.isNotEmpty() }.toHashSet()
if (paths.contains(RECYCLE_BIN)) {
showRecycleBinLast = false
}
}
fun removePinnedFolders(paths: Set<String>) {
@ -104,7 +123,7 @@ class Config(context: Context) : BaseConfig(context) {
fun addExcludedFolders(paths: Set<String>) {
val currExcludedFolders = HashSet<String>(excludedFolders)
currExcludedFolders.addAll(paths)
excludedFolders = currExcludedFolders
excludedFolders = currExcludedFolders.filter { it.isNotEmpty() }.toHashSet()
}
fun removeExcludedFolder(path: String) {
@ -114,23 +133,21 @@ class Config(context: Context) : BaseConfig(context) {
}
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()
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) {
val currIncludedFolders = HashSet<String>(includedFolders)
currIncludedFolders.add(path)
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) {
val currIncludedFolders = HashSet<String>(includedFolders)
currIncludedFolders.remove(path)
@ -143,7 +160,7 @@ class Config(context: Context) : BaseConfig(context) {
var autoplayVideos: Boolean
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
get() = prefs.getBoolean(ANIMATE_GIFS, false)
@ -157,6 +174,10 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getBoolean(CROP_THUMBNAILS, true)
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
get() = prefs.getInt(SCREEN_ROTATION, ROTATE_BY_SYSTEM_SETTING)
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)
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
get() = prefs.getBoolean(DISPLAY_FILE_NAMES, false)
set(display) = prefs.edit().putBoolean(DISPLAY_FILE_NAMES, display).apply()
var blackBackground: Boolean
get() = prefs.getBoolean(DARK_BACKGROUND, false)
set(darkBackground) = prefs.edit().putBoolean(DARK_BACKGROUND, darkBackground).apply()
get() = prefs.getBoolean(BLACK_BACKGROUND, false)
set(blackBackground) = prefs.edit().putBoolean(BLACK_BACKGROUND, blackBackground).apply()
var filterMedia: Int
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())
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
get() = prefs.getBoolean(ALLOW_INSTANT_CHANGE, false)
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)
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
get() = prefs.getBoolean(SLIDESHOW_INCLUDE_VIDEOS, false)
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)
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
get() = prefs.getInt(VISIBLE_BOTTOM_ACTIONS, DEFAULT_BOTTOM_ACTIONS)
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())
set(everShownFolders) = prefs.edit().putStringSet(EVER_SHOWN_FOLDERS, everShownFolders).apply()
fun getEverShownFolders() = hashSetOf(
private fun getEverShownFolders() = hashSetOf(
internalStoragePath,
Environment.DIRECTORY_DCIM,
Environment.DIRECTORY_PICTURES
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath,
"${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath}/Screenshots"
)
var showRecycleBinAtFolders: Boolean
@ -388,4 +419,52 @@ class Config(context: Context) : BaseConfig(context) {
var lastBinCheck: Long
get() = prefs.getLong(LAST_BIN_CHECK, 0L)
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
@ -6,17 +6,21 @@ import com.simplemobiletools.commons.helpers.MONTH_SECONDS
const val DIRECTORY_SORT_ORDER = "directory_sort_order"
const val SORT_FOLDER_PREFIX = "sort_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 TEMPORARILY_SHOW_HIDDEN = "temporarily_show_hidden"
const val IS_THIRD_PARTY_INTENT = "is_third_party_intent"
const val AUTOPLAY_VIDEOS = "autoplay_videos"
const val REMEMBER_LAST_VIDEO_POSITION = "remember_last_video_position"
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 MAX_BRIGHTNESS = "max_brightness"
const val CROP_THUMBNAILS = "crop_thumbnails"
const val SHOW_THUMBNAIL_VIDEO_DURATION = "show_thumbnail_video_duration"
const val SCREEN_ROTATION = "screen_rotation"
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 FILTER_MEDIA = "filter_media"
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 EXTENDED_DETAILS = "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 DO_EXTRA_CHECK = "do_extra_check"
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 TEMP_SKIP_DELETE_CONFIRMATION = "temp_skip_delete_confirmation"
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 WERE_FAVORITES_PINNED = "were_favorites_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 EVER_SHOWN_FOLDERS = "ever_shown_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 WAS_SVG_SHOWING_HANDLED = "was_svg_showing_handled"
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
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_GIFS = "slideshow_include_gifs"
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_DEFAULT_INTERVAL = 5
const val SLIDESHOW_SCROLL_DURATION = 500L
const val SLIDESHOW_START_ON_ENTER = "slideshow_start_on_enter"
const val NOMEDIA = ".nomedia"
const val FAVORITES = "favorites"
const val RECYCLE_BIN = "recycle_bin"
const val SHOW_FAVORITES = "show_favorites"
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 SHOW_TEMP_HIDDEN_DURATION = 300000L
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 MONTH_MILLISECONDS = MONTH_SECONDS * 1000L
const val MIN_SKIP_LENGTH = 2000
const val HIDE_SYSTEM_UI_DELAY = 500L
const val DIRECTORY = "directory"
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_RENAME = 1024
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
// 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 com.simplemobiletools.gallery.models.FilterItem
import com.simplemobiletools.gallery.pro.models.FilterItem
import java.util.*
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.Matrix
@ -8,8 +8,9 @@ import java.security.MessageDigest
class GlideRotateTransformation(val rotateRotationAngle: Int) : BitmapTransformation() {
override fun transform(pool: BitmapPool, bitmap: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
if (rotateRotationAngle % 360 == 0)
if (rotateRotationAngle % 360 == 0) {
return bitmap
}
val matrix = Matrix()
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.database.Cursor
@ -7,33 +7,39 @@ import android.provider.MediaStore
import android.text.format.DateFormat
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.extensions.*
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.models.ThumbnailItem
import com.simplemobiletools.gallery.models.ThumbnailSection
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.extensions.*
import com.simplemobiletools.gallery.pro.models.Medium
import com.simplemobiletools.gallery.pro.models.ThumbnailItem
import com.simplemobiletools.gallery.pro.models.ThumbnailSection
import java.io.File
import java.util.*
class MediaFetcher(val context: Context) {
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
if (filterMedia == 0) {
return ArrayList()
}
val curMedia = ArrayList<Medium>()
if (curPath.startsWith(OTG_PATH)) {
val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia, favoritePaths)
curMedia.addAll(newMedia)
if (curPath.startsWith(OTG_PATH, true)) {
if (context.hasOTGConnected()) {
val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia, favoritePaths, getVideoDurations)
curMedia.addAll(newMedia)
}
} else {
val newMedia = getMediaInFolder(curPath, isPickImage, isPickVideo, filterMedia, getProperDateTaken, favoritePaths)
val newMedia = getMediaInFolder(curPath, isPickImage, isPickVideo, filterMedia, getProperDateTaken, favoritePaths, getVideoDurations)
curMedia.addAll(newMedia)
}
sortMedia(curMedia, context.config.getFileSorting(curPath))
if (sortMedia) {
sortMedia(curMedia, context.config.getFileSorting(curPath))
}
return curMedia
}
@ -122,7 +128,7 @@ class MediaFetcher(val context: Context) {
val foldersToIgnore = arrayListOf("/storage/emulated/legacy")
val config = context.config
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 {
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,
favoritePaths: ArrayList<String>): ArrayList<Medium> {
favoritePaths: ArrayList<String>, getVideoDurations: Boolean): ArrayList<Medium> {
val media = ArrayList<Medium>()
val deletedMedia = if (folder == RECYCLE_BIN) {
@ -176,27 +182,27 @@ class MediaFetcher(val context: Context) {
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) {
FAVORITES -> favoritePaths.map { File(it) }.toTypedArray()
FAVORITES -> favoritePaths.filter { showHidden || !it.contains("/.") }.map { File(it) }.toTypedArray()
RECYCLE_BIN -> deletedMedia.map { File(it.path) }.toTypedArray()
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) {
if (shouldStop) {
break
}
val filename = file.name
val isImage = filename.isImageFast()
val isVideo = if (isImage) false else filename.isVideoFast()
val isGif = if (isImage || isVideo) false else filename.isGif()
val isRaw = if (isImage || isVideo || isGif) false else filename.isRawFast()
val isSvg = if (isImage || isVideo || isGif || isRaw) false else filename.isSvg()
val path = file.absolutePath
val isImage = path.isImageFast()
val isVideo = if (isImage) false else path.isVideoFast()
val isGif = if (isImage || isVideo) false else path.isGif()
val isRaw = if (isImage || isVideo || isGif) false else path.isRawFast()
val isSvg = if (isImage || isVideo || isGif || isRaw) false else path.isSvg()
if (!isImage && !isVideo && !isGif && !isRaw && !isSvg)
continue
@ -216,6 +222,7 @@ class MediaFetcher(val context: Context) {
if (isSvg && filterMedia and TYPE_SVGS == 0)
continue
val filename = file.name
if (!showHidden && filename.startsWith('.'))
continue
@ -223,7 +230,6 @@ class MediaFetcher(val context: Context) {
if (size <= 0L || (doExtraCheck && !file.exists()))
continue
val path = file.absolutePath
if (folder == RECYCLE_BIN) {
deletedMedia.firstOrNull { it.path == path }?.apply {
media.add(this)
@ -231,6 +237,7 @@ class MediaFetcher(val context: Context) {
} else {
val lastModified = file.lastModified()
var dateTaken = lastModified
val videoDuration = if (getVideoDurations && isVideo) path.getVideoDuration() else 0
if (getProperDateTaken) {
dateTaken = dateTakens.remove(filename) ?: lastModified
@ -245,14 +252,15 @@ class MediaFetcher(val context: Context) {
}
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)
}
}
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 files = context.getDocumentFile(folder)?.listFiles() ?: return media
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 videoDuration = if (getVideoDurations) path.getVideoDuration() else 0
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)
}
@ -344,6 +353,11 @@ class MediaFetcher(val context: Context) {
}
fun sortMedia(media: ArrayList<Medium>, sorting: Int) {
if (sorting and SORT_BY_RANDOM != 0) {
media.shuffle()
return
}
media.sortWith(Comparator { o1, o2 ->
o1 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.graphics.Bitmap
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.Picasso

View file

@ -1,16 +1,17 @@
package com.simplemobiletools.gallery.helpers
package com.simplemobiletools.gallery.pro.helpers
import android.content.Context
import android.graphics.*
import android.net.Uri
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder
import com.davemorrissey.labs.subscaleview.ImageRegionDecoder
class PicassoRegionDecoder : ImageRegionDecoder {
private var decoder: BitmapRegionDecoder? = null
private val decoderLock = Any()
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)
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 android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query
import com.simplemobiletools.gallery.helpers.RECYCLE_BIN
import com.simplemobiletools.gallery.models.Directory
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy.REPLACE
import androidx.room.Query
import com.simplemobiletools.gallery.pro.helpers.RECYCLE_BIN
import com.simplemobiletools.gallery.pro.models.Directory
@Dao
interface DirectoryDao {
@ -29,4 +29,7 @@ interface DirectoryDao {
@Query("DELETE FROM directories WHERE path = \'$RECYCLE_BIN\' COLLATE NOCASE")
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
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

View file

@ -1,24 +1,24 @@
package com.simplemobiletools.gallery.interfaces
package com.simplemobiletools.gallery.pro.interfaces
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Delete
import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query
import com.simplemobiletools.gallery.models.Medium
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy.REPLACE
import androidx.room.Query
import com.simplemobiletools.gallery.pro.models.Medium
@Dao
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>
@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>
@Query("SELECT full_path FROM media WHERE deleted_ts = 0 AND is_favorite = 1")
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>
@Insert(onConflict = REPLACE)
@ -33,7 +33,7 @@ interface MediumDao {
@Query("DELETE FROM media WHERE full_path = :path COLLATE NOCASE")
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)
@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")
fun updateFavorite(path: String, isFavorite: Boolean)
@Query("UPDATE media SET deleted_ts = :deletedTS WHERE full_path = :path COLLATE NOCASE")
fun updateDeleted(path: String, deletedTS: Long)
@Query("UPDATE OR REPLACE media SET full_path = :newPath, deleted_ts = :deletedTS WHERE full_path = :oldPath COLLATE NOCASE")
fun updateDeleted(newPath: String, deletedTS: Long, oldPath: String)
@Query("UPDATE media SET date_taken = :dateTaken WHERE full_path = :path COLLATE NOCASE")
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)

View file

@ -1,20 +1,16 @@
package com.simplemobiletools.gallery.models
package com.simplemobiletools.gallery.pro.models
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
import androidx.room.*
import com.simplemobiletools.commons.extensions.formatDate
import com.simplemobiletools.commons.extensions.formatSize
import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED
import com.simplemobiletools.commons.helpers.SORT_BY_NAME
import com.simplemobiletools.commons.helpers.SORT_BY_PATH
import com.simplemobiletools.commons.helpers.SORT_BY_SIZE
import com.simplemobiletools.gallery.helpers.FAVORITES
import com.simplemobiletools.gallery.helpers.RECYCLE_BIN
import java.io.Serializable
import com.simplemobiletools.gallery.pro.helpers.FAVORITES
import com.simplemobiletools.gallery.pro.helpers.RECYCLE_BIN
@Entity(tableName = "directories", indices = [Index(value = "path", unique = true)])
@Entity(tableName = "directories", indices = [Index(value = ["path"], unique = true)])
data class Directory(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "path") var path: String,
@ -24,12 +20,14 @@ data class Directory(
@ColumnInfo(name = "last_modified") var modified: Long,
@ColumnInfo(name = "date_taken") var taken: Long,
@ColumnInfo(name = "size") var size: Long,
@ColumnInfo(name = "location") val location: Int,
@ColumnInfo(name = "media_types") var types: Int) : Serializable {
@ColumnInfo(name = "location") var location: Int,
@ColumnInfo(name = "media_types") var types: Int,
companion object {
private const val serialVersionUID = -6553345863555455L
}
// used with "Group direct subfolders" enabled
@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 {
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 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 android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import com.simplemobiletools.commons.extensions.formatDate
import com.simplemobiletools.commons.extensions.formatSize
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_PATH
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.util.*
@Entity(tableName = "media", indices = [(Index(value = "full_path", unique = true))])
@Entity(tableName = "media", indices = [(Index(value = ["full_path"], unique = true))])
data class Medium(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "filename") var name: String,
@ -25,6 +25,7 @@ data class Medium(
@ColumnInfo(name = "date_taken") var taken: Long,
@ColumnInfo(name = "size") val size: Long,
@ColumnInfo(name = "type") val type: Int,
@ColumnInfo(name = "video_duration") val videoDuration: Int,
@ColumnInfo(name = "is_favorite") var isFavorite: Boolean,
@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()

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.Context
import android.content.Intent
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.REFRESH_PATH
import com.simplemobiletools.gallery.extensions.galleryDB
import com.simplemobiletools.gallery.helpers.*
import com.simplemobiletools.gallery.models.Medium
import com.simplemobiletools.gallery.pro.extensions.galleryDB
import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.models.Medium
import java.io.File
class RefreshMediaReceiver : BroadcastReceiver() {
@ -16,7 +16,7 @@ class RefreshMediaReceiver : BroadcastReceiver() {
Thread {
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)
}.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.ResourceDecoder

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.svg
package com.simplemobiletools.gallery.pro.svg
import android.graphics.drawable.PictureDrawable
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.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.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.util.AttributeSet
import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.RelativeLayout
import com.simplemobiletools.gallery.helpers.CLICK_MAX_DURATION
import com.simplemobiletools.gallery.helpers.DRAG_THRESHOLD
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
// 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) {
@ -40,7 +41,9 @@ class InstantItemSwitch(context: Context, attrs: AttributeSet) : RelativeLayout(
mTouchDownTime = System.currentTimeMillis()
}
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()
}
}

View file

@ -1,4 +1,4 @@
package com.simplemobiletools.gallery.helpers
package com.simplemobiletools.gallery.pro.views
import android.app.Activity
import android.content.Context
@ -10,9 +10,12 @@ import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.TextView
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.ViewPagerActivity
import com.simplemobiletools.gallery.extensions.audioManager
import com.simplemobiletools.commons.extensions.onGlobalLayout
import com.simplemobiletools.gallery.pro.R
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
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 mTempBrightness = 0
private var mLastTouchY = 0f
private var mViewHeight = 0
private var mIsBrightnessScroll = false
private var mPassTouches = false
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 mSlideInfoFadeHandler = Handler()
private var mParentView: ViewGroup? = null
private var activity: Activity? = null
private lateinit var activity: Activity
private lateinit var slideInfoView: TextView
private lateinit var callback: (Float, Float) -> Unit
@ -42,6 +46,9 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
mParentView = parentView
mIsBrightnessScroll = isBrightness
mSlideInfoText = activity.getString(if (isBrightness) R.string.brightness else R.string.volume)
onGlobalLayout {
mViewHeight = height
}
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
@ -55,7 +62,7 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (mPassTouches) {
if (mPassTouches && activity == null) {
return false
}
@ -78,7 +85,7 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
val diffY = mTouchDownY - event.y
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))
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
}
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)
}
@ -112,11 +121,11 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
return true
}
private fun getCurrentVolume() = activity.audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
private fun getCurrentVolume() = activity!!.audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
private fun getCurrentBrightness(): Int {
return try {
Settings.System.getInt(activity.contentResolver, Settings.System.SCREEN_BRIGHTNESS)
Settings.System.getInt(activity!!.contentResolver, Settings.System.SCREEN_BRIGHTNESS)
} catch (e: Settings.SettingNotFoundException) {
70
}
@ -132,11 +141,11 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
private fun volumePercentChanged(percent: Int) {
val stream = AudioManager.STREAM_MUSIC
val maxVolume = activity.audioManager.getStreamMaxVolume(stream)
val maxVolume = activity!!.audioManager.getStreamMaxVolume(stream)
val percentPerPoint = 100 / maxVolume
val addPoints = percent / percentPerPoint
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()
showValue(absolutePercent)
@ -156,9 +165,9 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co
val absolutePercent = ((newBrightness / maxBrightness) * 100).toInt()
showValue(absolutePercent)
val attributes = activity.window.attributes
val attributes = activity!!.window.attributes
attributes.screenBrightness = absolutePercent / 100f
activity.window.attributes = attributes
activity!!.window.attributes = attributes
mSlideInfoFadeHandler.removeCallbacksAndMessages(null)
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.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