diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..1606d61fc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided this notice is +# preserved. This file is offered as-is, without any warranty. +# Names of contributors must not be used to endorse or promote products +# derived from this file without specific prior written permission. + +# EditorConfig +# http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# LF end-of-line, insert an empty new line and UTF-8 +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 +continuation_indent_size = 8 + +[*.xml] +continuation_indent_size = 4 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..9bad22a9b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: [tibbi] +patreon: tiborkaputa +custom: ["https://www.paypal.com/paypalme/simplemobiletools", "https://www.simplemobiletools.com/donate"] diff --git a/.gitignore b/.gitignore index 7051e766d..d94d37b66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,12 @@ *.iml +*.aab .gradle /local.properties -/gradle.properties /.idea/ .DS_Store /build /captures -release.keystore -signing.properties +keystore.jks +keystore.properties +/app/src/proprietary/assets/pesdk_license +/app/src/proprietary/assets/vesdk_license diff --git a/CHANGELOG.md b/CHANGELOG.md index e7657fb9e..8bf95193a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,1502 @@ Changelog ========== +Version 6.19.0 *(2021-01-21)* +---------------------------- + + * Allow easily clearing the app cache from the app settings + * Allow customizing the spacing between file thumbnails, or using rounded corners + * Many other performance, UI, UX, translation and stability improvements + +Version 6.18.3 *(2021-01-14)* +---------------------------- + + * Speed up fullscreen medium loading a bit too + +Version 6.18.2 *(2021-01-14)* +---------------------------- + + * Improve the performance at scrolling media thumbnails + * Speed up cached folder fetching + * Some stability and translation improvements + +Version 6.18.1 *(2021-01-03)* +---------------------------- + + * Updated the photo and video editor to fix some glitches + * Added some translation and stability improvements + +Version 6.18.0 *(2020-12-08)* +---------------------------- + + * Added a Video Editor + * Added Stickers and Overlays to the Photo Editor + * Some translation, stability and UI improvements + +Version 6.17.3 *(2020-11-10)* +---------------------------- + + * Properly update the folder thumbnail text colors + +Version 6.17.2 *(2020-11-09)* +---------------------------- + + * Adding some folder thumbnail customization at the app settings + * Allow setting a default folder to be opened at app launch (by roland-kister) + * Updated the photo editor to fix some glitches + +Version 6.17.1 *(2020-11-03)* +---------------------------- + + * Reverting to the previous UI of the main screen, but keep rounded corners at list view + * Some UI and stability improvements + +Version 6.17.0 *(2020-11-02)* +---------------------------- + + * Redesigning the main folders screen, try making it moderner (!) + * Fix some glitches with deep zoomable fullscreen images not loading in well + * Couple other UX, stability and translation improvements + +Version 6.16.5 *(2020-10-28)* +---------------------------- + + * Added some hidden folder handling related improvements + * Fixed saving files on the SD card after editing + * Couple other UX, translation and performance improvements + +Version 6.16.4 *(2020-10-24)* +---------------------------- + + * Fixed an editor glitch occurring if the file path contained spaces + * Added some stability improvements + +Version 6.16.3 *(2020-10-16)* +---------------------------- + + * Updated the photo editor to fix some issues, mostly related to Android 11 + * Make sure file moving doesn't block the UI in any case + +Version 6.16.2 *(2020-10-10)* +---------------------------- + + * Fix sorting by Date Taken + * Properly display some wrongly named WebP files + +Version 6.16.1 *(2020-09-27)* +---------------------------- + + * Fixed a bug with some folders not being recognized + * Improved thumbnail loading performance in some cases + +Version 6.16.0 *(2020-09-25)* +---------------------------- + + * Adding support for animated WebP files + * Fixed a permissions glitch after upgrading to Android 11 + * Added many performance related improvements + +Version 6.15.6 *(2020-09-22)* +---------------------------- + + * Fixed the file loading performance in some cases + * Fixed some photo editor related glitches + * Fixed the Use English language settings toggle + +Version 6.15.5 *(2020-09-16)* +---------------------------- + + * Properly fetch the latest available Last Modified of files + +Version 6.15.4 *(2020-09-16)* +---------------------------- + + * Improved file loading performance + +Version 6.15.3 *(2020-09-09)* +---------------------------- + + * Improved file loading performance in some cases + * Some UX, translation and stability improvements + +Version 6.15.2 *(2020-06-17)* +---------------------------- + + * Fixed a glitch at caching fullscreen images + * Properly recognize a new type of panoramic images + +Version 6.15.1 *(2020-06-12)* +---------------------------- + + * Fixed a glitch at caching fullscreen images + * Improved batch renaming with patterns + +Version 6.15.0 *(2020-06-07)* +---------------------------- + + * Rescan the internal storage from time to time, look for new folders containing media + * Improve the Search user experience, do not reset it at opening an item + * Fixed some editor glitches + * Speed up fullscreen image loading + +Version 6.14.10 *(2020-05-29)* +---------------------------- + + * Updated the image editor, fix a glitch with black preview + * Show Portrait images by default on Android 10 + * Use a nicer icon on older devices + * Some translation and stability improvements + +Version 6.14.9 *(2020-05-18)* +---------------------------- + + * Fixing some glitches with hidden folders not being shown when appropriate + * Added some stability and translation improvements + +Version 6.14.8 *(2020-05-05)* +---------------------------- + + * Added some crashfixes + +Version 6.14.7 *(2020-05-04)* +---------------------------- + + * Fixed a glitch with some favorite items disappearing + * Improve folder hiding, add the new .nomedia file into MediaStore + * Improve the performance of getting video file duration + * A few other improvements here and there + +Version 6.14.6 *(2020-04-26)* +---------------------------- + + * Added a few more stability and performance improvements + +Version 6.14.5 *(2020-04-26)* +---------------------------- + + * Disable Portrait photo showing by default, until it gets improved + * Added a couple other performance improvements here and there + +Version 6.14.4 *(2020-04-24)* +---------------------------- + + * Improved the performance on multiple places + * Removed some unnecessary permissions added recently + * Fixed some photo editor glitches + * Show Portrait images by default only on Android 9+ + +Version 6.14.3 *(2020-04-21)* +---------------------------- + + * Use the selected date format grouped thumbnail section titles too + * Fixed a glitch related to locked folders asking authentication too often + * Refresh the UI here and there a bit + +Version 6.14.2 *(2020-04-18)* +---------------------------- + + * Fixed some Photo Editor bugs + * Properly handle locked folders at opening from widgets and shortcuts + * Open the map at clicking the coordinates at the Properties window + * Properly sort items at the Other Folder dialog at copy/move + * Fixed some folder un/hiding related glitches + * A couple other translation and UX improvements + +Version 6.14.1 *(2020-04-14)* +---------------------------- + + * Fixed some SD card file related glitches + * Improved some third party intent handling + * Added some translation and stability improvements + +Version 6.14.0 *(2020-03-19)* +---------------------------- + + * Properly delete playing videos + * Removed the Manage Hidden Folders settings button on Android 10+, it no longer works + * Added many changes under the hood in preparation for handling Scoped Storage soon + * Do not require the Storage permission at some third party intents + +Version 6.13.4 *(2020-03-08)* +---------------------------- + + * Fixed some hiding, excluding and including related glitches + * Flipped Pin and Properties icons at the top menu for better user experience + * Avoid showing Portrait image folders at the main folders screen + +Version 6.13.3 *(2020-03-01)* +---------------------------- + + * Avoid showing the "No Date Takens found" error in some cases + +Version 6.13.2 *(2020-03-01)* +---------------------------- + + * Properly handle videos at slideshows + * Fixed some gestures during video playback + * Fixed a glitch with videos randomly restarting in some cases + +Version 6.13.1 *(2020-02-28)* +---------------------------- + + * Adding a quick crashfix + +Version 6.13.0 *(2020-02-28)* +---------------------------- + + * Allow fast forwarding videos by double clicking on screen sides + * Fixed an issue with the editor producing low quality outputs in some cases + * Improve some error messages, make them clearer + * Many translation and stability improvements + +Version 6.12.5 *(2020-02-12)* +---------------------------- + + * Fixed some sorting related glitches + * Keep the old last modified value at file editing + +Version 6.12.4 *(2020-02-11)* +---------------------------- + + * Fixed some folder sorting related glitches + +Version 6.12.3 *(2020-02-10)* +---------------------------- + + * Fixed image disappearing at using the Editor + * Properly copy over EXIF values after editing an image + +Version 6.12.2 *(2020-02-10)* +---------------------------- + + * Fixed some sorting and thumbnail related issues + +Version 6.12.1 *(2020-02-10)* +---------------------------- + + * Improved the performance of the initial screen loading + * Fixed some editor related glitches + +Version 6.12.0 *(2020-01-28)* +---------------------------- + + * Properly handle sorting by date taken after using "Fix Date Taken values" on the wrong files + * Fixed some issues at copying files, when the source was on an SD card + * Change the way Favorite items are stored, to avoid accidental removal + * Improved video looping (by ForgottenUmbrella) + * Recognize a new type of panoramic photos + * Properly remember last video playback position if the video is paused on exit + * Properly color the top status bar icons at using light primary colors + * Other UX and translation improvements + +Version 6.11.8 *(2020-01-19)* +---------------------------- + + * Reverted Glide to fix some crashes + +Version 6.11.7 *(2020-01-16)* +---------------------------- + + * Do not convert every edited file into a JPG, keep PNGs intact + * Fixed a glitch with empty portrait photos being shown as grey thumbnails + * Show a FAQ/settings prompt once at pressing Rate Us in the About section + * Added a 16:10 editor crop aspect ratio, used mostly on tablets + * Do some preparations for better handling Date Taken values and Favorites + * Other stability and translation improvements + +Version 6.11.6 *(2020-01-11)* +---------------------------- + + * Improved the image loading performance + * Allow excluding the root "/" folder + * Properly handle editing files with spaces in path + * Couple other UX, UI, translation and stability improvements + +Version 6.11.5 *(2020-01-04)* +---------------------------- + + * Fixed a few SD card related issues + * Fixed some theming issues + * Added some UI and translation improvements + +Version 6.11.4 *(2019-12-27)* +---------------------------- + + * Fixed a few SD card related issues + * Moved Focus in the editor as the last tool, to prioritize more popular tools + * Added a new Crop aspect ratio 37:18 to be used instead of 18.5x9 + * Some translation improvements + * Last app update for a while now, wishing you a Happy New Year! + +Version 6.11.3 *(2019-12-25)* +---------------------------- + + * Fixed various editor related glitches + * Some translation and other UX improvements + +Version 6.11.2 *(2019-12-21)* +---------------------------- + + * Added a few more aspect ratios in the editor + * Remember the last used editor brush settings + * Properly refresh the cache of edited images + +Version 6.11.1 *(2019-12-18)* +---------------------------- + + * Removing Text Design from the editor, it takes up too much space + +Version 6.11.0 *(2019-12-17)* +---------------------------- + + * Fully replaced the photo editor with a powerful third party library + * Added some crashfixes and stability improvements + +Version 6.10.8 *(2019-12-17)* +---------------------------- + + * Added some crashfixes and stability improvements + +Version 6.10.7 *(2019-12-12)* +---------------------------- + + * Refreshed the thumbnails list views by removing the dividers + * Reordered some top menu items for consistency + * Added a Resize button to resize images directly from the fullscreen view + * Migrate album covers at export/import settings too + * Remember the last used path and file name at exporting settings + * Fixed a glitch with empty screen at direct subfolder grouping + * Many other stability, ux and translation improvements + +Version 6.10.6 *(2019-11-28)* +---------------------------- + + * Fixed some smaller glitches + * Added some stability and translation improvements + +Version 6.10.5 *(2019-11-10)* +---------------------------- + + * Remember the last used pattern at batch renaming + * Allow adding an incrementing number at pattern batch renaming + * Fixed some USB file related issues + * Fixed some fullscreen glitches at using split screen + * Allow using videos as custom folder covers + * Some stability and translation improvements + +Version 6.10.4 *(2019-11-05)* +---------------------------- + + * Improved USB device handling + * Some smaller stability and translation improvements + +Version 6.10.3 *(2019-10-27)* +---------------------------- + + * Adding some smaller stability, translation improvements and bugfixes + +Version 6.10.2 *(2019-10-13)* +---------------------------- + + * Fixed a glitch with small letters in some cases + * Properly display SVG images + * Show a "Pro" label at the About sections' app version + +Version 6.10.1 *(2019-10-06)* +---------------------------- + + * Fixed some Portrait photo related crashes + +Version 6.10.0 *(2019-10-04)* +---------------------------- + + * Added initial support for Portrait images + * Updated some helper libraries + * Some stability and translation improvements + +Version 6.9.6 *(2019-09-12)* +---------------------------- + + * Improved the performance of loading fullscreen images in some cases + * Properly handle some specific SD cards + * Properly fetch Date Taken value on Android 10 + +Version 6.9.5 *(2019-09-08)* +---------------------------- + + * Added optional thumbnail icons for showing GIF/SVG/RAW file types + * Properly handle Date Taken value at copy/move/edit + * Fixed a glitch with white top actionmenu at selecting items + * Fixed fullscreen mode toggling on Chromebooks + +Version 6.9.4 *(2019-08-21)* +---------------------------- + + * Let's load a higher resolution image at the fullscreen view + +Version 6.9.3 *(2019-08-19)* +---------------------------- + + * Added some light theme related improvements + * Properly keep the last_modified field at copy/move in some new cases + * Changed the way fullscreen images are loaded to fix some rotation issues + * Fixed some video playback aspect ratio glitches + * Few other improvements here and there + +Version 6.9.2 *(2019-08-11)* +---------------------------- + + * Added some performance improvements at fullscreen media on weaker devices + * Allow long pressing Properties fields to copy values to the clipboard + * Show the errors occuring at file fetching with a toast + * Fixed a glitch at batch renaming using a pattern + * Try fixing Date Taken values automatically after copy/move + * Changed most of the icons to vectors for better quality and lower size + * Properly color the top menu icons + * Some other UX, performance and stability improvements + +Version 6.9.1 *(2019-08-03)* +---------------------------- + + * Fixing a video player related crash + +Version 6.9.0 *(2019-08-02)* +---------------------------- + + * Show a message at copy/move if the destination doesn't have enough space + * Rewrote the video playback to fix some glitches + * Improve the performance at loading initial screen folders + * Allow toggling between the old renaming of appending/prepending or using a pattern + * Some improvements related to folder un/hiding + +Version 6.8.4 *(2019-07-29)* +---------------------------- + + * Share files in the order they were selected + * Allow customizing the bottom navigation bar color + * Fixed some UI glitches related to fullscreen view bottom buttons + * Many other stability and UX improvements + +Version 6.8.3 *(2019-07-14)* +---------------------------- + + * Added support for HEIC/HEIF files + * Reverted back to the previous way of searching folders, with a button for searching all files instead + * Added some dark theme improvements + * Show some location related values at the Properties window, or at the Extended details + * Misc other stability, performance and translation improvements + +Version 6.8.2 *(2019-07-02)* +---------------------------- + + * Allow password protecting individual folders + * Let's try using dark theme menu + * Fixed the performance of scrolling GIF thumbnails + * Fixed some video stucking issues + * Some other stability and translation improvements + +Version 6.8.1 *(2019-06-27)* +---------------------------- + + * Improved Search on the main screen, allow using it for searching all files, not folders + * Added Print functionality at fullscreen images + * Fixed a glitch at PNGs getting deleted after rotating + * Other stability, translation and performance improvements + +Version 6.8.0 *(2019-06-21)* +---------------------------- + + * Allow grouping files by date_taken or last_modified either daily, or monthly + * Allow selecting fade animation or no animation at all at slideshow transitions + * Improved the performance at loading fullscreen videos + * Use last_modified value at batch file renaming, if date_taken isn't available + * Some other stability and translation improvements + +Version 6.7.9 *(2019-06-12)* +---------------------------- + + * Fixed a crash at zooming + +Version 6.7.8 *(2019-06-11)* +---------------------------- + + * Improved the UX at zooming and panning at the fullscreen view + * Fixed unchecking Favorite items in some cases + * Show the available aspect ratios at the editor by default + * Couple stability, performance and translation improvements + +Version 6.7.7 *(2019-05-28)* +---------------------------- + + * Fixed some file deleting related glitches + * Improved batch renaming, use the old file extension in case a new one is missing + +Version 6.7.6 *(2019-05-26)* +---------------------------- + + * Improved batch renaming, allow using date time patterns in it + * Fixed empty folder deleting after deleting its content + * Improved new file cache updating in the background + * Improved the placeholder text in case no files are found + * Keep last_modified field at deleting and restoring files from the bin + * Increase the max image duration at slideshows + * Highlight the warning at deleting a folder + * Other stability, translation and performance improvements + +Version 6.7.5 *(2019-05-15)* +---------------------------- + + * Hotfixing a glitch with opening third party intents + +Version 6.7.4 *(2019-05-15)* +---------------------------- + + * Speeded up video deleting from fullscreen view + * Hotfixed some crashes + +Version 6.7.3 *(2019-05-14)* +---------------------------- + + * Fixed folder sorting if used together with subfolder grouping + * Fixed some copy/move related progressbar issues + * Added many performance and stability improvements + +Version 6.7.2 *(2019-05-09)* +---------------------------- + + * Allow creating file or folder shortcuts only from Android 8+ + +Version 6.7.1 *(2019-05-08)* +---------------------------- + + * Allow creating file or folder shortcuts on home screen on Android 7+ + * Allow creating new folders on the file thumbnails screen too + * Added a checkbox at sorting by name/path to sort numbers by their actual numeric value + * Improve grouping direct subfolders, do not ignore parent folders without media files + * Show the Open Camera button at the menu on the main screen, instead of the Sort by button + * Other translation and stability improvements + +Version 6.7.0 *(2019-05-02)* +---------------------------- + + * Moved the video duration field at the top right corner of thumbnails, if enabled + * Fixed some fullscreen image related glitches + * Misc translation and stability improvements + +Version 6.6.4 *(2019-04-09)* +---------------------------- + + * Reverting to the previous way of sorting items by name/path + * Some stability and translation improvements + +Version 6.6.3 *(2019-04-02)* +---------------------------- + + * Fixed some OTG devices and SD card related glitches + * Drastically increased the sorting performance by file path and name by simplifying it + * Fixed some third party related issues at opening images/videos + * Allow zooming raw images + * Try making "Fix Date Taken values" more reliable in some cases + * Added an explanation dialog if someone upgrades to Pro app from the free one + * Remember all video positions if enabled, not just the last one (by centic9) + * Added a new FAQ item about the app size + +Version 6.6.1 *(2019-03-21)* +---------------------------- + + * Fixed recognizing of some SD cards + * Added some stability and translation improvements + +Version 6.6.0 *(2019-03-10)* +---------------------------- + + * Further improved new file discovery + * Exclude some folders by default, like fb stickers + * Added other stability and ux improvements + +Version 6.5.5 *(2019-03-05)* +---------------------------- + + * Improve new file discovery + * Fixed some third party intent related glitches + * Fixed some issues related to grouping thumbnails + * Added a note at Sorting and Grouping dialog to avoid some confusion + * Avoid deleting filtered out file types at deleting folders + +Version 6.5.4 *(2019-02-28)* +---------------------------- + + * Allow customizing file loading priority + * Fixed the handling of some image picker intents + * Speeded up renaming files on SD cards + * Added some file loading performance improvements + * Some stability improvements + +Version 6.5.3 *(2019-02-22)* +---------------------------- + + * Added main screen menu buttons for fast Recycle Bin showing/hiding + * Added a new setting item for changing date and time format + * Do not shuffle images with Random sorting that often + * Fixed some glitches related to file rename, delete, move + * Added many smaller bugfixes and UX improvements + +Version 6.5.2 *(2019-02-16)* +---------------------------- + + * Added an option to disable rotating fullscreen images with gestures + * Improved OTG usb device handling + * Fixed Grouping by date taken + * Improved the performance at deleting files from the fullscreen view + * Some stability improvements + +Version 6.5.1 *(2019-02-11)* +---------------------------- + + * Fixed a glitch with image panning + * Added a couple stability improvements + +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)* +---------------------------- + + * Fixed opening some email client attachments and MMS images + * Attempt to fix lagging at zooming in on some devices + * Couple other smaller bugfixes and improvements + +Version 4.6.1 *(2018-08-21)* +---------------------------- + + * Added a crashfix at loading fullscreen images + +Version 4.6.0 *(2018-08-20)* +---------------------------- + + * Added support for SVGs + * Improved fullscreen image quality and performance + * Properly show files with hastags and percentage signs in their paths + * Many other smaller UX improvements + +Version 4.5.2 *(2018-08-08)* +---------------------------- + + * Adding a toggle for disabling deep zoomable images + * Fix displaying third party images + * Couple smaller UX fixes + +Version 4.5.1 *(2018-08-07)* +---------------------------- + + * Adding a crashfix + +Version 4.5.0 *(2018-08-07)* +---------------------------- + + * Use real Move instead of the old copy/delete if both source and destination are on the internal storage + * Remake the fullscreen view, always use deep zoomable images with good quality + * Couple stability improvements + +Version 4.4.4 *(2018-08-02)* +---------------------------- + + * Adding a crashfix + +Version 4.4.3 *(2018-08-01)* +---------------------------- + + * Removed the More Donating Options from the Purchase Thank You dialog + +Version 4.4.2 *(2018-08-01)* +---------------------------- + + * Removed the homepage from About section + +Version 4.4.1 *(2018-07-30)* +---------------------------- + + * Hide both Play and Pause video buttons after 2 secs + * Improved Immersive mode fullscreen behaviour + * Some other stability improvements + +Version 4.4.0 *(2018-07-26)* +---------------------------- + + * Reworked the editor, added some filters + * Allow hiding the Recycle Bin from the main screen folders + * Added a menu item for fixing file Date Taken + * Fixed some glitches around recycle bin item restoring + * Fixed some issues with video playback on resume + * Many other UX and stability improvements + +Version 4.3.5 *(2018-07-17)* +---------------------------- + + * Fixed some Recycle bin related issues + * A few more UX and stability improvements + +Version 4.3.4 *(2018-07-15)* +---------------------------- + + * Fixed disappearing launcher icon after changing its color on some devices + * Fixed some video related errors + * Added "Set as" as an available action at the fullscreen bottom actions + * Do the appropriate actions at trying to delete the Recycle Bin or Favorites folders + * Fixed a glitch with some panorama images not recognized properly + * Avoid blank screen at toggling "Temporarily show hidden" + +Version 4.3.3 *(2018-07-06)* +---------------------------- + + * Couple stability improvements and glitch fixes + +Version 4.3.2 *(2018-07-04)* +---------------------------- + + * Added Panorama photo support + * Allow customizing visible fullscreen bottom actions + * Allow forcing portrait/landscape modes at fullscreen view + * Use Exoplayer for playing videos + * Many smaller UX and stability improvements + +Version 4.3.1 *(2018-06-28)* +---------------------------- + + * Adding some crashfixes + +Version 4.3.0 *(2018-06-28)* +---------------------------- + + * Added a Recycle Bin + * Allow grouping media thumbnails by different criteria + * Fixed some calculation glitches around fastscroller + * Change the fullscreen Edit icon to a pencil + * Allow sorting "Show All" separately + * Many smaller stability and UX improvements + +Version 4.2.1 *(2018-06-20)* +---------------------------- + + * Allow selecting Favorite items for easy access + * Fix sorting by Date Taken after files have been copied + * Couple other stability and UX improvements + +Version 4.2.0 *(2018-06-18)* +---------------------------- + + * Move some actions at the fullscreen view to the bottom of the screen + * Allow filtering out RAW images separately + * Add a warning if the user tries deleting a folder + * Properly reset the temporary Skip Delete Confirmation dialog + * Show a Pause button over video if not in fullscreen mode + * Fix some glitches around inserting pin/pattern/fingerprint + * Many other stability and ux improvements + +Version 4.1.1 *(2018-05-26)* +---------------------------- + + * Always set folder thumbnail based on folder content sorting + * Make sure hidden folders have the "(hidden)" appended + +Version 4.1.0 *(2018-05-25)* +---------------------------- + + * Added sorting by Date Taken + * Fixed file renaming on Android Oreo + * Fixed some scrollbar glitches + * Fixed broken "Use english language" in some cases + * Make sure only the proper files are shown at "Show all folders content" + * Many other smaller UX, stability improvements and bugfixes + +Version 4.0.0 *(2018-05-13)* +---------------------------- + + * Allow customizing the app launcher color + * Remove the top spinning circle at initial launch + * Many other bugfixes and UX/stability improvements + +Version 3.8.2 *(2018-04-26)* +---------------------------- + + * Rewrote media caching and new file discovery for better performance + * Many OTG file handling improvements + +Version 3.8.1 *(2018-04-24)* +---------------------------- + + * Rewrote media caching and new file discovery for better performance + * Some OTG file handling improvements + +Version 3.8.0 *(2018-04-22)* +---------------------------- + + * Rewrote media caching for better performance + * Cache all media items, not just 80 per folder + * Some additional performance and stability improvements + +Version 3.7.3 *(2018-04-15)* +---------------------------- + + * Show hidden folders when appropriate + +Version 3.7.2 *(2018-04-14)* +---------------------------- + + * Fix Edit intent handled by other apps + * Hide folders containing ".nomedia" file, even if explicitly included + * Remove sorting by Date Taken until proper implementation + +Version 3.7.1 *(2018-04-12)* +---------------------------- + + * Fix no media being shown to some people + * Fix some glitches at renaming files + * Show a count of files being deleted at the confirmation prompt + +Version 3.7.0 *(2018-04-10)* +---------------------------- + + * Rewrote media file fetching for better performance and new item discovering + * Make un/hiding folders quicker + * Make automatic fullscreen toggling animation smoother by delaying it + * Many other smaller performance and UX improvements + +Version 3.6.3 *(2018-03-30)* +---------------------------- + + * Couple file scanning and thumbnail displaying updates + * Show a dialog about the new Simple Clock app to some users + +Version 3.6.2 *(2018-03-23)* +---------------------------- + + * Fixing some crashes related to file scanning + * Do not scan Download folder file by file, it can contain many items + +Version 3.6.1 *(2018-03-22)* +---------------------------- + + * Set proper file mimetype after editing or other file operation + * Try scanning Screenshots and Download folders more thoroughly + * Couple stability improvements + +Version 3.6.0 *(2018-03-15)* +---------------------------- + + * Fix duplicate files at renaming or hiding + * Improve some third party handling + * Optimize rotated image saving to avoid Out of memory errors + * Optimize new item discoveries or folder refreshing + * Many other smaller performance and UX improvements + +Version 3.5.3 *(2018-03-03)* +---------------------------- + + * Properly keep last-modified at file copy/move if set so + * Misc other smaller glitch and translation improvements + +Version 3.5.2 *(2018-02-25)* +---------------------------- + + * Fixed third party intent uri generation + * Properly handle files with colon in filename + * Fix copying whole folders + +Version 3.5.1 *(2018-02-25)* +---------------------------- + + * Added a toggle for disabling pull-to-refresh + * Added a toggle for permanent Delete confirmation dialog skipping + * Fixed saving image after editing + +Version 3.5.0 *(2018-02-20)* +---------------------------- + + * Added copy/move progress notification + * Fixed some glitches a round rotating media by aspect ratio + * Added FAQ + * Make explicit folder inclusion recursive + * Added initial OTG device support + * Rewrote third party intent handling and file handling, fixed some bugs along the way + * Probably added some new bugs + +Version 3.4.1 *(2018-02-09)* +---------------------------- + + * Fix some glitches around swiping fullscreen media with instant media switch or gesture brightness change enabled + * Make changing image brightness with gestures disabled by default + * Allow skipping forward/backward videos by pressing max/current time + * Fix some cases of editing third party images + * Couple other stability improvements + +Version 3.4.0 *(2018-02-05)* +---------------------------- + + * Allow changing the brightness by vertical gestures on images (by trubitsyn) + * Properly fetch all media files from recognized folders + * Make thumbnail info on the main screen a bit easier to read + * Fix seeing blank thumbnail after deleting files in some cases + * Reset zoom level on orientation change at fullscreen media + * Add an optional extra check to avoid showing invalid files + * Add a toggle to prevent showing What's new on startup + * Many other stability and performance improvements + +Version 3.3.1 *(2018-01-29)* +---------------------------- + + * Added a toggle for replacing deep zoomable images with better quality ones + * Added a toggle for hiding Extended details when the statusbar is hidden + * Added a toggle for switching media files by clicking on screen sides + * Disable "Temporarily show hidden" after 10 minutes of backgrounding + * Split Settings in separate sections + +Version 3.3.0 *(2018-01-23)* +---------------------------- + + * Added optional one-finger drag zoom at fullscreen media (by gh123man) + * Allow opening the app even without any media files (by gh123man) + * Refresh media files in the background when Simple Camera creates a new photo/video + * Improve fullscreen media rotation by "Device Rotation" + +Version 3.2.4 *(2018-01-17)* +---------------------------- + + * An F-droid build only, trying to add screenshots there + +Version 3.2.3 *(2018-01-14)* +---------------------------- + + * An F-droid build only, fixing a compile error + +Version 3.2.2 *(2018-01-09)* +---------------------------- + + * Some scrolling issues fixed + * Improve new media file discovery + +Version 3.2.1 *(2018-01-08)* +---------------------------- + + * Adding a crashfix + * Couple scrollbar glitch fixes + +Version 3.2.0 *(2018-01-07)* +---------------------------- + + * Rewrote scrolling to improve the performance + * Disable "Delete empty folders" by default + * Added initial Search to media thumbnails screen + * Apply the hidden folder password protection to "Manage hidden folders" + * Replace Move with Copy/Delete on Android 7+ + * Improve SD card file support + +Version 3.1.2 *(2017-12-30)* +---------------------------- + + * Fixed some video related crashes + +Version 3.1.1 *(2017-12-29)* +---------------------------- + + * Added a new setting item for managing folders hidden with .nomedia + * Speed up image loading + * Use copy/delete instead of move on Android 8.x + * Improved double-tap zoom ratios + +Version 3.1.0 *(2017-12-25)* +---------------------------- + + * Fixed some issues around picking contact images + * Misc other improvements + +Version 3.0.3 *(2017-12-20)* +---------------------------- + + * Added a new Black & White theme with special handling + * Fixed opening MMS attachments + * Fixed viewing properties/sharing etc at fullscreen media + * Apply "Dark background at fullscreen media" to the status bar too + * Misc performance/stability improvements + +Version 3.0.2 *(2017-12-17)* +---------------------------- + + * Properly display email attachments + * Some crashfixes + +Version 3.0.1 *(2017-12-06)* +---------------------------- + + * Fix missing launcher icon on some devices + * Added an info bubble at scrolling by dragging + * Allow zooming gifs + * Display raw .dng files + +Version 3.0.0 *(2017-12-04)* +---------------------------- + + * Improved primary color customization + * Allow setting home and lock screen wallpapers separately on Android 7+ + * Many smaller performance and stability improvements + +Version 2.19.0 *(2017-11-23)* +---------------------------- + + * Rolled back to displaying images in RGB_565 quality for proper zoom and performance + * Load directory thumbnails faster if a new medium has been discovered + * Couple performance and stability improvements + +Version 2.18.1 *(2017-11-16)* +---------------------------- + + * Fixed some double-tap zoom issues + * Misc smaller fixes and improvements here and there + +Version 2.18.0 *(2017-11-09)* +---------------------------- + + * Added an option to use english language on non-english devices + * Added an option to password protect the whole app + * Added an option to lock screen orientation at fullscreen view + * Split the Rotate button to 3 buttons per degrees + * Changed the way fullscreen images are loaded for better quality + * Fixed many memory leaks and smaller issues + +Version 2.17.4 *(2017-11-06)* +---------------------------- + + * Fixed some third party intent handling + * Increased max columns count to 20 + * Allow rotating JPGs in a lossless way (by ltGuillaume) + +Version 2.17.3 *(2017-11-02)* +---------------------------- + + * Fixed some corrupt gif file related crashes + * Rotate jpgs on the internal storage by exif + * Fixed some invisible SD card content + +Version 2.17.2 *(2017-10-29)* +---------------------------- + + * Couple more minor fixes + +Version 2.17.1 *(2017-10-29)* +---------------------------- + + * Show "Set As" and "Edit" menu buttons at videos and gifs too + * Couple other smaller issues fixed + +Version 2.17.0 *(2017-10-28)* +---------------------------- + + * Added a toggle for keeping last-modified field at file copy/move/rename + * Improved GIF animation speed + * Implemented fileprovider support to third party intents + * Make rotation by "Device rotation" less sensitive + * Automatically append "_1" to filename after saving through the Editor + * Added support for Adaptive icons for Android 8 (by fiepi) + * Added Dutch translation (by ltGuillaume) + * Many other smaller improvements + +Version 2.16.1 *(2017-10-24)* +---------------------------- + + * Added a toggle for hiding folder media count on the main screen + * Fixed SD card folders not being visible on some devices + * Fixed videos not playing properly in some cases + * Do not modify last_modified at copy/move/rename + * Added support for 3gpp videos + +Version 2.16.0 *(2017-10-19)* +---------------------------- + + * Added sorting by path + * Added an option to show customizable extended details over fullscreen media + * Allow selecting Album cover photos from any folders + * Added a checkbox for skipping Delete confirmation dialog + +Version 2.15.2 *(2017-10-06)* +---------------------------- + + * Properly display SD card content to Android 4 users + * Fix displaying some third party media, like Bluemail attachments + * Fix media picking intents if "Show all folders content" is enabled + +Version 2.15.1 *(2017-10-01)* +---------------------------- + + * Updated commons library with minor fixes + +Version 2.15.0 *(2017-10-01)* +---------------------------- + + * Added fingerprint to hidden item protection + * Added a new List view type + * Fixed an issue with some hidden items being shown at "Show all folders content" + * Fixed typing in color hex codes manually with some keyboards + * Do not autosave rotated images in any case + * Tons of other performance, stability and UX improvements + +Version 2.14.4 *(2017-09-18)* +---------------------------- + + * Revert to the old way of loading fullscreen images to avoid issues on Android 7+ + +Version 2.14.3 *(2017-09-17)* +---------------------------- + + * Removed some error toast messages after delete, or if image loading failed + * Fixed some visual glitches at horizontal scrolling + * Disable pull-to-refresh at horizontal scrolling + * Many other smaller bugfixes and improvements + +Version 2.14.2 *(2017-09-11)* +---------------------------- + + * Fixing some glitches with fullscreen images + * Add an extra check to avoid displaying non-existing media + * Fix opening media from third party intents + +Version 2.14.1 *(2017-09-07)* +---------------------------- + + * Fixing some glitches around fullscreen view + +Version 2.14.0 *(2017-09-05)* +---------------------------- + + * Simplified the way of creating new folders + * Added a loop option to slideshows, slowed down the swipe animation + * Added an option to filter out gifs from slideshows + * Improved the quality of fullscreen images + * Properly allow excluding the root folder + +Version 2.13.4 *(2017-09-01)* +---------------------------- + + * Improved the image loading performance + * Added a switch for disabling video gestures + * Added a switch for deleting empty folders after deleting content + * Show excluded folder content at third party intent if needed + +Version 2.13.3 *(2017-08-29)* +---------------------------- + + * Fixing copy/move actions on some devices + +Version 2.13.2 *(2017-08-28)* +---------------------------- + + * Moved media type filter from Settings to the Action menu + * Allow filtering GIFs out + * Make sure we always show manually included folders + * Properly show hidden files, when open from some File Manager + +Version 2.13.1 *(2017-08-16)* +---------------------------- + + * Show a folder if its both excluded and included + * Many translation improvements + +Version 2.13.0 *(2017-08-07)* +---------------------------- + + * Allow changing the screen brightness and volume at videos by vertically dragging the screen sides + * Fixed sorting of numbers in filenames + * Fixed a glitch with hidden files sometimes temporarily visible + * Unified thumbnail corner icon sizes and style + +Version 2.12.6 *(2017-08-05)* +---------------------------- + + * Added slideshow at the fullscreen view + * Replaced the foreground color of selected items with a check + * Made copy/move to SD card a lot faster + +Version 2.12.5 *(2017-08-03)* +---------------------------- + + * Updating file operation on SD card + +Version 2.12.4 *(2017-08-03)* +---------------------------- + + * Fixed SD card file operations + +Version 2.12.3 *(2017-08-02)* +---------------------------- + + * Added pattern/pin protection for showing hidden items + * Hopefully fixed unintentional SD card file deleting + +Version 2.12.2 *(2017-07-09)* +---------------------------- + + * Added a toggle for replacing Share with Rotate at fullscreen media + * Some crashfixes and translation updates + +Version 2.12.1 *(2017-07-02)* +---------------------------- + + * Couple crashfixes + +Version 2.12.0 *(2017-07-01)* +---------------------------- + + * Added a button for disabling "Temporarily show hidden" + * Updated Glide (library used for loading images) to 4.0.0 + * Made playing gifs smooth + +Version 2.11.5 *(2017-06-29)* +---------------------------- + + * Added an indicator of folders located on SD cards + * Improved the way of rotating jpg images on the internal storage by modifying the exif tags + added autosave + +Version 2.11.4 *(2017-06-26)* +---------------------------- + + * Added an option for automatically hiding the system UI at entering fullscreen mode + * Fix deleting SD card files on some devices + * Couple crashfixes + +Version 2.11.3 *(2017-06-24)* +---------------------------- + + * Added optional horizontal scrolling + +Version 2.11.1 *(2017-06-19)* +---------------------------- + + * Fixed a crash at starting video + +Version 2.11.0 *(2017-06-18)* +---------------------------- + + * Store column count separately for portrait and landscape modes + * Improve zooming at double taping fullscreen images + * Allow opening a third party editor from our Editor screen + * Many crashfixes and smaller improvements + Version 2.10.10 *(2017-06-07)* ---------------------------- diff --git a/LICENSE b/LICENSE index 28c26588c..f288702d2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,674 @@ -Copyright 2016 SimpleMobileTools + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. - https://www.apache.org/licenses/LICENSE-2.0 + Preamble -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index 9d5d26e5c..501baa4b0 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,114 @@ # Simple Gallery -Logo +Logo -A gallery for viewing photos and videos. +Simple Gallery Pro is a highly customizable lightweight gallery loved by millions of people for its great user experience. Organize and edit your photos, recover deleted files with the recycle bin, protect & hide files and easily view a huge variety of different photo and video formats including RAW, SVG, GIF, panoramic and much more. -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. +------------------------------------------------- +SIMPLE GALLERY PRO – FEATURES +------------------------------------------------- -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. +• A beautiful modern gallery with no ads or popups -Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. +• Photo editor – crop, rotate, resize, draw, filters & more -This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com +• No internet access needed, giving you more privacy, security and stability -Get it on Google Play -Get it on F-Droid +• No unnecessary permissions required -App image -App image -App image +• Quickly search images, videos & other files -License -------- - Copyright 2017 SimpleMobileTools - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +• Open and view many different photo and video types (RAW, SVG, panoramic, GIF etc) + +• A variety of intuitive gestures to easily edit, rotate & organize files + +• Lots of ways to filter, group and sort files + +• Customize the appearance of Simple Gallery Pro + +• Available in over 30 languages + +• Mark files as favorites for quick access + +• Protect your photos & videos with a pattern, pin or fingerprint + +• Use pin, pattern & fingerprint to protect the app launch or specific functions too + +• Recover deleted photos & videos from the recycle bin + +• Toggle visibility of files to hide photos & videos + +• Create a customizable slideshow of your files with many options + +• View detailed information of your files (resolution, EXIF values etc) + +• Zoom high quality photos, videos and GIFs easily with gestures + +• Easily force portrait or landscape orientation for easy video viewing + +• Quick file share to social media, email or anywhere else + +• Print images or set them as wallpaper with a few clicks + +• Create home screen shortcuts or widgets to easily access any file or folder + +• Show the image location on a map, if available + +• Obvious presence of standard operations like rename, copy/move, un/hide, delete + +• Rewind videos with horizontal gestures + +• Change photo and video brightness, or volume with vertical gestures + +• Reorder, lock folders or change cover thumbnails anytime + +• Set any image as wallpaper without hassle + +… and much much more! + +ADVANCED PHOTO EDITOR +Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + +SUPPORT FOR MANY FILE TYPES +Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, GIF Panoramic photos, Panoramic videos and many more. + + +HIGHLY CUSTOMIZABLE FILE MANAGER +From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery has this kind of flexibility! Thanks to being open source, we’re also available in over 30 languages! + + +RECOVER DELETED PHOTOS & VIDEOS +Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + +PROTECT & HIDE PHOTOS, VIDEOS & FILES +Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + +Check out the full suite of Simple Tools here: +https://www.simplemobiletools.com + + +Standalone website of Simple Gallery Pro: +https://www.simplemobiletools.com/gallery + + +Facebook: +https://www.facebook.com/simplemobiletools + + +Reddit: +https://www.reddit.com/r/SimpleMobileTools + + +Don't forget that if you uninstall any paid app within 2 hours, you will automatically be refunded. If you want a refund anytime later, just contact us at hello@simplemobiletools.com and you will get it. That makes it easy to try it out :) + +Get it on Google Play +Get it on F-Droid + +
+App image +App image +App image +
diff --git a/app/build.gradle b/app/build.gradle index cd03e6b5f..096eddbd6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,78 +1,143 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' + +def keystorePropertiesFile = rootProject.file("keystore.properties") +def keystoreProperties = new Properties() +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} android { - compileSdkVersion 25 - buildToolsVersion "25.0.2" + compileSdkVersion 29 + buildToolsVersion "29.0.3" defaultConfig { - applicationId "com.simplemobiletools.gallery" - minSdkVersion 16 - targetSdkVersion 23 - versionCode 109 - versionName "2.10.10" + applicationId "com.simplemobiletools.gallery.pro" + minSdkVersion 21 + targetSdkVersion 29 + versionCode 335 + versionName "6.19.0" + setProperty("archivesBaseName", "gallery-$versionCode") + vectorDrawables.useSupportLibrary = true } signingConfigs { - release + if (keystorePropertiesFile.exists()) { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } } buildTypes { + debug { + // we cannot change the original package name, else PhotoEditorSDK won't work + //applicationIdSuffix ".debug" + } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.release + if (keystorePropertiesFile.exists()) { + signingConfig signingConfigs.release + } } } sourceSets { main.java.srcDirs += 'src/main/kotlin' + if (is_proprietary) { + main.java.srcDirs += 'src/proprietary/kotlin' + } + } + + flavorDimensions "licensing" + productFlavors { + proprietary {} + foss {} + } + + lintOptions { + checkReleaseBuilds false + abortOnError false + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + packagingOptions { + exclude 'META-INF/library_release.kotlin_module' } } dependencies { - compile 'com.simplemobiletools:commons:2.20.1' - compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.6.0' - compile 'com.theartofdev.edmodo:android-image-cropper:2.4.0' - compile 'com.bignerdranch.android:recyclerview-multiselect:0.2' - compile 'com.google.code.gson:gson:2.8.0' - compile 'com.github.chrisbanes:PhotoView:1.3.1' - compile 'it.sephiroth.android.exif:library:1.0.1' - compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.simplemobiletools:commons:5.33.7' + implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0' + implementation 'it.sephiroth.android.exif:library:1.0.1' + implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.19' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'com.google.android.exoplayer:exoplayer-core:2.9.6' + 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:512f929d82' + implementation 'com.github.tibbi:subsampling-scale-image-view:81c021514c' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + implementation 'com.github.penfeizhou.android.animation:awebp:2.5.1' - debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' - releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' - testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' + kapt 'com.github.bumptech.glide:compiler:4.10.0' + + kapt 'androidx.room:room-compiler:2.2.6' + implementation 'androidx.room:room-runtime:2.2.6' + annotationProcessor 'androidx.room:room-compiler:2.2.6' } -buildscript { - ext.kotlin_version = '1.1.2-3' - repositories { - mavenCentral() - } +// Apply the PESDKPlugin +if (is_proprietary) { + apply plugin: 'ly.img.android.sdk' - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" + imglyConfig { + vesdk { + enabled true + licencePath 'vesdk_license' + } + + pesdk { + enabled true + licencePath 'pesdk_license' + } + + modules { + include 'ui:video-trim' + include 'ui:core' + include 'ui:text' + include 'ui:focus' + include 'ui:brush' + include 'ui:filter' + include 'ui:sticker' + include 'ui:overlay' + include 'ui:transform' + include 'ui:adjustment' + + include 'backend:serializer' + include 'backend:sticker-smart' + include 'backend:sticker-animated' + + include 'assets:font-basic' + include 'assets:filter-basic' + include 'assets:overlay-basic' + include 'assets:sticker-shapes' + include 'assets:sticker-emoticons' + include 'assets:sticker-animated' + } } } - -def 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 -} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 34de0fb81..763f4ff3b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,5 +1,20 @@ -keep class com.simplemobiletools.** { *; } +-dontwarn android.graphics.Canvas -dontwarn com.simplemobiletools.** +-dontwarn org.apache.** --renamesourcefileattribute SourceFile --keepattributes SourceFile, LineNumberTable +# Picasso +-dontwarn javax.annotation.** +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase +-dontwarn org.codehaus.mojo.animal_sniffer.* +-dontwarn okhttp3.internal.platform.ConscryptPlatform + +-keepclassmembers class * implements android.os.Parcelable { + static ** CREATOR; +} + +# RenderScript +-keepclasseswithmembernames class * { +native ; +} +-keep class androidx.renderscript.** { *; } diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml new file mode 100644 index 000000000..ebf8b9d2a --- /dev/null +++ b/app/src/debug/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Gallery_debug + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8c640a923..2cbbf2613 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,33 +1,59 @@ + xmlns:tools="http://schemas.android.com/tools" + package="com.simplemobiletools.gallery.pro" + android:installLocation="auto"> + + + - + + + + + + + + android:theme="@style/AppTheme" + tools:replace="android:label"> - - - - - - + android:theme="@style/SplashTheme"/> + + + + + + @@ -53,13 +79,37 @@ + android:parentActivityName=".activities.MainActivity"> + + + + + + + + + + + + + + + + + + android:parentActivityName=".activities.MediaActivity"> @@ -82,18 +132,37 @@ android:label="@string/third_party_licences" android:parentActivityName="com.simplemobiletools.commons.activities.AboutActivity"/> - - + + + + + + + + + + @@ -107,10 +176,14 @@ android:label="@string/excluded_folders" android:parentActivityName=".activities.SettingsActivity"/> + + + android:configChanges="orientation|keyboardHidden|screenSize"> @@ -123,8 +196,7 @@ + android:configChanges="orientation|keyboardHidden|screenSize"> @@ -138,7 +210,10 @@ - + + + @@ -177,8 +252,17 @@ + + + + + + @@ -188,11 +272,283 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/App.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/App.kt deleted file mode 100644 index 9e46e19a9..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/App.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.simplemobiletools.gallery - -import android.app.Application -import com.squareup.leakcanary.LeakCanary - -class App : Application() { - override fun onCreate() { - super.onCreate() - if (LeakCanary.isInAnalyzerProcess(this)) { - return - } - LeakCanary.install(this) - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/EditActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/EditActivity.kt deleted file mode 100644 index d69eb1393..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/EditActivity.kt +++ /dev/null @@ -1,199 +0,0 @@ -package com.simplemobiletools.gallery.activities - -import android.app.Activity -import android.graphics.Bitmap -import android.graphics.Bitmap.CompressFormat -import android.graphics.Point -import android.net.Uri -import android.os.Bundle -import android.provider.MediaStore -import android.util.Log -import android.view.Menu -import android.view.MenuItem -import com.simplemobiletools.commons.extensions.getCompressionFormat -import com.simplemobiletools.commons.extensions.getFileOutputStream -import com.simplemobiletools.commons.extensions.scanPath -import com.simplemobiletools.commons.extensions.toast -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.dialogs.ResizeDialog -import com.simplemobiletools.gallery.dialogs.SaveAsDialog -import com.simplemobiletools.gallery.extensions.getRealPathFromURI -import com.theartofdev.edmodo.cropper.CropImageView -import kotlinx.android.synthetic.main.view_crop_image.* -import java.io.* - - -class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener { - val TAG = EditActivity::class.java.simpleName - val ASPECT_X = "aspectX" - val ASPECT_Y = "aspectY" - val CROP = "crop" - - lateinit var uri: Uri - var resizeWidth = 0 - var resizeHeight = 0 - var isCropIntent = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.view_crop_image) - - if (intent.data == null) { - toast(R.string.invalid_image_path) - finish() - return - } - - uri = intent.data - if (uri.scheme != "file" && uri.scheme != "content") { - toast(R.string.unknown_file_location) - finish() - return - } - - isCropIntent = intent.extras?.get(CROP) == "true" - - crop_image_view.apply { - setOnCropImageCompleteListener(this@EditActivity) - setImageUriAsync(intent.data) - - if (isCropIntent && shouldCropSquare()) - setFixedAspectRatio(true) - } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_editor, menu) - menu.findItem(R.id.resize).isVisible = !isCropIntent - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.save_as -> crop_image_view.getCroppedImageAsync() - R.id.rotate -> crop_image_view.rotateImage(90) - R.id.resize -> resizeImage() - R.id.flip_horizontally -> flipImage(true) - R.id.flip_vertically -> flipImage(false) - else -> return super.onOptionsItemSelected(item) - } - return true - } - - private fun resizeImage() { - val point = getAreaSize() - if (point == null) { - toast(R.string.unknown_error_occurred) - return - } - - ResizeDialog(this, point) { - resizeWidth = it.x - resizeHeight = it.y - crop_image_view.getCroppedImageAsync() - } - } - - private fun shouldCropSquare(): Boolean { - val extras = intent.extras - return if (extras != null && extras.containsKey(ASPECT_X) && extras.containsKey(ASPECT_Y)) { - extras.getInt(ASPECT_X) == extras.getInt(ASPECT_Y) - } else { - false - } - } - - private fun getAreaSize(): Point? { - val rect = crop_image_view.cropRect ?: return null - val rotation = crop_image_view.rotatedDegrees - return if (rotation == 0 || rotation == 180) { - Point(rect.width(), rect.height()) - } else { - Point(rect.height(), rect.width()) - } - } - - override fun onCropImageComplete(view: CropImageView, result: CropImageView.CropResult) { - if (result.error == null) { - if (isCropIntent && intent.extras?.containsKey(MediaStore.EXTRA_OUTPUT) == true) { - val targetUri = intent.extras!!.get(MediaStore.EXTRA_OUTPUT) as Uri - var inputStream: InputStream? = null - var outputStream: OutputStream? = null - try { - val stream = ByteArrayOutputStream() - result.bitmap.compress(CompressFormat.JPEG, 100, stream) - inputStream = ByteArrayInputStream(stream.toByteArray()) - outputStream = contentResolver.openOutputStream(targetUri) - inputStream.copyTo(outputStream) - } finally { - inputStream?.close() - outputStream?.close() - } - setResult(RESULT_OK) - finish() - } else if (uri.scheme == "file") { - SaveAsDialog(this, uri.path) { - saveBitmapToFile(result.bitmap, it) - } - } else if (uri.scheme == "content") { - val newPath = applicationContext.getRealPathFromURI(uri) ?: "" - if (!newPath.isEmpty()) { - SaveAsDialog(this, newPath) { - saveBitmapToFile(result.bitmap, it) - } - } else { - toast(R.string.image_editing_failed) - finish() - } - } else { - toast(R.string.unknown_file_location) - finish() - } - } else { - toast("${getString(R.string.image_editing_failed)}: ${result.error.message}") - } - } - - private fun saveBitmapToFile(bitmap: Bitmap, path: String) { - val file = File(path) - - try { - getFileOutputStream(file) { - saveBitmap(file, bitmap, it) - } - } catch (e: Exception) { - Log.e(TAG, "Crop compressing failed $path $e") - toast(R.string.image_editing_failed) - finish() - } catch (e: OutOfMemoryError) { - toast(R.string.out_of_memory_error) - } - } - - private fun saveBitmap(file: File, bitmap: Bitmap, out: OutputStream) { - if (resizeWidth > 0 && resizeHeight > 0) { - val resized = Bitmap.createScaledBitmap(bitmap, resizeWidth, resizeHeight, false) - resized.compress(file.getCompressionFormat(), 90, out) - } else { - bitmap.compress(file.getCompressionFormat(), 90, out) - } - setResult(Activity.RESULT_OK, intent) - scanFinalPath(file.absolutePath) - out.close() - } - - private fun flipImage(horizontally: Boolean) { - if (horizontally) - crop_image_view.flipImageHorizontally() - else - crop_image_view.flipImageVertically() - } - - private fun scanFinalPath(path: String) { - scanPath(path) { - setResult(Activity.RESULT_OK, intent) - toast(R.string.file_saved) - finish() - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ExcludedFoldersActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ExcludedFoldersActivity.kt deleted file mode 100644 index a37f6d452..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ExcludedFoldersActivity.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.simplemobiletools.gallery.activities - -import android.graphics.PorterDuff -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import com.simplemobiletools.commons.dialogs.FilePickerDialog -import com.simplemobiletools.commons.extensions.beVisibleIf -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.extensions.config -import kotlinx.android.synthetic.main.activity_excluded_folders.* -import kotlinx.android.synthetic.main.item_manage_folder.view.* - -class ExcludedFoldersActivity : SimpleActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_excluded_folders) - updateExcludedFolders() - } - - private fun updateExcludedFolders() { - excluded_folders_holder.removeAllViews() - val folders = config.excludedFolders - excluded_folders_placeholder.beVisibleIf(folders.isEmpty()) - excluded_folders_placeholder.setTextColor(config.textColor) - - for (folder in folders) { - layoutInflater.inflate(R.layout.item_manage_folder, null, false).apply { - managed_folder_title.apply { - text = folder - setTextColor(config.textColor) - } - managed_folders_icon.apply { - setColorFilter(config.textColor, PorterDuff.Mode.SRC_IN) - setOnClickListener { - config.removeExcludedFolder(folder) - updateExcludedFolders() - } - } - excluded_folders_holder.addView(this) - } - } - } - - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.menu_excluded_folders, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.add_folder -> addExcludedFolder() - else -> return super.onOptionsItemSelected(item) - } - return true - } - - private fun addExcludedFolder() { - FilePickerDialog(this, pickFile = false, showHidden = config.shouldShowHidden) { - config.addExcludedFolder(it) - updateExcludedFolders() - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/IncludedFoldersActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/IncludedFoldersActivity.kt deleted file mode 100644 index f69f4f987..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/IncludedFoldersActivity.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.simplemobiletools.gallery.activities - -import android.graphics.PorterDuff -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import com.simplemobiletools.commons.dialogs.FilePickerDialog -import com.simplemobiletools.commons.extensions.beVisibleIf -import com.simplemobiletools.commons.extensions.scanPath -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.extensions.config -import kotlinx.android.synthetic.main.activity_included_folders.* -import kotlinx.android.synthetic.main.item_manage_folder.view.* - -class IncludedFoldersActivity : SimpleActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_included_folders) - updateIncludedFolders() - } - - private fun updateIncludedFolders() { - included_folders_holder.removeAllViews() - val folders = config.includedFolders - included_folders_placeholder.beVisibleIf(folders.isEmpty()) - included_folders_placeholder.setTextColor(config.textColor) - - for (folder in folders) { - layoutInflater.inflate(R.layout.item_manage_folder, null, false).apply { - managed_folder_title.apply { - text = folder - setTextColor(config.textColor) - } - managed_folders_icon.apply { - setColorFilter(config.textColor, PorterDuff.Mode.SRC_IN) - setOnClickListener { - config.removeIncludedFolder(folder) - updateIncludedFolders() - } - } - included_folders_holder.addView(this) - } - } - } - - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.menu_included_folders, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.add_folder -> addIncludedFolder() - else -> return super.onOptionsItemSelected(item) - } - return true - } - - private fun addIncludedFolder() { - FilePickerDialog(this, pickFile = false, showHidden = config.shouldShowHidden) { - config.addIncludedFolder(it) - updateIncludedFolders() - scanPath(it) {} - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MainActivity.kt deleted file mode 100644 index 8e5c99539..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MainActivity.kt +++ /dev/null @@ -1,441 +0,0 @@ -package com.simplemobiletools.gallery.activities - -import android.Manifest -import android.app.Activity -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri -import android.os.AsyncTask -import android.os.Build -import android.os.Bundle -import android.os.Handler -import android.provider.MediaStore -import android.support.v4.app.ActivityCompat -import android.support.v7.widget.GridLayoutManager -import android.view.Menu -import android.view.MenuItem -import com.google.gson.Gson -import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.models.Release -import com.simplemobiletools.gallery.BuildConfig -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.adapters.DirectoryAdapter -import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask -import com.simplemobiletools.gallery.dialogs.ChangeSortingDialog -import com.simplemobiletools.gallery.extensions.* -import com.simplemobiletools.gallery.helpers.* -import com.simplemobiletools.gallery.models.Directory -import com.simplemobiletools.gallery.views.MyScalableRecyclerView -import kotlinx.android.synthetic.main.activity_main.* -import java.io.File -import java.io.FileInputStream -import java.io.InputStream -import java.io.OutputStream -import java.util.* - -class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener { - private val STORAGE_PERMISSION = 1 - private val PICK_MEDIA = 2 - private val PICK_WALLPAPER = 3 - private val LAST_MEDIA_CHECK_PERIOD = 3000L - - lateinit var mDirs: ArrayList - - private var mIsPickImageIntent = false - private var mIsPickVideoIntent = false - private var mIsGetImageContentIntent = false - private var mIsGetVideoContentIntent = false - private var mIsGetAnyContentIntent = false - private var mIsSetWallpaperIntent = false - private var mIsThirdPartyIntent = false - private var mIsGettingDirs = false - private var mStoredAnimateGifs = true - private var mStoredCropThumbnails = true - private var mLoadedInitialPhotos = false - private var mLastMediaModified = 0 - private var mLastMediaHandler = Handler() - - private var mCurrAsyncTask: GetDirectoriesAsynctask? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - mIsPickImageIntent = isPickImageIntent(intent) - mIsPickVideoIntent = isPickVideoIntent(intent) - mIsGetImageContentIntent = isGetImageContentIntent(intent) - mIsGetVideoContentIntent = isGetVideoContentIntent(intent) - mIsGetAnyContentIntent = isGetAnyContentIntent(intent) - mIsSetWallpaperIntent = isSetWallpaperIntent(intent) - mIsThirdPartyIntent = mIsPickImageIntent || mIsPickVideoIntent || mIsGetImageContentIntent || mIsGetVideoContentIntent || - mIsGetAnyContentIntent || mIsSetWallpaperIntent - - directories_refresh_layout.setOnRefreshListener({ getDirectories() }) - mDirs = ArrayList() - mStoredAnimateGifs = config.animateGifs - mStoredCropThumbnails = config.cropThumbnails - storeStoragePaths() - checkWhatsNewDialog() - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - if (mIsThirdPartyIntent) { - menuInflater.inflate(R.menu.menu_main_intent, menu) - } else { - menuInflater.inflate(R.menu.menu_main, menu) - menu.findItem(R.id.increase_column_count).isVisible = config.dirColumnCnt < 10 - menu.findItem(R.id.reduce_column_count).isVisible = config.dirColumnCnt > 1 - } - menu.findItem(R.id.temporarily_show_hidden).isVisible = !config.showHiddenMedia - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.sort -> showSortingDialog() - R.id.open_camera -> launchCamera() - R.id.show_all -> showAllMedia() - R.id.temporarily_show_hidden -> temporarilyShowHidden() - R.id.increase_column_count -> increaseColumnCount() - R.id.reduce_column_count -> reduceColumnCount() - R.id.settings -> launchSettings() - R.id.about -> launchAbout() - else -> return super.onOptionsItemSelected(item) - } - return true - } - - override fun onResume() { - super.onResume() - if (mStoredAnimateGifs != config.animateGifs) { - mDirs.clear() - } - - if (mStoredCropThumbnails != config.cropThumbnails) { - mDirs.clear() - } - tryloadGallery() - invalidateOptionsMenu() - } - - override fun onPause() { - super.onPause() - mCurrAsyncTask?.shouldStop = true - storeDirectories() - directories_refresh_layout.isRefreshing = false - mIsGettingDirs = false - mStoredAnimateGifs = config.animateGifs - mStoredCropThumbnails = config.cropThumbnails - MyScalableRecyclerView.mListener = null - mLastMediaHandler.removeCallbacksAndMessages(null) - } - - override fun onDestroy() { - super.onDestroy() - config.temporarilyShowHidden = false - } - - private fun tryloadGallery() { - if (hasWriteStoragePermission()) { - if (config.showAll) - showAllMedia() - else - getDirectories() - handleZooming() - checkIfColorChanged() - } else { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), STORAGE_PERMISSION) - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - - if (requestCode == STORAGE_PERMISSION) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - getDirectories() - } else { - toast(R.string.no_storage_permissions) - finish() - } - } - } - - private fun getDirectories() { - if (mIsGettingDirs) - return - - mIsGettingDirs = true - val dirs = getCachedDirectories() - if (dirs.isNotEmpty() && !mLoadedInitialPhotos) { - gotDirectories(dirs) - } - - if (!mLoadedInitialPhotos) { - directories_refresh_layout.isRefreshing = true - } - - mLoadedInitialPhotos = true - mCurrAsyncTask = GetDirectoriesAsynctask(applicationContext, mIsPickVideoIntent || mIsGetVideoContentIntent, mIsPickImageIntent || mIsGetImageContentIntent) { - gotDirectories(it) - } - mCurrAsyncTask!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) - } - - private fun showSortingDialog() { - ChangeSortingDialog(this, true, false) { - getDirectories() - } - } - - private fun showAllMedia() { - config.showAll = true - Intent(this, MediaActivity::class.java).apply { - putExtra(DIRECTORY, "/") - startActivity(this) - } - finish() - } - - private fun temporarilyShowHidden() { - config.temporarilyShowHidden = true - getDirectories() - } - - private fun checkIfColorChanged() { - if (directories_grid.adapter != null && getRecyclerAdapter().foregroundColor != config.primaryColor) { - getRecyclerAdapter().updatePrimaryColor(config.primaryColor) - directories_fastscroller.updateHandleColor() - } - } - - override fun tryDeleteFolders(folders: ArrayList) { - for (file in folders) { - deleteFolders(folders) { - runOnUiThread { - refreshItems() - } - } - } - } - - private fun getRecyclerAdapter() = (directories_grid.adapter as DirectoryAdapter) - - private fun handleZooming() { - val layoutManager = directories_grid.layoutManager as GridLayoutManager - layoutManager.spanCount = config.dirColumnCnt - MyScalableRecyclerView.mListener = object : MyScalableRecyclerView.MyScalableRecyclerViewListener { - override fun zoomIn() { - if (layoutManager.spanCount > 1) { - reduceColumnCount() - getRecyclerAdapter().actMode?.finish() - } - } - - override fun zoomOut() { - if (layoutManager.spanCount < 10) { - increaseColumnCount() - getRecyclerAdapter().actMode?.finish() - } - } - - override fun selectItem(position: Int) { - getRecyclerAdapter().selectItem(position) - } - - override fun selectRange(initialSelection: Int, lastDraggedIndex: Int, minReached: Int, maxReached: Int) { - getRecyclerAdapter().selectRange(initialSelection, lastDraggedIndex, minReached, maxReached) - } - } - } - - private fun increaseColumnCount() { - config.dirColumnCnt = ++(directories_grid.layoutManager as GridLayoutManager).spanCount - invalidateOptionsMenu() - } - - private fun reduceColumnCount() { - config.dirColumnCnt = --(directories_grid.layoutManager as GridLayoutManager).spanCount - invalidateOptionsMenu() - } - - private fun isPickImageIntent(intent: Intent) = isPickIntent(intent) && (hasImageContentData(intent) || isImageType(intent)) - - private fun isPickVideoIntent(intent: Intent) = isPickIntent(intent) && (hasVideoContentData(intent) || isVideoType(intent)) - - private fun isPickIntent(intent: Intent) = intent.action == Intent.ACTION_PICK - - private fun isGetContentIntent(intent: Intent) = intent.action == Intent.ACTION_GET_CONTENT && intent.type != null - - private fun isGetImageContentIntent(intent: Intent) = isGetContentIntent(intent) && - (intent.type.startsWith("image/") || intent.type == MediaStore.Images.Media.CONTENT_TYPE) - - private fun isGetVideoContentIntent(intent: Intent) = isGetContentIntent(intent) && - (intent.type.startsWith("video/") || intent.type == MediaStore.Video.Media.CONTENT_TYPE) - - private fun isGetAnyContentIntent(intent: Intent) = isGetContentIntent(intent) && intent.type == "*/*" - - private fun isSetWallpaperIntent(intent: Intent?) = intent?.action == Intent.ACTION_SET_WALLPAPER - - private fun hasImageContentData(intent: Intent) = (intent.data == MediaStore.Images.Media.EXTERNAL_CONTENT_URI || - intent.data == MediaStore.Images.Media.INTERNAL_CONTENT_URI) - - private fun hasVideoContentData(intent: Intent) = (intent.data == MediaStore.Video.Media.EXTERNAL_CONTENT_URI || - intent.data == MediaStore.Video.Media.INTERNAL_CONTENT_URI) - - private fun isImageType(intent: Intent) = (intent.type?.startsWith("image/") == true || intent.type == MediaStore.Images.Media.CONTENT_TYPE) - - private fun isVideoType(intent: Intent) = (intent.type?.startsWith("video/") == true || intent.type == MediaStore.Video.Media.CONTENT_TYPE) - - override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { - if (resultCode == Activity.RESULT_OK) { - if (requestCode == PICK_MEDIA && resultData?.data != null) { - Intent().apply { - val path = resultData.data.path - val uri = Uri.fromFile(File(path)) - if (mIsGetImageContentIntent || mIsGetVideoContentIntent || mIsGetAnyContentIntent) { - if (intent.extras?.containsKey(MediaStore.EXTRA_OUTPUT) == true) { - var inputStream: InputStream? = null - var outputStream: OutputStream? = null - try { - val output = intent.extras.get(MediaStore.EXTRA_OUTPUT) as Uri - inputStream = FileInputStream(File(path)) - outputStream = contentResolver.openOutputStream(output) - inputStream.copyTo(outputStream) - } finally { - inputStream?.close() - outputStream?.close() - } - } else { - val type = File(path).getMimeType("image/jpeg") - setDataAndTypeAndNormalize(uri, type) - flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - } - } else if (mIsPickImageIntent || mIsPickVideoIntent) { - data = uri - flags = Intent.FLAG_GRANT_READ_URI_PERMISSION - } - - setResult(Activity.RESULT_OK, this) - } - finish() - } else if (requestCode == PICK_WALLPAPER) { - setResult(Activity.RESULT_OK) - finish() - } - } - super.onActivityResult(requestCode, resultCode, resultData) - } - - private fun itemClicked(path: String) { - Intent(this, MediaActivity::class.java).apply { - putExtra(DIRECTORY, path) - - if (mIsSetWallpaperIntent) { - putExtra(SET_WALLPAPER_INTENT, true) - startActivityForResult(this, PICK_WALLPAPER) - } else { - putExtra(GET_IMAGE_INTENT, mIsPickImageIntent || mIsGetImageContentIntent) - putExtra(GET_VIDEO_INTENT, mIsPickVideoIntent || mIsGetVideoContentIntent) - putExtra(GET_ANY_INTENT, mIsGetAnyContentIntent) - startActivityForResult(this, PICK_MEDIA) - } - } - } - - private fun gotDirectories(dirs: ArrayList) { - mLastMediaModified = getLastMediaModified() - directories_refresh_layout.isRefreshing = false - mIsGettingDirs = false - - checkLastMediaChanged() - if (dirs.hashCode() == mDirs.hashCode()) - return - - mDirs = dirs - - setupAdapter() - storeDirectories() - } - - private fun storeDirectories() { - if (!config.temporarilyShowHidden) { - val directories = Gson().toJson(mDirs) - config.directories = directories - } - } - - private fun setupAdapter() { - val adapter = DirectoryAdapter(this, mDirs, this) { - itemClicked(it.path) - } - - val currAdapter = directories_grid.adapter - if (currAdapter != null) { - (currAdapter as DirectoryAdapter).updateDirs(mDirs) - } else { - directories_grid.adapter = adapter - } - directories_fastscroller.setViews(directories_grid, directories_refresh_layout) - } - - private fun checkLastMediaChanged() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed) - return - - mLastMediaHandler.removeCallbacksAndMessages(null) - mLastMediaHandler.postDelayed({ - Thread({ - val lastModified = getLastMediaModified() - if (mLastMediaModified != lastModified) { - mLastMediaModified = lastModified - runOnUiThread { - getDirectories() - } - } else { - checkLastMediaChanged() - } - }).start() - }, LAST_MEDIA_CHECK_PERIOD) - } - - override fun refreshItems() { - getDirectories() - } - - override fun itemLongClicked(position: Int) { - directories_grid.setDragSelectActive(position) - } - - private fun checkWhatsNewDialog() { - arrayListOf().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)) - checkWhatsNew(this, BuildConfig.VERSION_CODE) - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MediaActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MediaActivity.kt deleted file mode 100644 index f6cefa981..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/MediaActivity.kt +++ /dev/null @@ -1,439 +0,0 @@ -package com.simplemobiletools.gallery.activities - -import android.app.Activity -import android.app.WallpaperManager -import android.content.Intent -import android.graphics.Bitmap -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.os.Handler -import android.support.v7.widget.GridLayoutManager -import android.util.Log -import android.view.Menu -import android.view.MenuItem -import com.bumptech.glide.Glide -import com.bumptech.glide.request.animation.GlideAnimation -import com.bumptech.glide.request.target.SimpleTarget -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import com.simplemobiletools.commons.dialogs.ConfirmationDialog -import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.adapters.MediaAdapter -import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask -import com.simplemobiletools.gallery.dialogs.ChangeSortingDialog -import com.simplemobiletools.gallery.dialogs.ExcludeFolderDialog -import com.simplemobiletools.gallery.extensions.* -import com.simplemobiletools.gallery.helpers.* -import com.simplemobiletools.gallery.models.Medium -import com.simplemobiletools.gallery.views.MyScalableRecyclerView -import kotlinx.android.synthetic.main.activity_media.* -import java.io.File -import java.io.IOException - -class MediaActivity : SimpleActivity(), MediaAdapter.MediaOperationsListener { - private val TAG = MediaActivity::class.java.simpleName - private val SAVE_MEDIA_CNT = 40 - private val LAST_MEDIA_CHECK_PERIOD = 3000L - - private var mPath = "" - private var mIsGetImageIntent = false - private var mIsGetVideoIntent = false - private var mIsGetAnyIntent = false - private var mIsGettingMedia = false - private var mShowAll = false - private var mLoadedInitialPhotos = false - private var mStoredAnimateGifs = true - private var mStoredCropThumbnails = true - private var mLastDrawnHashCode = 0 - private var mLastMediaModified = 0 - private var mLastMediaHandler = Handler() - - companion object { - var mMedia = ArrayList() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_media) - intent.apply { - mIsGetImageIntent = getBooleanExtra(GET_IMAGE_INTENT, false) - mIsGetVideoIntent = getBooleanExtra(GET_VIDEO_INTENT, false) - mIsGetAnyIntent = getBooleanExtra(GET_ANY_INTENT, false) - } - - media_refresh_layout.setOnRefreshListener({ getMedia() }) - mPath = intent.getStringExtra(DIRECTORY) - mStoredAnimateGifs = config.animateGifs - mStoredCropThumbnails = config.cropThumbnails - mShowAll = config.showAll - if (mShowAll) - supportActionBar?.setDisplayHomeAsUpEnabled(false) - } - - override fun onResume() { - super.onResume() - if (mShowAll && mStoredAnimateGifs != config.animateGifs) { - mMedia.clear() - } - - if (mStoredCropThumbnails != config.cropThumbnails) { - mMedia.clear() - } - tryloadGallery() - invalidateOptionsMenu() - } - - override fun onPause() { - super.onPause() - mIsGettingMedia = false - media_refresh_layout.isRefreshing = false - mStoredAnimateGifs = config.animateGifs - mStoredCropThumbnails = config.cropThumbnails - MyScalableRecyclerView.mListener = null - mLastMediaHandler.removeCallbacksAndMessages(null) - } - - override fun onDestroy() { - super.onDestroy() - if (config.showAll) - config.temporarilyShowHidden = false - mMedia.clear() - } - - private fun tryloadGallery() { - if (hasWriteStoragePermission()) { - val dirName = getHumanizedFilename(mPath) - title = if (mShowAll) resources.getString(R.string.all_folders) else dirName - getMedia() - handleZooming() - checkIfColorChanged() - } else { - finish() - } - } - - private fun checkIfColorChanged() { - if (media_grid.adapter != null && getRecyclerAdapter().foregroundColor != config.primaryColor) { - getRecyclerAdapter().updatePrimaryColor(config.primaryColor) - media_fastscroller.updateHandleColor() - } - } - - private fun setupAdapter() { - if (isDirEmpty()) - return - - val adapter = MediaAdapter(this, mMedia, this) { - itemClicked(it.path) - } - - val currAdapter = media_grid.adapter - if (currAdapter != null) { - (currAdapter as MediaAdapter).updateMedia(mMedia) - } else { - media_grid.adapter = adapter - } - media_fastscroller.setViews(media_grid, media_refresh_layout) - } - - private fun checkLastMediaChanged() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed) - return - - mLastMediaHandler.removeCallbacksAndMessages(null) - mLastMediaHandler.postDelayed({ - Thread({ - val lastModified = getLastMediaModified() - if (mLastMediaModified != lastModified) { - mLastMediaModified = lastModified - runOnUiThread { - getMedia() - } - } else { - checkLastMediaChanged() - } - }).start() - }, LAST_MEDIA_CHECK_PERIOD) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_media, menu) - - val isFolderHidden = File(mPath).containsNoMedia() - menu.apply { - findItem(R.id.hide_folder).isVisible = !isFolderHidden && !mShowAll - findItem(R.id.unhide_folder).isVisible = isFolderHidden && !mShowAll - - findItem(R.id.folder_view).isVisible = mShowAll - findItem(R.id.open_camera).isVisible = mShowAll - findItem(R.id.about).isVisible = mShowAll - - findItem(R.id.temporarily_show_hidden).isVisible = !config.showHiddenMedia - - findItem(R.id.increase_column_count).isVisible = config.mediaColumnCnt < 10 - findItem(R.id.reduce_column_count).isVisible = config.mediaColumnCnt > 1 - } - - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.sort -> showSortingDialog() - R.id.toggle_filename -> toggleFilenameVisibility() - R.id.open_camera -> launchCamera() - R.id.folder_view -> switchToFolderView() - R.id.hide_folder -> tryHideFolder() - R.id.unhide_folder -> unhideFolder() - R.id.exclude_folder -> tryExcludeFolder() - R.id.temporarily_show_hidden -> temporarilyShowHidden() - R.id.increase_column_count -> increaseColumnCount() - R.id.reduce_column_count -> reduceColumnCount() - R.id.settings -> launchSettings() - R.id.about -> launchAbout() - else -> return super.onOptionsItemSelected(item) - } - return true - } - - private fun toggleFilenameVisibility() { - config.displayFileNames = !config.displayFileNames - if (media_grid.adapter != null) - getRecyclerAdapter().updateDisplayFilenames(config.displayFileNames) - } - - private fun showSortingDialog() { - ChangeSortingDialog(this, false, !config.showAll, mPath) { - getMedia() - } - } - - private fun switchToFolderView() { - config.showAll = false - startActivity(Intent(this, MainActivity::class.java)) - finish() - } - - private fun tryHideFolder() { - if (config.wasHideFolderTooltipShown) { - hideFolder() - } else { - ConfirmationDialog(this, getString(R.string.hide_folder_description)) { - config.wasHideFolderTooltipShown = true - hideFolder() - } - } - } - - private fun hideFolder() { - addNoMedia(mPath) { - runOnUiThread { - if (!config.shouldShowHidden) - finish() - else - invalidateOptionsMenu() - } - } - } - - private fun unhideFolder() { - removeNoMedia(mPath) { - runOnUiThread { - invalidateOptionsMenu() - } - } - } - - private fun tryExcludeFolder() { - ExcludeFolderDialog(this, arrayListOf(mPath)) { - finish() - } - } - - private fun deleteDirectoryIfEmpty() { - val file = File(mPath) - if (!file.isDownloadsFolder() && file.isDirectory && file.listFiles()?.isEmpty() == true) { - file.delete() - } - } - - private fun getMedia() { - if (mIsGettingMedia) - return - - mIsGettingMedia = true - val token = object : TypeToken>() {}.type - val media = Gson().fromJson>(config.loadFolderMedia(mPath), token) ?: ArrayList(1) - if (media.isNotEmpty() && !mLoadedInitialPhotos) { - gotMedia(media) - } else { - media_refresh_layout.isRefreshing = true - } - - mLoadedInitialPhotos = true - GetMediaAsynctask(applicationContext, mPath, mIsGetVideoIntent, mIsGetImageIntent, mShowAll) { - gotMedia(it) - }.execute() - } - - private fun isDirEmpty(): Boolean { - return if (mMedia.size <= 0) { - deleteDirectoryIfEmpty() - finish() - true - } else - false - } - - private fun temporarilyShowHidden() { - config.temporarilyShowHidden = true - getMedia() - } - - private fun getRecyclerAdapter() = (media_grid.adapter as MediaAdapter) - - private fun handleZooming() { - val layoutManager = media_grid.layoutManager as GridLayoutManager - layoutManager.spanCount = config.mediaColumnCnt - MyScalableRecyclerView.mListener = object : MyScalableRecyclerView.MyScalableRecyclerViewListener { - override fun zoomIn() { - if (layoutManager.spanCount > 1) { - reduceColumnCount() - getRecyclerAdapter().actMode?.finish() - } - } - - override fun zoomOut() { - if (layoutManager.spanCount < 10) { - increaseColumnCount() - getRecyclerAdapter().actMode?.finish() - } - } - - override fun selectItem(position: Int) { - getRecyclerAdapter().selectItem(position) - } - - override fun selectRange(initialSelection: Int, lastDraggedIndex: Int, minReached: Int, maxReached: Int) { - getRecyclerAdapter().selectRange(initialSelection, lastDraggedIndex, minReached, maxReached) - } - } - } - - private fun increaseColumnCount() { - config.mediaColumnCnt = ++(media_grid.layoutManager as GridLayoutManager).spanCount - invalidateOptionsMenu() - } - - private fun reduceColumnCount() { - config.mediaColumnCnt = --(media_grid.layoutManager as GridLayoutManager).spanCount - invalidateOptionsMenu() - } - - private fun isSetWallpaperIntent() = intent.getBooleanExtra(SET_WALLPAPER_INTENT, false) - - override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { - if (requestCode == REQUEST_EDIT_IMAGE) { - if (resultCode == Activity.RESULT_OK && resultData != null) { - mMedia.clear() - refreshItems() - } - } - super.onActivityResult(requestCode, resultCode, resultData) - } - - private fun itemClicked(path: String) { - if (isSetWallpaperIntent()) { - toast(R.string.setting_wallpaper) - - val wantedWidth = wallpaperDesiredMinimumWidth - val wantedHeight = wallpaperDesiredMinimumHeight - val ratio = wantedWidth.toFloat() / wantedHeight - Glide.with(this) - .load(File(path)) - .asBitmap() - .override((wantedWidth * ratio).toInt(), wantedHeight) - .fitCenter() - .into(object : SimpleTarget() { - override fun onResourceReady(bitmap: Bitmap?, glideAnimation: GlideAnimation?) { - try { - WallpaperManager.getInstance(applicationContext).setBitmap(bitmap) - setResult(Activity.RESULT_OK) - } catch (e: IOException) { - Log.e(TAG, "item click $e") - } - - finish() - } - }) - } else if (mIsGetImageIntent || mIsGetVideoIntent || mIsGetAnyIntent) { - Intent().apply { - data = Uri.parse(path) - setResult(Activity.RESULT_OK, this) - } - finish() - } else { - val file = File(path) - val isVideo = file.isVideoFast() - if (isVideo) { - openWith(file, false) - } else { - Intent(this, ViewPagerActivity::class.java).apply { - putExtra(MEDIUM, path) - putExtra(SHOW_ALL, mShowAll) - startActivity(this) - } - } - } - } - - private fun gotMedia(media: ArrayList) { - mLastMediaModified = getLastMediaModified() - mIsGettingMedia = false - media_refresh_layout.isRefreshing = false - - checkLastMediaChanged() - if (mLastDrawnHashCode == 0) - mLastDrawnHashCode = media.hashCode() - - if (media.hashCode() == mMedia.hashCode() && media.hashCode() == mLastDrawnHashCode) - return - - mLastDrawnHashCode = media.hashCode() - mMedia = media - setupAdapter() - storeFolder() - } - - private fun storeFolder() { - if (!config.temporarilyShowHidden) { - val subList = mMedia.subList(0, Math.min(SAVE_MEDIA_CNT, mMedia.size)) - val json = Gson().toJson(subList) - config.saveFolderMedia(mPath, json) - } - } - - override fun deleteFiles(files: ArrayList) { - val filtered = files.filter { it.isImageVideoGif() } as ArrayList - deleteFiles(filtered) { - if (!it) { - toast(R.string.unknown_error_occurred) - } else if (mMedia.isEmpty()) { - finish() - } - } - } - - override fun refreshItems() { - getMedia() - Handler().postDelayed({ - getMedia() - }, 1000) - } - - override fun itemLongClicked(position: Int) { - media_grid.setDragSelectActive(position) - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoVideoActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoVideoActivity.kt deleted file mode 100644 index 56e39f852..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoVideoActivity.kt +++ /dev/null @@ -1,166 +0,0 @@ -package com.simplemobiletools.gallery.activities - -import android.Manifest -import android.content.Intent -import android.content.pm.PackageManager -import android.database.Cursor -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.net.Uri -import android.os.Bundle -import android.provider.MediaStore -import android.support.v4.app.ActivityCompat -import android.view.Menu -import android.view.MenuItem -import android.view.View -import com.simplemobiletools.commons.extensions.hasWriteStoragePermission -import com.simplemobiletools.commons.extensions.scanPath -import com.simplemobiletools.commons.extensions.toast -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.IS_VIEW_INTENT -import com.simplemobiletools.gallery.helpers.MEDIUM -import com.simplemobiletools.gallery.models.Medium -import kotlinx.android.synthetic.main.fragment_holder.* -import java.io.File - -open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentListener { - private val STORAGE_PERMISSION = 1 - private var mMedium: Medium? = null - private var mIsFullScreen = false - - lateinit var mUri: Uri - lateinit var mFragment: ViewPagerFragment - - companion object { - var mIsVideo = false - } - - public override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.fragment_holder) - - if (hasWriteStoragePermission()) { - checkIntent(savedInstanceState) - } else { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), STORAGE_PERMISSION) - } - } - - private fun checkIntent(savedInstanceState: Bundle? = null) { - mUri = intent.data ?: return - - if (mUri.scheme == "file") { - scanPath(mUri.path) {} - sendViewPagerIntent(mUri.path) - finish() - return - } else { - val path = applicationContext.getRealPathFromURI(mUri) ?: "" - scanPath(mUri.path) {} - if (path.isNotEmpty()) { - sendViewPagerIntent(path) - finish() - return - } - } - - showSystemUI() - val bundle = Bundle() - val file = File(mUri.toString()) - mMedium = Medium(file.name, mUri.toString(), mIsVideo, 0, 0, file.length()) - bundle.putSerializable(MEDIUM, mMedium) - - if (savedInstanceState == null) { - mFragment = if (mIsVideo) VideoFragment() else PhotoFragment() - mFragment.listener = this - mFragment.arguments = bundle - supportFragmentManager.beginTransaction().replace(R.id.fragment_holder, mFragment).commit() - } - - if (config.darkBackground) - fragment_holder.background = ColorDrawable(Color.BLACK) - - val proj = arrayOf(MediaStore.Images.Media.TITLE) - var cursor: Cursor? = null - try { - cursor = contentResolver.query(mUri, proj, null, null, null) - if (cursor != null && cursor.count != 0) { - val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE) - cursor.moveToFirst() - title = cursor.getString(columnIndex) - } - } catch (e: Exception) { - title = mMedium?.name ?: "" - } finally { - cursor?.close() - } - } - - override fun onResume() { - super.onResume() - supportActionBar?.setBackgroundDrawable(resources.getDrawable(R.drawable.actionbar_gradient_background)) - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - - if (requestCode == STORAGE_PERMISSION) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - checkIntent() - } else { - toast(R.string.no_storage_permissions) - finish() - } - } - } - - private fun sendViewPagerIntent(path: String) { - Intent(this, ViewPagerActivity::class.java).apply { - putExtra(IS_VIEW_INTENT, true) - putExtra(MEDIUM, path) - startActivity(this) - } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.photo_video_menu, menu) - - menu.findItem(R.id.menu_set_as_wallpaper).isVisible = mMedium?.isImage() == true - menu.findItem(R.id.menu_edit).isVisible = mMedium?.isImage() == true - - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.menu_set_as_wallpaper -> trySetAsWallpaper(File(mMedium!!.path)) - R.id.menu_open_with -> openWith(File(mMedium!!.path)) - R.id.menu_share -> shareUri(mMedium!!, mUri) - R.id.menu_edit -> openEditor(File(mMedium!!.path)) - else -> return super.onOptionsItemSelected(item) - } - return true - } - - override fun fragmentClicked() { - mIsFullScreen = !mIsFullScreen - if (mIsFullScreen) { - hideSystemUI() - } else { - showSystemUI() - } - } - - override fun systemUiVisibilityChanged(visibility: Int) { - if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { - mIsFullScreen = false - showSystemUI() - } else { - mIsFullScreen = true - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SettingsActivity.kt deleted file mode 100644 index dfcb59d95..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SettingsActivity.kt +++ /dev/null @@ -1,156 +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.RadioGroupDialog -import com.simplemobiletools.commons.extensions.updateTextColors -import com.simplemobiletools.commons.models.RadioItem -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.extensions.config -import com.simplemobiletools.gallery.helpers.* -import kotlinx.android.synthetic.main.activity_settings.* - -class SettingsActivity : SimpleActivity() { - lateinit var res: Resources - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_settings) - res = resources - } - - override fun onResume() { - super.onResume() - - setupCustomizeColors() - setupManageIncludedFolders() - setupManageExcludedFolders() - setupShowHiddenFolders() - setupAutoplayVideos() - setupLoopVideos() - setupAnimateGifs() - setupMaxBrightness() - setupCropThumbnails() - setupDarkBackground() - setupScreenRotation() - setupShowMedia() - updateTextColors(settings_holder) - } - - private fun setupCustomizeColors() { - settings_customize_colors_holder.setOnClickListener { - startCustomizationActivity() - } - } - - 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 setupShowHiddenFolders() { - settings_show_hidden_folders.isChecked = config.showHiddenMedia - settings_show_hidden_folders_holder.setOnClickListener { - settings_show_hidden_folders.toggle() - config.showHiddenMedia = settings_show_hidden_folders.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_dark_background.isChecked = config.darkBackground - settings_dark_background_holder.setOnClickListener { - settings_dark_background.toggle() - config.darkBackground = settings_dark_background.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 setupShowMedia() { - settings_show_media.text = getShowMediaText() - settings_show_media_holder.setOnClickListener { - val items = arrayListOf( - RadioItem(IMAGES_AND_VIDEOS, res.getString(R.string.images_and_videos)), - RadioItem(IMAGES, res.getString(R.string.images)), - RadioItem(VIDEOS, res.getString(R.string.videos))) - - RadioGroupDialog(this@SettingsActivity, items, config.showMedia) { - config.showMedia = it as Int - settings_show_media.text = getShowMediaText() - } - } - } - - private fun getShowMediaText() = getString(when (config.showMedia) { - IMAGES_AND_VIDEOS -> R.string.images_and_videos - IMAGES -> R.string.images - else -> R.string.videos - }) -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SimpleActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SimpleActivity.kt deleted file mode 100644 index 6cfd6eb38..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SimpleActivity.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.simplemobiletools.gallery.activities - -import com.simplemobiletools.commons.activities.BaseSimpleActivity -import com.simplemobiletools.commons.extensions.toast -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.dialogs.PickDirectoryDialog -import java.io.File -import java.util.* - -open class SimpleActivity : BaseSimpleActivity() { - fun tryCopyMoveFilesTo(files: ArrayList, isCopyOperation: Boolean, callback: () -> Unit) { - if (files.isEmpty()) { - toast(R.string.unknown_error_occurred) - return - } - - val source = if (files[0].isFile) files[0].parent else files[0].absolutePath - PickDirectoryDialog(this, source) { - copyMoveFilesTo(files, source.trimEnd('/'), it, isCopyOperation, true, callback) - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SplashActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SplashActivity.kt deleted file mode 100644 index 13e968dcf..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SplashActivity.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.simplemobiletools.gallery.activities - -import android.content.Intent -import android.os.Bundle -import android.support.v7.app.AppCompatActivity - -class SplashActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - startActivity(Intent(this, MainActivity::class.java)) - finish() - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt deleted file mode 100644 index fbd122166..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt +++ /dev/null @@ -1,543 +0,0 @@ -package com.simplemobiletools.gallery.activities - -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.hardware.SensorManager -import android.media.ExifInterface -import android.net.Uri -import android.os.Build -import android.os.Bundle -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.OrientationEventListener -import android.view.View -import com.simplemobiletools.commons.dialogs.ConfirmationDialog -import com.simplemobiletools.commons.dialogs.PropertiesDialog -import com.simplemobiletools.commons.dialogs.RenameItemDialog -import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.activities.MediaActivity.Companion.mMedia -import com.simplemobiletools.gallery.adapters.MyPagerAdapter -import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask -import com.simplemobiletools.gallery.dialogs.SaveAsDialog -import com.simplemobiletools.gallery.extensions.* -import com.simplemobiletools.gallery.fragments.PhotoFragment -import com.simplemobiletools.gallery.fragments.ViewPagerFragment -import com.simplemobiletools.gallery.helpers.* -import com.simplemobiletools.gallery.models.Medium -import kotlinx.android.synthetic.main.activity_medium.* -import java.io.File -import java.io.OutputStream -import java.util.* - -class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, ViewPagerFragment.FragmentListener { - lateinit var mOrientationEventListener: OrientationEventListener - private var mPath = "" - private var mDirectory = "" - - private var mIsFullScreen = false - private var mPos = -1 - private var mShowAll = false - private var mRotationDegrees = 0f - private var mLastHandledOrientation = 0 - private var mPrevHashcode = 0 - - companion object { - var screenWidth = 0 - var screenHeight = 0 - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_medium) - - if (!hasWriteStoragePermission()) { - finish() - return - } - - measureScreen() - val uri = intent.data - if (uri != null) { - var cursor: Cursor? = null - try { - val proj = arrayOf(MediaStore.Images.Media.DATA) - cursor = contentResolver.query(uri, proj, null, null, null) - if (cursor?.moveToFirst() == true) { - mPath = cursor.getStringValue(MediaStore.Images.Media.DATA) - } - } finally { - cursor?.close() - } - } else { - mPath = intent.getStringExtra(MEDIUM) - mShowAll = config.showAll - } - - if (mPath.isEmpty()) { - toast(R.string.unknown_error_occurred) - finish() - return - } - - if (intent.extras?.containsKey(IS_VIEW_INTENT) == true) { - config.temporarilyShowHidden = true - } - - showSystemUI() - - mDirectory = File(mPath).parent - title = mPath.getFilenameFromPath() - - if (mMedia.isNotEmpty()) { - gotMedia(mMedia) - } - - reloadViewPager() - scanPath(mPath) {} - setupOrientationEventListener() - - if (config.darkBackground) - view_pager.background = ColorDrawable(Color.BLACK) - } - - override fun onDestroy() { - super.onDestroy() - if (intent.extras?.containsKey(IS_VIEW_INTENT) == true) { - config.temporarilyShowHidden = false - } - } - - private fun setupOrientationEventListener() { - mOrientationEventListener = object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) { - override fun onOrientationChanged(orientation: Int) { - val currOrient = if (orientation in 45..134) { - ORIENT_LANDSCAPE_RIGHT - } else if (orientation in 225..314) { - ORIENT_LANDSCAPE_LEFT - } else { - ORIENT_PORTRAIT - } - - if (mLastHandledOrientation != currOrient) { - mLastHandledOrientation = currOrient - - if (currOrient == ORIENT_LANDSCAPE_LEFT) { - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - } else if (currOrient == ORIENT_LANDSCAPE_RIGHT) { - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE - } else { - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } - } - } - } - } - - override fun onResume() { - super.onResume() - if (!hasWriteStoragePermission()) { - finish() - } - supportActionBar?.setBackgroundDrawable(resources.getDrawable(R.drawable.actionbar_gradient_background)) - - if (config.maxBrightness) { - val attributes = window.attributes - attributes.screenBrightness = 1f - window.attributes = attributes - } - - if (config.screenRotation == ROTATE_BY_DEVICE_ROTATION && mOrientationEventListener.canDetectOrientation()) { - mOrientationEventListener.enable() - } else if (config.screenRotation == ROTATE_BY_SYSTEM_SETTING) { - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - } - } - - override fun onPause() { - super.onPause() - mOrientationEventListener.disable() - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_viewpager, menu) - if (getCurrentMedium() == null) - return true - - menu.apply { - findItem(R.id.menu_set_as_wallpaper).isVisible = getCurrentMedium()!!.isImage() == true - findItem(R.id.menu_edit).isVisible = getCurrentMedium()!!.isImage() == true - findItem(R.id.menu_rotate).isVisible = getCurrentMedium()!!.isImage() == true - findItem(R.id.menu_save_as).isVisible = mRotationDegrees != 0f - findItem(R.id.menu_hide).isVisible = !getCurrentMedium()!!.name.startsWith('.') - findItem(R.id.menu_unhide).isVisible = getCurrentMedium()!!.name.startsWith('.') - - findItem(R.id.menu_rotate).subMenu.apply { - clearHeader() - findItem(R.id.rotate_right).icon = resources.getColoredDrawable(R.drawable.ic_rotate_right, R.color.actionbar_menu_icon) - findItem(R.id.rotate_left).icon = resources.getColoredDrawable(R.drawable.ic_rotate_left, R.color.actionbar_menu_icon) - findItem(R.id.rotate_one_eighty).icon = resources.getColoredDrawable(R.drawable.ic_rotate_one_eighty, R.color.actionbar_menu_icon) - } - } - - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (getCurrentMedium() == null) - return true - - when (item.itemId) { - R.id.menu_set_as_wallpaper -> trySetAsWallpaper(getCurrentFile()) - R.id.menu_copy_to -> copyMoveTo(true) - R.id.menu_move_to -> copyMoveTo(false) - R.id.menu_open_with -> openWith(getCurrentFile()) - R.id.menu_hide -> toggleFileVisibility(true) - R.id.menu_unhide -> toggleFileVisibility(false) - R.id.menu_share -> shareMedium(getCurrentMedium()!!) - R.id.menu_delete -> askConfirmDelete() - R.id.menu_rename -> renameFile() - R.id.menu_edit -> openEditor(getCurrentFile()) - R.id.menu_properties -> showProperties() - R.id.menu_save_as -> saveImageAs() - R.id.show_on_map -> showOnMap() - R.id.rotate_right -> rotateImage(90f) - R.id.rotate_left -> rotateImage(-90f) - R.id.rotate_one_eighty -> rotateImage(180f) - R.id.settings -> launchSettings() - else -> return super.onOptionsItemSelected(item) - } - return true - } - - private fun updatePagerItems() { - val pagerAdapter = MyPagerAdapter(this, supportFragmentManager, mMedia) - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !isDestroyed) { - view_pager.apply { - adapter = pagerAdapter - currentItem = mPos - addOnPageChangeListener(this@ViewPagerActivity) - } - } - } - - private fun copyMoveTo(isCopyOperation: Boolean) { - val files = ArrayList(1).apply { add(getCurrentFile()) } - tryCopyMoveFilesTo(files, isCopyOperation) { - if (!isCopyOperation) { - reloadViewPager() - } - } - } - - private fun toggleFileVisibility(hide: Boolean) { - toggleFileVisibility(getCurrentFile(), hide) { - val newFileName = it.absolutePath.getFilenameFromPath() - title = newFileName - - getCurrentMedium()!!.apply { - name = newFileName - path = it.absolutePath - mMedia[mPos] = this - } - invalidateOptionsMenu() - } - } - - private fun saveImageAs() { - val currPath = getCurrentPath() - SaveAsDialog(this, currPath) { - Thread({ - toast(R.string.saving) - val selectedFile = File(it) - val tmpFile = File(selectedFile.parent, "tmp_${it.getFilenameFromPath()}") - try { - val bitmap = BitmapFactory.decodeFile(currPath) - getFileOutputStream(tmpFile) { - saveFile(tmpFile, bitmap, it) - if (needsStupidWritePermissions(selectedFile.absolutePath)) { - deleteFile(selectedFile) {} - } - - renameFile(tmpFile, selectedFile) { - deleteFile(tmpFile) {} - } - } - } catch (e: OutOfMemoryError) { - toast(R.string.out_of_memory_error) - deleteFile(tmpFile) {} - } catch (e: Exception) { - toast(R.string.unknown_error_occurred) - deleteFile(tmpFile) {} - } - }).start() - } - } - - private fun saveFile(file: File, bitmap: Bitmap, out: OutputStream) { - val matrix = Matrix() - matrix.postRotate(mRotationDegrees) - val bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) - bmp.compress(file.getCompressionFormat(), 90, out) - out.flush() - toast(R.string.file_saved) - out.close() - } - - private fun rotateImage(degrees: Float) { - mRotationDegrees = (mRotationDegrees + degrees) % 360 - getCurrentFragment()?.rotateImageViewBy(mRotationDegrees) - supportInvalidateOptionsMenu() - } - - private fun getCurrentFragment(): PhotoFragment? { - val fragment = (view_pager.adapter as MyPagerAdapter).getCurrentFragment(view_pager.currentItem) - return if (fragment is PhotoFragment) - fragment - else - null - } - - private fun showProperties() { - if (getCurrentMedium() != null) - PropertiesDialog(this, getCurrentPath(), false) - } - - private fun showOnMap() { - val exif = ExifInterface(getCurrentPath()) - val lat = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) - val lat_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF) - val lon = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE) - val lon_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF) - - if (lat == null || lat_ref == null || lon == null || lon_ref == null) { - toast(R.string.unknown_location) - } else { - val geoLat = if (lat_ref == "N") { - convertToDegree(lat) - } else { - 0 - convertToDegree(lat) - } - - val geoLon = if (lon_ref == "E") { - convertToDegree(lon) - } else { - 0 - convertToDegree(lon) - } - - val uriBegin = "geo:$geoLat,$geoLon" - val query = "$geoLat, $geoLon" - val encodedQuery = Uri.encode(query) - val uriString = "$uriBegin?q=$encodedQuery&z=16" - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uriString)) - val packageManager = packageManager - if (intent.resolveActivity(packageManager) != null) { - startActivity(intent) - } else { - toast(R.string.no_map_application) - } - } - } - - private fun convertToDegree(stringDMS: String): Float { - val dms = stringDMS.split(",".toRegex(), 3).toTypedArray() - - val stringD = dms[0].split("/".toRegex(), 2).toTypedArray() - val d0 = stringD[0].toDouble() - val d1 = stringD[1].toDouble() - val floatD = d0 / d1 - - val stringM = dms[1].split("/".toRegex(), 2).toTypedArray() - val m0 = stringM[0].toDouble() - val m1 = stringM[1].toDouble() - val floatM = m0 / m1 - - val stringS = dms[2].split("/".toRegex(), 2).toTypedArray() - val s0 = stringS[0].toDouble() - val s1 = stringS[1].toDouble() - val floatS = s0 / s1 - - return (floatD + floatM / 60 + floatS / 3600).toFloat() - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { - if (requestCode == REQUEST_EDIT_IMAGE) { - if (resultCode == Activity.RESULT_OK && resultData != null) { - mPos = -1 - reloadViewPager() - } - } else if (requestCode == REQUEST_SET_WALLPAPER) { - if (resultCode == Activity.RESULT_OK) { - toast(R.string.wallpaper_set_successfully) - } - } - super.onActivityResult(requestCode, resultCode, resultData) - } - - private fun askConfirmDelete() { - ConfirmationDialog(this) { - deleteFileBg(File(mMedia[mPos].path)) { - reloadViewPager() - } - } - } - - private fun isDirEmpty(media: ArrayList): Boolean { - return if (media.isEmpty()) { - deleteDirectoryIfEmpty() - finish() - true - } else - false - } - - private fun renameFile() { - RenameItemDialog(this, getCurrentPath()) { - mMedia[mPos].path = it - updateActionbarTitle() - } - } - - override fun onConfigurationChanged(newConfig: Configuration?) { - super.onConfigurationChanged(newConfig) - measureScreen() - } - - private fun measureScreen() { - val metrics = DisplayMetrics() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - windowManager.defaultDisplay.getRealMetrics(metrics) - screenWidth = metrics.widthPixels - screenHeight = metrics.heightPixels - } else { - windowManager.defaultDisplay.getMetrics(metrics) - screenWidth = metrics.widthPixels - screenHeight = metrics.heightPixels - } - } - - private fun reloadViewPager() { - GetMediaAsynctask(applicationContext, mDirectory, false, false, mShowAll) { - gotMedia(it) - }.execute() - } - - private fun gotMedia(media: ArrayList) { - if (isDirEmpty(media) || media.hashCode() == mPrevHashcode) { - return - } - - mPrevHashcode = media.hashCode() - mMedia = media - if (mPos == -1) { - mPos = getProperPosition() - } else { - mPos = Math.min(mPos, mMedia.size - 1) - } - - updateActionbarTitle() - updatePagerItems() - invalidateOptionsMenu() - checkOrientation() - } - - private fun getProperPosition(): Int { - mPos = 0 - var i = 0 - for (medium in mMedia) { - if (medium.path == mPath) { - return i - } - i++ - } - return mPos - } - - private fun deleteDirectoryIfEmpty() { - val file = File(mDirectory) - if (!file.isDownloadsFolder() && file.isDirectory && file.listFiles()?.isEmpty() == true) { - file.delete() - } - - scanPath(mDirectory) {} - } - - private fun checkOrientation() { - if (config.screenRotation == ROTATE_BY_ASPECT_RATIO) { - val res = getCurrentFile().getResolution() - if (res.x > res.y) { - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - } else if (res.x < res.y) { - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } - } - } - - override fun fragmentClicked() { - mIsFullScreen = !mIsFullScreen - if (mIsFullScreen) { - hideSystemUI() - } else { - showSystemUI() - } - } - - override fun systemUiVisibilityChanged(visibility: Int) { - if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { - mIsFullScreen = false - showSystemUI() - } else { - mIsFullScreen = true - } - } - - private fun updateActionbarTitle() { - runOnUiThread { - if (mPos < mMedia.size) { - title = mMedia[mPos].path.getFilenameFromPath() - } - } - } - - private fun getCurrentMedium(): Medium? { - return if (mMedia.isEmpty() || mPos == -1) - null - else - mMedia[Math.min(mPos, mMedia.size - 1)] - } - - 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 (view_pager.offscreenPageLimit == 1) { - view_pager.offscreenPageLimit = 2 - } - mPos = position - updateActionbarTitle() - mRotationDegrees = 0f - supportInvalidateOptionsMenu() - } - - override fun onPageScrollStateChanged(state: Int) { - if (state == ViewPager.SCROLL_STATE_IDLE) { - checkOrientation() - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/DirectoryAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/DirectoryAdapter.kt deleted file mode 100644 index 7b76fb7a9..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/DirectoryAdapter.kt +++ /dev/null @@ -1,479 +0,0 @@ -package com.simplemobiletools.gallery.adapters - -import android.os.Build -import android.support.v7.view.ActionMode -import android.support.v7.widget.RecyclerView -import android.util.SparseArray -import android.view.* -import android.widget.FrameLayout -import com.bignerdranch.android.multiselector.ModalMultiSelectorCallback -import com.bignerdranch.android.multiselector.MultiSelector -import com.bignerdranch.android.multiselector.SwappingHolder -import com.bumptech.glide.Glide -import com.google.gson.Gson -import com.simplemobiletools.commons.dialogs.ConfirmationDialog -import com.simplemobiletools.commons.dialogs.PropertiesDialog -import com.simplemobiletools.commons.dialogs.RenameItemDialog -import com.simplemobiletools.commons.extensions.isAStorageRootFolder -import com.simplemobiletools.commons.extensions.isImageVideoGif -import com.simplemobiletools.commons.extensions.needsStupidWritePermissions -import com.simplemobiletools.commons.extensions.toast -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.activities.SimpleActivity -import com.simplemobiletools.gallery.dialogs.ExcludeFolderDialog -import com.simplemobiletools.gallery.dialogs.PickMediumDialog -import com.simplemobiletools.gallery.extensions.* -import com.simplemobiletools.gallery.models.AlbumCover -import com.simplemobiletools.gallery.models.Directory -import kotlinx.android.synthetic.main.directory_item.view.* -import kotlinx.android.synthetic.main.directory_tmb.view.* -import java.io.File -import java.util.* - -class DirectoryAdapter(val activity: SimpleActivity, var dirs: MutableList, val listener: DirOperationsListener?, val itemClick: (Directory) -> Unit) : - RecyclerView.Adapter() { - - val multiSelector = MultiSelector() - val config = activity.config - - var actMode: ActionMode? = null - var itemViews = SparseArray() - val selectedPositions = HashSet() - var foregroundColor = 0 - var pinnedFolders = config.pinnedFolders - - fun toggleItemSelection(select: Boolean, pos: Int) { - if (itemViews[pos] != null) - getProperView(itemViews[pos]!!).isSelected = select - - if (select) - selectedPositions.add(pos) - else - selectedPositions.remove(pos) - - if (selectedPositions.isEmpty()) { - actMode?.finish() - return - } - - updateTitle(selectedPositions.size) - actMode?.invalidate() - } - - fun getProperView(itemView: View): View { - return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) - itemView.dir_frame - else - itemView.dir_thumbnail - } - - fun updateTitle(cnt: Int) { - actMode?.title = "$cnt / ${dirs.size}" - } - - fun updatePrimaryColor(color: Int) { - foregroundColor = color - (0..itemViews.size() - 1).mapNotNull { itemViews[it] } - .forEach { setupItemViewForeground(it) } - } - - private fun setupItemViewForeground(itemView: View) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) - (getProperView(itemView) as FrameLayout).foreground = foregroundColor.createSelector() - else - getProperView(itemView).foreground = foregroundColor.createSelector() - } - - val adapterListener = object : MyAdapterListener { - override fun toggleItemSelectionAdapter(select: Boolean, position: Int) { - toggleItemSelection(select, position) - } - - override fun setupItemForeground(itemView: View) { - setupItemViewForeground(itemView) - } - - override fun getSelectedPositions(): HashSet = selectedPositions - } - - init { - foregroundColor = config.primaryColor - } - - val multiSelectorMode = object : ModalMultiSelectorCallback(multiSelector) { - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - when (item.itemId) { - R.id.cab_properties -> showProperties() - R.id.cab_rename -> renameDir() - R.id.cab_pin -> pinFolders(true) - R.id.cab_unpin -> pinFolders(false) - R.id.cab_hide -> toggleFoldersVisibility(true) - 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_select_all -> selectAll() - R.id.cab_delete -> askConfirmDelete() - R.id.cab_select_photo -> changeAlbumCover(false) - R.id.cab_use_default -> changeAlbumCover(true) - else -> return false - } - return true - } - - override fun onCreateActionMode(actionMode: ActionMode?, menu: Menu?): Boolean { - super.onCreateActionMode(actionMode, menu) - actMode = actionMode - activity.menuInflater.inflate(R.menu.cab_directories, menu) - return true - } - - override fun onPrepareActionMode(actionMode: ActionMode?, menu: Menu): Boolean { - menu.findItem(R.id.cab_rename).isVisible = selectedPositions.size <= 1 - menu.findItem(R.id.cab_change_cover_image).isVisible = selectedPositions.size <= 1 - - checkHideBtnVisibility(menu) - checkPinBtnVisibility(menu) - - return true - } - - override fun onDestroyActionMode(actionMode: ActionMode?) { - super.onDestroyActionMode(actionMode) - selectedPositions.forEach { - if (itemViews[it] != null) - getProperView(itemViews[it]!!).isSelected = false - } - selectedPositions.clear() - actMode = null - } - - fun checkHideBtnVisibility(menu: Menu) { - var hiddenCnt = 0 - var unhiddenCnt = 0 - selectedPositions.map { dirs.getOrNull(it)?.path }.filterNotNull().forEach { - if (File(it).containsNoMedia()) - hiddenCnt++ - else - unhiddenCnt++ - } - - menu.findItem(R.id.cab_hide).isVisible = unhiddenCnt > 0 - menu.findItem(R.id.cab_unhide).isVisible = hiddenCnt > 0 - } - - fun checkPinBtnVisibility(menu: Menu) { - val pinnedFolders = config.pinnedFolders - var pinnedCnt = 0 - var unpinnedCnt = 0 - selectedPositions.map { dirs.getOrNull(it)?.path }.filterNotNull().forEach { - if (pinnedFolders.contains(it)) - pinnedCnt++ - else - unpinnedCnt++ - } - - menu.findItem(R.id.cab_pin).isVisible = unpinnedCnt > 0 - menu.findItem(R.id.cab_unpin).isVisible = pinnedCnt > 0 - } - } - - private fun showProperties() { - if (selectedPositions.size <= 1) { - PropertiesDialog(activity, dirs[selectedPositions.first()].path, config.shouldShowHidden) - } else { - val paths = ArrayList() - selectedPositions.forEach { paths.add(dirs[it].path) } - PropertiesDialog(activity, paths, config.shouldShowHidden) - } - } - - private fun renameDir() { - val path = dirs[selectedPositions.first()].path - val dir = File(path) - if (activity.isAStorageRootFolder(dir.absolutePath)) { - activity.toast(R.string.rename_folder_root) - return - } - - RenameItemDialog(activity, dir.absolutePath) { - activity.runOnUiThread { - listener?.refreshItems() - actMode?.finish() - } - } - } - - private fun toggleFoldersVisibility(hide: Boolean) { - getSelectedPaths().forEach { - if (hide) { - if (config.wasHideFolderTooltipShown) { - hideFolder(it) - } else { - config.wasHideFolderTooltipShown = true - ConfirmationDialog(activity, activity.getString(R.string.hide_folder_description)) { - hideFolder(it) - } - } - } else { - activity.removeNoMedia(it) { - noMediaHandled() - } - } - } - } - - private fun hideFolder(path: String) { - activity.addNoMedia(path) { - noMediaHandled() - } - } - - private fun tryExcludeFolder() { - ExcludeFolderDialog(activity, getSelectedPaths().toList()) { - listener?.refreshItems() - actMode?.finish() - } - } - - private fun noMediaHandled() { - activity.runOnUiThread { - listener?.refreshItems() - actMode?.finish() - } - } - - private fun pinFolders(pin: Boolean) { - if (pin) - config.addPinnedFolders(getSelectedPaths()) - else - config.removePinnedFolders(getSelectedPaths()) - - pinnedFolders = config.pinnedFolders - listener?.refreshItems() - notifyDataSetChanged() - actMode?.finish() - } - - private fun copyMoveTo(isCopyOperation: Boolean) { - if (selectedPositions.isEmpty()) - return - - val files = ArrayList() - selectedPositions.forEach { - val dir = File(dirs[it].path) - files.addAll(dir.listFiles().filter { it.isFile && it.isImageVideoGif() }) - } - - activity.tryCopyMoveFilesTo(files, isCopyOperation) { - listener?.refreshItems() - actMode?.finish() - } - } - - fun selectAll() { - val cnt = dirs.size - for (i in 0..cnt - 1) { - selectedPositions.add(i) - notifyItemChanged(i) - } - updateTitle(cnt) - actMode?.invalidate() - } - - private fun askConfirmDelete() { - ConfirmationDialog(activity) { - deleteFiles() - actMode?.finish() - } - } - - private fun deleteFiles() { - val folders = ArrayList(selectedPositions.size) - val removeFolders = ArrayList(selectedPositions.size) - - var needPermissionForPath = "" - selectedPositions.forEach { - val path = dirs[it].path - if (activity.needsStupidWritePermissions(path) && config.treeUri.isEmpty()) { - needPermissionForPath = path - } - } - - activity.handleSAFDialog(File(needPermissionForPath)) { - selectedPositions.sortedDescending().forEach { - val directory = dirs[it] - folders.add(File(directory.path)) - removeFolders.add(directory) - notifyItemRemoved(it) - itemViews.put(it, null) - } - - dirs.removeAll(removeFolders) - selectedPositions.clear() - listener?.tryDeleteFolders(folders) - - val newItems = SparseArray() - var curIndex = 0 - for (i in 0..itemViews.size() - 1) { - if (itemViews[i] != null) { - newItems.put(curIndex, itemViews[i]) - curIndex++ - } - } - - itemViews = newItems - } - } - - private fun changeAlbumCover(useDefault: Boolean) { - if (selectedPositions.size != 1) - return - - val path = dirs[selectedPositions.first()].path - var albumCovers = config.parseAlbumCovers() - - if (useDefault) { - albumCovers = albumCovers.filterNot { it.path == path } as ArrayList - storeCovers(albumCovers) - } else { - PickMediumDialog(activity, path) { - albumCovers = albumCovers.filterNot { it.path == path } as ArrayList - albumCovers.add(AlbumCover(path, it)) - storeCovers(albumCovers) - } - } - } - - private fun storeCovers(albumCovers: ArrayList) { - activity.config.albumCovers = Gson().toJson(albumCovers) - actMode?.finish() - listener?.refreshItems() - } - - private fun getSelectedPaths(): HashSet { - val paths = HashSet(selectedPositions.size) - selectedPositions.forEach { paths.add(dirs[it].path) } - return paths - } - - override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder { - val view = LayoutInflater.from(parent?.context).inflate(R.layout.directory_item, parent, false) - return ViewHolder(view, adapterListener, itemClick) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val dir = dirs[position] - itemViews.put(position, holder.bindView(activity, multiSelectorMode, multiSelector, dir, pinnedFolders.contains(dir.path), listener)) - toggleItemSelection(selectedPositions.contains(position), position) - holder.itemView.tag = holder - } - - override fun onViewRecycled(holder: ViewHolder?) { - super.onViewRecycled(holder) - holder?.stopLoad() - } - - override fun getItemCount() = dirs.size - - fun updateDirs(newDirs: ArrayList) { - dirs = newDirs - notifyDataSetChanged() - } - - fun selectItem(pos: Int) { - toggleItemSelection(true, pos) - } - - fun selectRange(from: Int, to: Int, min: Int, max: Int) { - if (from == to) { - (min..max).filter { it != from } - .forEach { toggleItemSelection(false, it) } - return - } - - if (to < from) { - for (i in to..from) - toggleItemSelection(true, i) - - if (min > -1 && min < to) { - (min..to - 1).filter { it != from } - .forEach { toggleItemSelection(false, it) } - } - if (max > -1) { - for (i in from + 1..max) - toggleItemSelection(false, i) - } - } else { - for (i in from..to) - toggleItemSelection(true, i) - - if (max > -1 && max > to) { - (to + 1..max).filter { it != from } - .forEach { toggleItemSelection(false, it) } - } - - if (min > -1) { - for (i in min..from - 1) - toggleItemSelection(false, i) - } - } - } - - class ViewHolder(val view: View, val adapter: MyAdapterListener, val itemClick: (Directory) -> (Unit)) : SwappingHolder(view, MultiSelector()) { - fun bindView(activity: SimpleActivity, multiSelectorCallback: ModalMultiSelectorCallback, multiSelector: MultiSelector, directory: Directory, - isPinned: Boolean, listener: DirOperationsListener?): View { - itemView.apply { - dir_name.text = directory.name - photo_cnt.text = directory.mediaCnt.toString() - dir_pin.visibility = if (isPinned) View.VISIBLE else View.GONE - activity.loadImage(directory.tmb, dir_thumbnail) - - setOnClickListener { viewClicked(multiSelector, directory) } - setOnLongClickListener { - if (listener != null) { - if (!multiSelector.isSelectable) { - activity.startSupportActionMode(multiSelectorCallback) - adapter.toggleItemSelectionAdapter(true, layoutPosition) - } - - listener.itemLongClicked(layoutPosition) - } - true - } - - adapter.setupItemForeground(this) - } - return itemView - } - - fun viewClicked(multiSelector: MultiSelector, directory: Directory) { - if (multiSelector.isSelectable) { - val isSelected = adapter.getSelectedPositions().contains(layoutPosition) - adapter.toggleItemSelectionAdapter(!isSelected, layoutPosition) - } else { - itemClick(directory) - } - } - - fun stopLoad() { - Glide.clear(view.dir_thumbnail) - } - } - - interface MyAdapterListener { - fun toggleItemSelectionAdapter(select: Boolean, position: Int) - - fun setupItemForeground(itemView: View) - - fun getSelectedPositions(): HashSet - } - - interface DirOperationsListener { - fun refreshItems() - - fun tryDeleteFolders(folders: ArrayList) - - fun itemLongClicked(position: Int) - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MediaAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MediaAdapter.kt deleted file mode 100644 index b0d7be70d..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MediaAdapter.kt +++ /dev/null @@ -1,394 +0,0 @@ -package com.simplemobiletools.gallery.adapters - -import android.os.Build -import android.support.v7.view.ActionMode -import android.support.v7.widget.RecyclerView -import android.util.SparseArray -import android.view.* -import android.widget.FrameLayout -import com.bignerdranch.android.multiselector.ModalMultiSelectorCallback -import com.bignerdranch.android.multiselector.MultiSelector -import com.bignerdranch.android.multiselector.SwappingHolder -import com.bumptech.glide.Glide -import com.simplemobiletools.commons.dialogs.ConfirmationDialog -import com.simplemobiletools.commons.dialogs.PropertiesDialog -import com.simplemobiletools.commons.dialogs.RenameItemDialog -import com.simplemobiletools.commons.extensions.beVisibleIf -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.activities.SimpleActivity -import com.simplemobiletools.gallery.extensions.* -import com.simplemobiletools.gallery.models.Medium -import kotlinx.android.synthetic.main.photo_video_item.view.* -import kotlinx.android.synthetic.main.photo_video_tmb.view.* -import java.io.File -import java.util.* - -class MediaAdapter(val activity: SimpleActivity, var media: MutableList, val listener: MediaOperationsListener?, val itemClick: (Medium) -> Unit) : - RecyclerView.Adapter() { - - val multiSelector = MultiSelector() - val config = activity.config - - var actMode: ActionMode? = null - var itemViews = SparseArray() - val selectedPositions = HashSet() - var foregroundColor = 0 - var displayFilenames = config.displayFileNames - - fun toggleItemSelection(select: Boolean, pos: Int) { - if (itemViews[pos] != null) - getProperView(itemViews[pos]!!).isSelected = select - - if (select) - selectedPositions.add(pos) - else - selectedPositions.remove(pos) - - if (selectedPositions.isEmpty()) { - actMode?.finish() - return - } - - updateTitle(selectedPositions.size) - actMode?.invalidate() - } - - fun getProperView(itemView: View): View { - return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) - itemView.medium_thumbnail_holder - else - itemView.medium_thumbnail - } - - fun updateTitle(cnt: Int) { - actMode?.title = "$cnt / ${media.size}" - } - - fun updatePrimaryColor(color: Int) { - foregroundColor = color - (0..itemViews.size() - 1).mapNotNull { itemViews[it] } - .forEach { setupItemViewForeground(it) } - } - - private fun setupItemViewForeground(itemView: View) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) - (getProperView(itemView) as FrameLayout).foreground = foregroundColor.createSelector() - else - getProperView(itemView).foreground = foregroundColor.createSelector() - } - - val adapterListener = object : MyAdapterListener { - override fun toggleItemSelectionAdapter(select: Boolean, position: Int) { - toggleItemSelection(select, position) - } - - override fun setupItemForeground(itemView: View) { - setupItemViewForeground(itemView) - } - - override fun getSelectedPositions(): HashSet = selectedPositions - } - - init { - foregroundColor = config.primaryColor - } - - val multiSelectorMode = object : ModalMultiSelectorCallback(multiSelector) { - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - when (item.itemId) { - R.id.cab_properties -> showProperties() - R.id.cab_rename -> renameFile() - R.id.cab_edit -> editFile() - R.id.cab_hide -> toggleFileVisibility(true) - R.id.cab_unhide -> toggleFileVisibility(false) - R.id.cab_share -> shareMedia() - R.id.cab_copy_to -> copyMoveTo(true) - R.id.cab_move_to -> copyMoveTo(false) - R.id.cab_select_all -> selectAll() - R.id.cab_delete -> askConfirmDelete() - else -> return false - } - return true - } - - override fun onCreateActionMode(actionMode: ActionMode?, menu: Menu?): Boolean { - super.onCreateActionMode(actionMode, menu) - actMode = actionMode - activity.menuInflater.inflate(R.menu.cab_media, menu) - return true - } - - override fun onPrepareActionMode(actionMode: ActionMode?, menu: Menu): Boolean { - menu.findItem(R.id.cab_rename).isVisible = selectedPositions.size <= 1 - menu.findItem(R.id.cab_edit).isVisible = selectedPositions.size == 1 && media[selectedPositions.first()].isImage() - - checkHideBtnVisibility(menu) - - return true - } - - override fun onDestroyActionMode(actionMode: ActionMode?) { - super.onDestroyActionMode(actionMode) - selectedPositions.forEach { - if (itemViews[it] != null) - getProperView(itemViews[it]!!).isSelected = false - } - selectedPositions.clear() - actMode = null - } - - fun checkHideBtnVisibility(menu: Menu) { - var hiddenCnt = 0 - var unhiddenCnt = 0 - selectedPositions.map { media.getOrNull(it) }.filterNotNull().forEach { - if (it.name.startsWith('.')) - hiddenCnt++ - else - unhiddenCnt++ - } - - menu.findItem(R.id.cab_hide).isVisible = unhiddenCnt > 0 - menu.findItem(R.id.cab_unhide).isVisible = hiddenCnt > 0 - } - } - - private fun showProperties() { - if (selectedPositions.size <= 1) { - PropertiesDialog(activity, media[selectedPositions.first()].path, config.shouldShowHidden) - } else { - val paths = ArrayList() - selectedPositions.forEach { paths.add(media[it].path) } - PropertiesDialog(activity, paths, config.shouldShowHidden) - } - } - - private fun renameFile() { - RenameItemDialog(activity, getCurrentFile().absolutePath) { - activity.runOnUiThread { - listener?.refreshItems() - actMode?.finish() - } - } - } - - private fun editFile() { - activity.openEditor(getCurrentFile()) - actMode?.finish() - } - - private fun toggleFileVisibility(hide: Boolean) { - Thread({ - getSelectedMedia().forEach { - val oldFile = File(it.path) - activity.toggleFileVisibility(oldFile, hide) {} - } - activity.runOnUiThread { - listener?.refreshItems() - actMode?.finish() - } - }).start() - } - - private fun shareMedia() { - if (selectedPositions.size <= 1) { - activity.shareMedium(getSelectedMedia()[0]) - } else { - activity.shareMedia(getSelectedMedia()) - } - } - - private fun copyMoveTo(isCopyOperation: Boolean) { - val files = ArrayList() - selectedPositions.forEach { files.add(File(media[it].path)) } - - activity.tryCopyMoveFilesTo(files, isCopyOperation) { - if (!isCopyOperation) { - listener?.refreshItems() - } - actMode?.finish() - } - } - - fun selectAll() { - val cnt = media.size - for (i in 0..cnt - 1) { - selectedPositions.add(i) - multiSelector.setSelected(i, 0, true) - notifyItemChanged(i) - } - updateTitle(cnt) - actMode?.invalidate() - } - - private fun askConfirmDelete() { - ConfirmationDialog(activity) { - deleteFiles() - actMode?.finish() - } - } - - private fun getCurrentFile() = File(media[selectedPositions.first()].path) - - private fun deleteFiles() { - if (selectedPositions.isEmpty()) - return - - val files = ArrayList(selectedPositions.size) - val removeMedia = ArrayList(selectedPositions.size) - - activity.handleSAFDialog(File(media[selectedPositions.first()].path)) { - selectedPositions.sortedDescending().forEach { - val medium = media[it] - files.add(File(medium.path)) - removeMedia.add(medium) - notifyItemRemoved(it) - itemViews.put(it, null) - } - - media.removeAll(removeMedia) - selectedPositions.clear() - listener?.deleteFiles(files) - - val newItems = SparseArray() - var curIndex = 0 - for (i in 0..itemViews.size() - 1) { - if (itemViews[i] != null) { - newItems.put(curIndex, itemViews[i]) - curIndex++ - } - } - - itemViews = newItems - } - } - - private fun getSelectedMedia(): List { - val selectedMedia = ArrayList(selectedPositions.size) - selectedPositions.forEach { selectedMedia.add(media[it]) } - return selectedMedia - } - - override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder { - val view = LayoutInflater.from(parent?.context).inflate(R.layout.photo_video_item, parent, false) - return ViewHolder(view, adapterListener, itemClick) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - itemViews.put(position, holder.bindView(activity, multiSelectorMode, multiSelector, media[position], listener, displayFilenames)) - toggleItemSelection(selectedPositions.contains(position), position) - holder.itemView.tag = holder - } - - override fun onViewRecycled(holder: ViewHolder?) { - super.onViewRecycled(holder) - holder?.stopLoad() - } - - override fun getItemCount() = media.size - - fun updateMedia(newMedia: ArrayList) { - media = newMedia - notifyDataSetChanged() - } - - fun updateDisplayFilenames(display: Boolean) { - displayFilenames = display - notifyDataSetChanged() - } - - fun selectItem(pos: Int) { - toggleItemSelection(true, pos) - } - - fun selectRange(from: Int, to: Int, min: Int, max: Int) { - if (from == to) { - (min..max).filter { it != from } - .forEach { toggleItemSelection(false, it) } - return - } - - if (to < from) { - for (i in to..from) - toggleItemSelection(true, i) - - if (min > -1 && min < to) { - (min..to - 1).filter { it != from } - .forEach { toggleItemSelection(false, it) } - } - if (max > -1) { - for (i in from + 1..max) - toggleItemSelection(false, i) - } - } else { - for (i in from..to) - toggleItemSelection(true, i) - - if (max > -1 && max > to) { - (to + 1..max).filter { it != from } - .forEach { toggleItemSelection(false, it) } - } - - if (min > -1) { - for (i in min..from - 1) - toggleItemSelection(false, i) - } - } - } - - class ViewHolder(val view: View, val adapter: MyAdapterListener, val itemClick: (Medium) -> (Unit)) : SwappingHolder(view, MultiSelector()) { - fun bindView(activity: SimpleActivity, multiSelectorCallback: ModalMultiSelectorCallback, multiSelector: MultiSelector, medium: Medium, - listener: MediaOperationsListener?, displayFilenames: Boolean): View { - itemView.apply { - play_outline.visibility = if (medium.video) View.VISIBLE else View.GONE - photo_name.beVisibleIf(displayFilenames) - photo_name.text = medium.name - activity.loadImage(medium.path, medium_thumbnail) - - setOnClickListener { viewClicked(multiSelector, medium) } - setOnLongClickListener { - if (listener != null) { - if (!multiSelector.isSelectable) { - activity.startSupportActionMode(multiSelectorCallback) - adapter.toggleItemSelectionAdapter(true, layoutPosition) - } - - listener.itemLongClicked(layoutPosition) - } - true - } - - adapter.setupItemForeground(this) - } - return itemView - } - - fun viewClicked(multiSelector: MultiSelector, medium: Medium) { - if (multiSelector.isSelectable) { - val isSelected = adapter.getSelectedPositions().contains(layoutPosition) - adapter.toggleItemSelectionAdapter(!isSelected, layoutPosition) - } else { - itemClick(medium) - } - } - - fun stopLoad() { - Glide.clear(view.medium_thumbnail) - } - } - - interface MyAdapterListener { - fun toggleItemSelectionAdapter(select: Boolean, position: Int) - - fun setupItemForeground(itemView: View) - - fun getSelectedPositions(): HashSet - } - - interface MediaOperationsListener { - fun refreshItems() - - fun deleteFiles(files: ArrayList) - - fun itemLongClicked(position: Int) - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MyPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MyPagerAdapter.kt deleted file mode 100644 index fbd05d975..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/adapters/MyPagerAdapter.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.simplemobiletools.gallery.adapters - -import android.os.Bundle -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.util.SparseArray -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 - -class MyPagerAdapter(val activity: ViewPagerActivity, fm: FragmentManager, val media: MutableList) : FragmentStatePagerAdapter(fm) { - private val mFragments = SparseArray() - override fun getCount() = media.size - - override fun getItem(position: Int): Fragment { - val medium = media[position] - val bundle = Bundle() - bundle.putSerializable(MEDIUM, medium) - val fragment: ViewPagerFragment - - if (medium.video) { - fragment = VideoFragment() - } else { - fragment = PhotoFragment() - } - - fragment.arguments = bundle - fragment.listener = activity - return fragment - } - - override fun getItemPosition(item: Any?) = PagerAdapter.POSITION_NONE - - override fun instantiateItem(container: ViewGroup?, position: Int): Any { - val fragment = super.instantiateItem(container, position) as ViewPagerFragment - mFragments.put(position, fragment) - return fragment - } - - override fun destroyItem(container: ViewGroup?, position: Int, `object`: Any?) { - mFragments.remove(position) - super.destroyItem(container, position, `object`) - } - - fun getCurrentFragment(position: Int) = mFragments.get(position) -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetDirectoriesAsynctask.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetDirectoriesAsynctask.kt deleted file mode 100644 index 539e78e1e..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetDirectoriesAsynctask.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.simplemobiletools.gallery.asynctasks - -import android.content.Context -import android.os.AsyncTask -import com.simplemobiletools.commons.extensions.getFilenameFromPath -import com.simplemobiletools.commons.extensions.hasWriteStoragePermission -import com.simplemobiletools.commons.extensions.internalStoragePath -import com.simplemobiletools.commons.extensions.sdCardPath -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.extensions.config -import com.simplemobiletools.gallery.extensions.containsNoMedia -import com.simplemobiletools.gallery.extensions.getFilesFrom -import com.simplemobiletools.gallery.models.Directory -import com.simplemobiletools.gallery.models.Medium -import java.io.File -import java.util.* - -class GetDirectoriesAsynctask(val context: Context, val isPickVideo: Boolean, val isPickImage: Boolean, - val callback: (dirs: ArrayList) -> Unit) : AsyncTask>() { - var config = context.config - var shouldStop = false - val showHidden = config.shouldShowHidden - - override fun doInBackground(vararg params: Void): ArrayList { - if (!context.hasWriteStoragePermission()) - return ArrayList() - - val media = context.getFilesFrom("", isPickImage, isPickVideo) - val excludedPaths = config.excludedFolders - val directories = groupDirectories(media) - val dirs = ArrayList(directories.values.filter { File(it.path).exists() }).filter { shouldFolderBeVisible(it.path, excludedPaths) } as ArrayList - Directory.sorting = config.directorySorting - dirs.sort() - return movePinnedToFront(dirs) - } - - private fun groupDirectories(media: ArrayList): Map { - val albumCovers = config.parseAlbumCovers() - val hidden = context.resources.getString(R.string.hidden) - val directories = LinkedHashMap() - for ((name, path, isVideo, dateModified, dateTaken, size) in media) { - if (shouldStop) - cancel(true) - - val parentDir = File(path).parent ?: continue - if (directories.containsKey(parentDir)) { - val directory = directories[parentDir]!! - val newImageCnt = directory.mediaCnt + 1 - directory.mediaCnt = newImageCnt - directory.addSize(size) - } else { - var dirName = parentDir.getFilenameFromPath() - if (parentDir == context.internalStoragePath) { - dirName = context.getString(R.string.internal) - } else if (parentDir == context.sdCardPath) { - dirName = context.getString(R.string.sd_card) - } - - if (File(parentDir).containsNoMedia()) { - dirName += " $hidden" - - if (!showHidden) - continue - } - - var thumbnail = path - albumCovers.forEach { - if (it.path == parentDir && File(it.tmb).exists()) { - thumbnail = it.tmb - } - } - - val directory = Directory(parentDir, thumbnail, dirName, 1, dateModified, dateTaken, size) - directories.put(parentDir, directory) - } - } - return directories - } - - private fun shouldFolderBeVisible(path: String, excludedPaths: MutableSet): Boolean { - val file = File(path) - return if (isThisOrParentExcluded(path, excludedPaths)) - false - else if (!config.shouldShowHidden && file.isDirectory && file.canonicalFile == file.absoluteFile) { - var containsNoMediaOrDot = file.containsNoMedia() || path.contains("/.") - if (!containsNoMediaOrDot) { - containsNoMediaOrDot = checkParentHasNoMedia(file.parentFile) - } - !containsNoMediaOrDot - } else { - true - } - } - - private fun checkParentHasNoMedia(file: File): Boolean { - var curFile = file - while (true) { - if (curFile.containsNoMedia()) { - return true - } - curFile = curFile.parentFile - if (curFile.absolutePath == "/") - break - } - return false - } - - private fun isThisOrParentExcluded(path: String, excludedPaths: MutableSet) = excludedPaths.any { path.startsWith(it) } - - private fun movePinnedToFront(dirs: ArrayList): ArrayList { - val foundFolders = ArrayList() - val pinnedFolders = config.pinnedFolders - - dirs.forEach { if (pinnedFolders.contains(it.path)) foundFolders.add(it) } - dirs.removeAll(foundFolders) - dirs.addAll(0, foundFolders) - return dirs - } - - override fun onPostExecute(dirs: ArrayList) { - super.onPostExecute(dirs) - callback.invoke(dirs) - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetMediaAsynctask.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetMediaAsynctask.kt deleted file mode 100644 index 89342dc00..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/asynctasks/GetMediaAsynctask.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.simplemobiletools.gallery.asynctasks - -import android.content.Context -import android.os.AsyncTask -import com.simplemobiletools.gallery.extensions.getFilesFrom -import com.simplemobiletools.gallery.models.Medium -import java.util.* - -class GetMediaAsynctask(val context: Context, val mPath: String, val isPickVideo: Boolean = false, val isPickImage: Boolean = false, - val showAll: Boolean, val callback: (media: ArrayList) -> Unit) : - AsyncTask>() { - - override fun doInBackground(vararg params: Void): ArrayList { - val path = if (showAll) "" else mPath - return context.getFilesFrom(path, isPickImage, isPickVideo) - } - - override fun onPostExecute(media: ArrayList) { - super.onPostExecute(media) - callback.invoke(media) - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ChangeSortingDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ChangeSortingDialog.kt deleted file mode 100644 index f6516ee9b..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ChangeSortingDialog.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.simplemobiletools.gallery.dialogs - -import android.content.DialogInterface -import android.support.v7.app.AlertDialog -import android.view.LayoutInflater -import android.view.View -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.activities.SimpleActivity -import com.simplemobiletools.gallery.extensions.config -import com.simplemobiletools.gallery.helpers.Config -import kotlinx.android.synthetic.main.dialog_change_sorting.view.* - -class ChangeSortingDialog(val activity: SimpleActivity, val isDirectorySorting: Boolean, showFolderCheckbox: Boolean, - val path: String = "", val callback: () -> Unit) : - DialogInterface.OnClickListener { - private var currSorting = 0 - private var config: Config = activity.config - private var view: View - - init { - view = LayoutInflater.from(activity).inflate(R.layout.dialog_change_sorting, null).apply { - use_for_this_folder_divider.beVisibleIf(showFolderCheckbox) - sorting_dialog_use_for_this_folder.beVisibleIf(showFolderCheckbox) - sorting_dialog_use_for_this_folder.isChecked = config.hasCustomSorting(path) - } - - AlertDialog.Builder(activity) - .setPositiveButton(R.string.ok, this) - .setNegativeButton(R.string.cancel, null) - .create().apply { - activity.setupDialogStuff(view, this, R.string.sort_by) - } - - currSorting = if (isDirectorySorting) config.directorySorting else config.getFileSorting(path) - setupSortRadio() - setupOrderRadio() - } - - private fun setupSortRadio() { - val sortingRadio = view.sorting_dialog_radio_sorting - var sortBtn = sortingRadio.sorting_dialog_radio_name - - if (currSorting and SORT_BY_SIZE != 0) { - sortBtn = sortingRadio.sorting_dialog_radio_size - } else if (currSorting and SORT_BY_DATE_MODIFIED != 0) { - sortBtn = sortingRadio.sorting_dialog_radio_last_modified - } else if (currSorting and SORT_BY_DATE_TAKEN != 0) - sortBtn = sortingRadio.sorting_dialog_radio_date_taken - sortBtn.isChecked = true - } - - private fun setupOrderRadio() { - val orderRadio = view.sorting_dialog_radio_order - var orderBtn = orderRadio.sorting_dialog_radio_ascending - - if (currSorting and SORT_DESCENDING != 0) { - orderBtn = orderRadio.sorting_dialog_radio_descending - } - orderBtn.isChecked = true - } - - override fun onClick(dialog: DialogInterface, which: Int) { - val sortingRadio = view.sorting_dialog_radio_sorting - var sorting = when (sortingRadio.checkedRadioButtonId) { - R.id.sorting_dialog_radio_name -> SORT_BY_NAME - R.id.sorting_dialog_radio_size -> SORT_BY_SIZE - R.id.sorting_dialog_radio_last_modified -> SORT_BY_DATE_MODIFIED - else -> SORT_BY_DATE_TAKEN - } - - if (view.sorting_dialog_radio_order.checkedRadioButtonId == R.id.sorting_dialog_radio_descending) { - sorting = sorting or SORT_DESCENDING - } - - if (isDirectorySorting) { - config.directorySorting = sorting - } else { - if (view.sorting_dialog_use_for_this_folder.isChecked) { - config.saveFileSorting(path, sorting) - } else { - config.removeFileSorting(path) - config.fileSorting = sorting - } - } - callback.invoke() - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickDirectoryDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickDirectoryDialog.kt deleted file mode 100644 index cf384829e..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickDirectoryDialog.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.simplemobiletools.gallery.dialogs - -import android.support.v7.app.AlertDialog -import android.support.v7.widget.RecyclerView -import android.view.LayoutInflater -import com.simplemobiletools.commons.dialogs.FilePickerDialog -import com.simplemobiletools.commons.extensions.setupDialogStuff -import com.simplemobiletools.commons.extensions.toast -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.activities.SimpleActivity -import com.simplemobiletools.gallery.adapters.DirectoryAdapter -import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask -import com.simplemobiletools.gallery.extensions.config -import com.simplemobiletools.gallery.extensions.getCachedDirectories -import com.simplemobiletools.gallery.models.Directory -import kotlinx.android.synthetic.main.dialog_directory_picker.view.* - -class PickDirectoryDialog(val activity: SimpleActivity, val sourcePath: String, val callback: (path: String) -> Unit) { - var dialog: AlertDialog - var directoriesGrid: RecyclerView - var shownDirectories: ArrayList = ArrayList() - - init { - val view = LayoutInflater.from(activity).inflate(R.layout.dialog_directory_picker, null) - directoriesGrid = view.directories_grid - - 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) - - val dirs = activity.getCachedDirectories() - if (dirs.isNotEmpty()) { - gotDirectories(dirs) - } - - GetDirectoriesAsynctask(activity, false, false) { - gotDirectories(it) - }.execute() - } - } - - fun showOtherFolder() { - val showHidden = activity.config.shouldShowHidden - FilePickerDialog(activity, sourcePath, false, showHidden, true) { - callback.invoke(it) - } - } - - private fun gotDirectories(directories: ArrayList) { - if (directories.hashCode() == shownDirectories.hashCode()) - return - - shownDirectories = directories - val adapter = DirectoryAdapter(activity, directories, null) { - if (it.path.trimEnd('/') == sourcePath) { - activity.toast(R.string.source_and_destination_same) - return@DirectoryAdapter - } else { - callback.invoke(it.path) - dialog.dismiss() - } - } - directoriesGrid.adapter = adapter - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickMediumDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickMediumDialog.kt deleted file mode 100644 index 8d2bacf2b..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/PickMediumDialog.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.simplemobiletools.gallery.dialogs - -import android.support.v7.app.AlertDialog -import android.support.v7.widget.RecyclerView -import android.view.LayoutInflater -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import com.simplemobiletools.commons.extensions.setupDialogStuff -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.activities.SimpleActivity -import com.simplemobiletools.gallery.adapters.MediaAdapter -import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask -import com.simplemobiletools.gallery.extensions.config -import com.simplemobiletools.gallery.models.Medium -import kotlinx.android.synthetic.main.dialog_medium_picker.view.* - -class PickMediumDialog(val activity: SimpleActivity, val path: String, val callback: (path: String) -> Unit) { - var dialog: AlertDialog - var mediaGrid: RecyclerView - var shownMedia: ArrayList = ArrayList() - - init { - val view = LayoutInflater.from(activity).inflate(R.layout.dialog_medium_picker, null) - mediaGrid = view.media_grid - - dialog = AlertDialog.Builder(activity) - .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.cancel, null) - .create().apply { - activity.setupDialogStuff(view, this, R.string.select_photo) - - val token = object : TypeToken>() {}.type - val media = Gson().fromJson>(activity.config.loadFolderMedia(path), token) ?: ArrayList(1) - - if (media.isNotEmpty()) { - gotMedia(media) - } - - GetMediaAsynctask(activity, path, false, true, false) { - gotMedia(it) - }.execute() - } - } - - private fun gotMedia(media: ArrayList) { - if (media.hashCode() == shownMedia.hashCode()) - return - - shownMedia = media - val adapter = MediaAdapter(activity, media, null) { - callback(it.path) - dialog.dismiss() - } - mediaGrid.adapter = adapter - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ResizeDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ResizeDialog.kt deleted file mode 100644 index 0d339dcc8..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ResizeDialog.kt +++ /dev/null @@ -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.view.LayoutInflater -import android.view.WindowManager -import android.widget.EditText -import com.simplemobiletools.commons.extensions.setupDialogStuff -import com.simplemobiletools.commons.extensions.toast -import com.simplemobiletools.commons.extensions.value -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.activities.SimpleActivity -import kotlinx.android.synthetic.main.resize_image.view.* - -class ResizeDialog(val activity: SimpleActivity, val size: Point, val callback: (newSize: Point) -> Unit) { - init { - val view = LayoutInflater.from(activity).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 { - window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) - activity.setupDialogStuff(view, this, R.string.resize_and_save) - 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.invoke(newSize) - dismiss() - }) - } - } - - fun getViewValue(view: EditText): Int { - val textValue = view.value - return if (textValue.isEmpty()) 0 else textValue.toInt() - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/SaveAsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/SaveAsDialog.kt deleted file mode 100644 index 3a5bd3320..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/SaveAsDialog.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.simplemobiletools.gallery.dialogs - -import android.support.v7.app.AlertDialog -import android.view.LayoutInflater -import android.view.WindowManager -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.activities.SimpleActivity -import kotlinx.android.synthetic.main.dialog_save_as.view.* -import java.io.File - -class SaveAsDialog(val activity: SimpleActivity, val path: String, val callback: (savePath: String) -> Unit) { - - init { - var realPath = File(path).parent.trimEnd('/') - val view = LayoutInflater.from(activity).inflate(R.layout.dialog_save_as, null).apply { - save_as_path.text = activity.humanizePath(realPath) - - val fullName = path.getFilenameFromPath() - val dotAt = fullName.lastIndexOf(".") - var name = fullName - - if (dotAt > 0) { - name = fullName.substring(0, dotAt) - val extension = fullName.substring(dotAt + 1) - save_as_extension.setText(extension) - } - - save_as_name.setText(name) - save_as_path.setOnClickListener { - FilePickerDialog(activity, realPath, false, false, true) { - save_as_path.text = activity.humanizePath(it) - realPath = it - } - } - } - - AlertDialog.Builder(activity) - .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.cancel, null) - .create().apply { - window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) - activity.setupDialogStuff(view, this, R.string.save_as) - getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener({ - val filename = view.save_as_name.value - val extension = view.save_as_extension.value - - if (filename.isEmpty()) { - activity.toast(R.string.filename_cannot_be_empty) - return@setOnClickListener - } - - if (extension.isEmpty()) { - activity.toast(R.string.extension_cannot_be_empty) - return@setOnClickListener - } - - val newFile = File(realPath, "$filename.$extension") - if (!newFile.name.isAValidFilename()) { - activity.toast(R.string.filename_invalid_characters) - return@setOnClickListener - } - - if (newFile.exists()) { - val title = String.format(activity.getString(R.string.file_already_exists_overwrite), newFile.name) - ConfirmationDialog(activity, title) { - callback.invoke(newFile.absolutePath) - dismiss() - } - } else { - callback.invoke(newFile.absolutePath) - dismiss() - } - }) - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Int.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Int.kt deleted file mode 100644 index 064c348b6..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Int.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.simplemobiletools.gallery.extensions - -import android.content.res.ColorStateList -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.RippleDrawable -import android.graphics.drawable.StateListDrawable -import android.os.Build -import com.simplemobiletools.commons.extensions.adjustAlpha - -fun Int.createSelector(): StateListDrawable { - val statelist = StateListDrawable() - val selectedDrawable = ColorDrawable(adjustAlpha(0.5f)) - statelist.addState(intArrayOf(android.R.attr.state_selected), selectedDrawable) - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - val pressedDrawable = ColorDrawable(adjustAlpha(0.2f)) - statelist.addState(intArrayOf(android.R.attr.state_pressed), pressedDrawable) - } else { - val pressedDrawable = RippleDrawable(ColorStateList.valueOf(adjustAlpha(0.2f)), null, ColorDrawable(Color.WHITE)) - statelist.addState(intArrayOf(), pressedDrawable) - } - return statelist -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/activity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/activity.kt deleted file mode 100644 index b1663b850..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/activity.kt +++ /dev/null @@ -1,328 +0,0 @@ -package com.simplemobiletools.gallery.extensions - -import android.app.Activity -import android.content.Intent -import android.database.Cursor -import android.net.Uri -import android.os.Build -import android.provider.MediaStore -import android.support.v7.app.AppCompatActivity -import android.util.DisplayMetrics -import android.view.KeyCharacterMap -import android.view.KeyEvent -import android.view.View -import android.view.ViewConfiguration -import com.bumptech.glide.Glide -import com.bumptech.glide.load.DecodeFormat -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.signature.StringSignature -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.helpers.* -import com.simplemobiletools.gallery.BuildConfig -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.activities.SimpleActivity -import com.simplemobiletools.gallery.helpers.NOMEDIA -import com.simplemobiletools.gallery.helpers.REQUEST_EDIT_IMAGE -import com.simplemobiletools.gallery.helpers.REQUEST_SET_WALLPAPER -import com.simplemobiletools.gallery.models.Directory -import com.simplemobiletools.gallery.models.Medium -import com.simplemobiletools.gallery.views.MySquareImageView -import java.io.File -import java.util.* - -fun Activity.shareUri(medium: Medium, uri: Uri) { - val shareTitle = resources.getString(R.string.share_via) - Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, uri) - type = medium.getMimeType() - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - startActivity(Intent.createChooser(this, shareTitle)) - } -} - -fun Activity.shareMedium(medium: Medium) { - val shareTitle = resources.getString(R.string.share_via) - val file = File(medium.path) - val uri = Uri.fromFile(file) - Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, uri) - type = medium.getMimeType() - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - startActivity(Intent.createChooser(this, shareTitle)) - } -} - -fun Activity.shareMedia(media: List) { - val shareTitle = resources.getString(R.string.share_via) - val uris = ArrayList(media.size) - Intent().apply { - action = Intent.ACTION_SEND_MULTIPLE - type = "image/* video/*" - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - media.map { File(it.path) } - .mapTo(uris) { Uri.fromFile(it) } - - putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) - startActivity(Intent.createChooser(this, shareTitle)) - } -} - -fun Activity.trySetAsWallpaper(file: File) { - try { - var uri = Uri.fromFile(file) - if (!setAsWallpaper(uri, file)) { - uri = getFileContentUri(file) - setAsWallpaper(uri, file, false) - } - } catch (e: Exception) { - toast(R.string.unknown_error_occurred) - } -} - -fun Activity.setAsWallpaper(uri: Uri, file: File, showToast: Boolean = true): Boolean { - var success = false - Intent().apply { - action = Intent.ACTION_ATTACH_DATA - setDataAndType(uri, file.getMimeType("image/*")) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - val chooser = Intent.createChooser(this, getString(R.string.set_as_wallpaper_with)) - - if (resolveActivity(packageManager) != null) { - startActivityForResult(chooser, REQUEST_SET_WALLPAPER) - success = true - } else { - if (showToast) { - toast(R.string.no_wallpaper_setter_found) - } - success = false - } - } - - return success -} - -fun Activity.getFileContentUri(file: File): Uri? { - val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI - val projection = arrayOf(MediaStore.Images.Media._ID) - val selection = "${MediaStore.Images.Media.DATA} = ?" - val selectionArgs = arrayOf(file.absolutePath) - - var cursor: Cursor? = null - try { - cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) - if (cursor?.moveToFirst() == true) { - val id = cursor.getIntValue(MediaStore.Images.Media._ID) - return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "$id") - } - } finally { - cursor?.close() - } - return null -} - -fun Activity.openWith(file: File, forceChooser: Boolean = true) { - val uri = Uri.fromFile(file) - Intent().apply { - action = Intent.ACTION_VIEW - setDataAndType(uri, file.getMimeType()) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - val chooser = Intent.createChooser(this, getString(R.string.open_with)) - - if (resolveActivity(packageManager) != null) { - startActivity(if (forceChooser) chooser else this) - } else { - toast(R.string.no_app_found) - } - } -} - -fun Activity.openEditor(file: File) { - val uri = Uri.fromFile(file) - Intent().apply { - action = Intent.ACTION_EDIT - setDataAndType(uri, "image/*") - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - - if (resolveActivity(packageManager) != null) { - startActivityForResult(this, REQUEST_EDIT_IMAGE) - } else { - toast(R.string.no_editor_found) - } - } -} - -fun Activity.hasNavBar(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - val display = 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(applicationContext).hasPermanentMenuKey() - val hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK) - !hasMenuKey && !hasBackKey - } -} - -fun Activity.launchCamera() { - val intent = Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA) - if (intent.resolveActivity(packageManager) != null) { - startActivity(intent) - } else { - toast(R.string.no_camera_app_found) - } -} - -fun SimpleActivity.launchAbout() { - startAboutActivity(R.string.app_name, LICENSE_KOTLIN or LICENSE_GLIDE or LICENSE_CROPPER or LICENSE_MULTISELECT or LICENSE_RTL - or LICENSE_PHOTOVIEW or LICENSE_SUBSAMPLING, BuildConfig.VERSION_NAME) -} - -fun AppCompatActivity.showSystemUI() { - 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() { - 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 SimpleActivity.addNoMedia(path: String, callback: () -> Unit) { - val file = File(path, NOMEDIA) - if (file.exists()) - return - - if (needsStupidWritePermissions(path)) { - handleSAFDialog(file) { - try { - getFileDocument(path)?.createFile("", NOMEDIA) - } catch (e: Exception) { - toast(R.string.unknown_error_occurred) - } - } - } else { - try { - file.createNewFile() - } catch (e: Exception) { - toast(R.string.unknown_error_occurred) - } - } - scanFile(file) { - callback() - } -} - -fun SimpleActivity.removeNoMedia(path: String, callback: () -> Unit) { - val file = File(path, NOMEDIA) - deleteFile(file) { - callback() - } -} - -fun SimpleActivity.toggleFileVisibility(oldFile: File, hide: Boolean, callback: (newFile: File) -> Unit) { - val path = oldFile.parent - var filename = oldFile.name - if (hide) { - filename = ".${filename.trimStart('.')}" - } else { - filename = filename.substring(1, filename.length) - } - val newFile = File(path, filename) - renameFile(oldFile, newFile) { - newFile.setLastModified(System.currentTimeMillis()) - callback(newFile) - } -} - -fun Activity.getFileSignature(path: String) = StringSignature(File(path).lastModified().toString()) - -fun Activity.loadImage(path: String, target: MySquareImageView) { - if (path.isImageFast() || path.isVideoFast()) { - if (path.isPng()) { - loadPng(path, target) - } else { - loadJpg(path, target) - } - } else if (path.isGif()) { - if (config.animateGifs) { - loadAnimatedGif(path, target) - } else { - loadStaticGif(path, target) - } - } -} - -fun Activity.loadPng(path: String, target: MySquareImageView) { - val builder = Glide.with(applicationContext) - .load(path) - .asBitmap() - .signature(getFileSignature(path)) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .format(DecodeFormat.PREFER_ARGB_8888) - - if (config.cropThumbnails) builder.centerCrop() else builder.fitCenter() - builder.into(target) -} - -fun Activity.loadJpg(path: String, target: MySquareImageView) { - val builder = Glide.with(applicationContext) - .load(path) - .signature(getFileSignature(path)) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .crossFade() - - if (config.cropThumbnails) builder.centerCrop() else builder.fitCenter() - builder.into(target) -} - -fun Activity.loadAnimatedGif(path: String, target: MySquareImageView) { - val builder = Glide.with(applicationContext) - .load(path) - .asGif() - .signature(getFileSignature(path)) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) - .crossFade() - - if (config.cropThumbnails) builder.centerCrop() else builder.fitCenter() - builder.into(target) -} - -fun Activity.loadStaticGif(path: String, target: MySquareImageView) { - val builder = Glide.with(applicationContext) - .load(path) - .asBitmap() - .signature(getFileSignature(path)) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) - - if (config.cropThumbnails) builder.centerCrop() else builder.fitCenter() - builder.into(target) -} - -fun Activity.getCachedDirectories(): ArrayList { - val token = object : TypeToken>() {}.type - return Gson().fromJson>(config.directories, token) ?: ArrayList(1) -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/context.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/context.kt deleted file mode 100644 index 9df154732..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/context.kt +++ /dev/null @@ -1,236 +0,0 @@ -package com.simplemobiletools.gallery.extensions - -import android.content.Context -import android.content.Intent -import android.database.Cursor -import android.net.Uri -import android.provider.MediaStore -import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED -import com.simplemobiletools.commons.helpers.SORT_BY_NAME -import com.simplemobiletools.commons.helpers.SORT_BY_SIZE -import com.simplemobiletools.commons.helpers.SORT_DESCENDING -import com.simplemobiletools.gallery.activities.SettingsActivity -import com.simplemobiletools.gallery.helpers.Config -import com.simplemobiletools.gallery.helpers.IMAGES -import com.simplemobiletools.gallery.helpers.NOMEDIA -import com.simplemobiletools.gallery.helpers.VIDEOS -import com.simplemobiletools.gallery.models.Medium -import java.io.File -import java.util.* - -fun Context.getRealPathFromURI(uri: Uri): String? { - var cursor: Cursor? = null - try { - val projection = arrayOf(MediaStore.Images.Media.DATA) - cursor = contentResolver.query(uri, projection, null, null, null) - if (cursor?.moveToFirst() == true) { - val index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) - return cursor.getString(index) - } - } catch (e: Exception) { - } finally { - cursor?.close() - } - return null -} - -fun Context.getHumanizedFilename(path: String): String { - val humanized = humanizePath(path) - return humanized.substring(humanized.lastIndexOf("/") + 1) -} - -fun Context.launchSettings() { - startActivity(Intent(this, SettingsActivity::class.java)) -} - -val Context.config: Config get() = Config.newInstance(this) - -fun Context.getFilesFrom(curPath: String, isPickImage: Boolean, isPickVideo: Boolean): ArrayList { - val projection = arrayOf(MediaStore.Images.Media._ID, - MediaStore.Images.Media.DISPLAY_NAME, - MediaStore.Images.Media.DATE_TAKEN, - MediaStore.Images.Media.DATE_MODIFIED, - MediaStore.Images.Media.DATA, - MediaStore.Images.Media.SIZE) - val uri = MediaStore.Files.getContentUri("external") - val selection = if (curPath.isEmpty()) null else "(${MediaStore.Images.Media.DATA} LIKE ? AND ${MediaStore.Images.Media.DATA} NOT LIKE ?)" - val selectionArgs = if (curPath.isEmpty()) null else arrayOf("$curPath/%", "$curPath/%/%") - - try { - val cur = contentResolver.query(uri, projection, selection, selectionArgs, getSortingForFolder(curPath)) - return parseCursor(this, cur, isPickImage, isPickVideo, curPath) - } catch (e: Exception) { - return ArrayList() - } -} - -private fun parseCursor(context: Context, cur: Cursor, isPickImage: Boolean, isPickVideo: Boolean, curPath: String): ArrayList { - val curMedia = ArrayList() - val config = context.config - val showMedia = config.showMedia - val showHidden = config.shouldShowHidden - val excludedFolders = config.excludedFolders - val noMediaFolders = context.getNoMediaFolders() - - cur.use { cur -> - if (cur.moveToFirst()) { - do { - try { - val path = cur.getStringValue(MediaStore.Images.Media.DATA) - var size = cur.getLongValue(MediaStore.Images.Media.SIZE) - if (size == 0L) { - size = File(path).length() - } - - if (size <= 0L) { - continue - } - - var filename = cur.getStringValue(MediaStore.Images.Media.DISPLAY_NAME) ?: "" - if (filename.isEmpty()) - filename = path.getFilenameFromPath() - - val isImage = filename.isImageFast() || filename.isGif() - val isVideo = if (isImage) false else filename.isVideoFast() - - if (!isImage && !isVideo) - continue - - if (isVideo && (isPickImage || showMedia == IMAGES)) - continue - - if (isImage && (isPickVideo || showMedia == VIDEOS)) - continue - - if (!showHidden && filename.startsWith('.')) - continue - - var isExcluded = false - excludedFolders.forEach { - if (path.startsWith(it)) { - isExcluded = true - } - } - - if (!isExcluded && !showHidden) { - noMediaFolders.forEach { - if (path.startsWith(it)) { - isExcluded = true - } - } - } - - if (!isExcluded && !showHidden && path.contains("/.")) { - isExcluded = true - } - - if (!isExcluded) { - val dateTaken = cur.getLongValue(MediaStore.Images.Media.DATE_TAKEN) - val dateModified = cur.getIntValue(MediaStore.Images.Media.DATE_MODIFIED) * 1000L - - val medium = Medium(filename, path, isVideo, dateModified, dateTaken, size) - curMedia.add(medium) - } - } catch (e: Exception) { - continue - } - } while (cur.moveToNext()) - } - } - - config.includedFolders.filter { it.isEmpty() || it == curPath }.mapNotNull { File(it).listFiles() }.forEach { - for (file in it) { - val size = file.length() - if (size <= 0L) { - continue - } - - val filename = file.name - val isImage = filename.isImageFast() || filename.isGif() - val isVideo = if (isImage) false else filename.isVideoFast() - - if (!isImage && !isVideo) - continue - - if (isVideo && (isPickImage || showMedia == IMAGES)) - continue - - if (isImage && (isPickVideo || showMedia == VIDEOS)) - continue - - val dateTaken = file.lastModified() - val dateModified = file.lastModified() - - val medium = Medium(filename, file.absolutePath, isVideo, dateModified, dateTaken, size) - val isAlreadyAdded = curMedia.any { it.path == file.absolutePath } - if (!isAlreadyAdded) - curMedia.add(medium) - } - } - - Medium.sorting = config.getFileSorting(curPath) - curMedia.sort() - - return curMedia -} - -fun Context.getSortingForFolder(path: String): String { - val sorting = config.getFileSorting(path) - val sortValue = if (sorting and SORT_BY_NAME > 0) - MediaStore.Images.Media.DISPLAY_NAME - else if (sorting and SORT_BY_SIZE > 0) - MediaStore.Images.Media.SIZE - else if (sorting and SORT_BY_DATE_MODIFIED > 0) - MediaStore.Images.Media.DATE_MODIFIED - else - MediaStore.Images.Media.DATE_TAKEN - - return if (sorting and SORT_DESCENDING > 0) - "$sortValue DESC" - else - "$sortValue ASC" -} - -fun Context.getNoMediaFolders(): ArrayList { - val folders = ArrayList() - val noMediaCondition = "${MediaStore.Files.FileColumns.MEDIA_TYPE} = ${MediaStore.Files.FileColumns.MEDIA_TYPE_NONE}" - - val uri = MediaStore.Files.getContentUri("external") - val columns = arrayOf(MediaStore.Files.FileColumns.DATA) - val where = "$noMediaCondition AND ${MediaStore.Files.FileColumns.TITLE} LIKE ?" - val args = arrayOf("%$NOMEDIA%") - var cursor: Cursor? = null - - try { - cursor = contentResolver.query(uri, columns, where, args, null) - if (cursor?.moveToFirst() == true) { - do { - val path = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA)) ?: continue - val noMediaFile = File(path) - if (noMediaFile.exists()) - folders.add(noMediaFile.parent) - } while (cursor.moveToNext()) - } - } finally { - cursor?.close() - } - - return folders -} - -fun Context.getLastMediaModified(): Int { - val max = "max" - val uri = MediaStore.Files.getContentUri("external") - val projection = arrayOf(MediaStore.Images.Media._ID, "MAX(${MediaStore.Images.Media.DATE_MODIFIED}) AS $max") - var cursor: Cursor? = null - try { - cursor = contentResolver.query(uri, projection, null, null, null) - if (cursor?.moveToFirst() == true) { - return cursor.getIntValue(max) - } - } finally { - cursor?.close() - } - return 0 -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/file.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/file.kt deleted file mode 100644 index 668c3acb5..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/file.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.simplemobiletools.gallery.extensions - -import android.os.Environment -import com.simplemobiletools.gallery.helpers.NOMEDIA -import java.io.File - -fun File.containsNoMedia() = isDirectory && File(this, NOMEDIA).exists() - -fun File.isDownloadsFolder() = absolutePath == Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt deleted file mode 100644 index eaa7bfc91..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt +++ /dev/null @@ -1,268 +0,0 @@ -package com.simplemobiletools.gallery.fragments - -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.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.bumptech.glide.Glide -import com.bumptech.glide.Priority -import com.bumptech.glide.load.DecodeFormat -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target -import com.davemorrissey.labs.subscaleview.ImageSource -import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView -import com.simplemobiletools.commons.extensions.beGone -import com.simplemobiletools.commons.extensions.beVisible -import com.simplemobiletools.commons.extensions.toast -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.activities.ViewPagerActivity -import com.simplemobiletools.gallery.extensions.config -import com.simplemobiletools.gallery.extensions.getFileSignature -import com.simplemobiletools.gallery.extensions.getRealPathFromURI -import com.simplemobiletools.gallery.helpers.GlideRotateTransformation -import com.simplemobiletools.gallery.helpers.MEDIUM -import com.simplemobiletools.gallery.models.Medium -import it.sephiroth.android.library.exif2.ExifInterface -import kotlinx.android.synthetic.main.pager_photo_item.view.* -import uk.co.senab.photoview.PhotoViewAttacher -import java.io.File -import java.io.FileOutputStream -import java.io.IOException - -class PhotoFragment : ViewPagerFragment() { - lateinit var medium: Medium - lateinit var view: ViewGroup - private var isFragmentVisible = false - private var wasInit = false - private var RATIO_THRESHOLD = 0.1 - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - view = inflater.inflate(R.layout.pager_photo_item, container, false) as ViewGroup - - medium = arguments.getSerializable(MEDIUM) as Medium - - if (medium.path.startsWith("content://")) { - val originalPath = medium.path - medium.path = context.getRealPathFromURI(Uri.parse(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 { - try { - out?.close() - } catch (e: IOException) { - } - } - } - } - - view.subsampling_view.setOnClickListener({ photoClicked() }) - view.photo_view.apply { - maximumScale = 8f - mediumScale = 3f - setOnPhotoTapListener(object : PhotoViewAttacher.OnPhotoTapListener { - override fun onPhotoTap(view: View?, x: Float, y: Float) { - photoClicked() - } - - override fun onOutsidePhotoTap() { - photoClicked() - } - }) - } - loadImage() - - activity.window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> - listener?.systemUiVisibilityChanged(visibility) - } - wasInit = true - - return view - } - - override fun setMenuVisibility(menuVisible: Boolean) { - super.setMenuVisibility(menuVisible) - isFragmentVisible = menuVisible - if (wasInit) { - if (menuVisible) { - addZoomableView() - } else { - view.subsampling_view.apply { - recycle() - beGone() - background = ColorDrawable(Color.TRANSPARENT) - } - } - } - } - - private fun degreesForRotation(orientation: Int): Int { - return when (orientation) { - 8 -> 270 - 3 -> 180 - 6 -> 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() { - if (medium.isGif()) { - Glide.with(this) - .load(medium.path) - .asGif() - .crossFade() - .priority(if (isFragmentVisible) Priority.IMMEDIATE else Priority.LOW) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) - .into(view.photo_view) - } else { - loadBitmap() - } - } - - private fun loadBitmap(degrees: Float = 0f) { - if (degrees == 0f) { - val targetWidth = if (ViewPagerActivity.screenWidth == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenWidth - val targetHeight = if (ViewPagerActivity.screenHeight == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenHeight - - Glide.with(this) - .load(medium.path) - .asBitmap() - .signature(activity.getFileSignature(medium.path)) - .format(if (medium.isPng()) DecodeFormat.PREFER_ARGB_8888 else DecodeFormat.PREFER_RGB_565) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .override(targetWidth, targetHeight) - .listener(object : RequestListener { - override fun onException(e: Exception?, model: String?, target: Target?, isFirstResource: Boolean): Boolean { - return false - } - - override fun onResourceReady(resource: Bitmap, model: String?, target: Target?, isFromMemoryCache: Boolean, isFirstResource: Boolean): Boolean { - if (isFragmentVisible) - addZoomableView() - return false - } - }).into(view.photo_view) - } else { - Glide.with(this) - .load(medium.path) - .asBitmap() - .transform(GlideRotateTransformation(context, degrees)) - .thumbnail(0.2f) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .into(view.photo_view) - } - } - - private fun addZoomableView() { - if ((medium.isImage()) && isFragmentVisible && view.subsampling_view.visibility == View.GONE) { - view.subsampling_view.apply { - maxScale = 10f - beVisible() - setImage(ImageSource.uri(medium.path)) - orientation = SubsamplingScaleImageView.ORIENTATION_USE_EXIF - setOnImageEventListener(object : SubsamplingScaleImageView.OnImageEventListener { - override fun onImageLoaded() { - } - - override fun onReady() { - background = ColorDrawable(if (context.config.darkBackground) Color.BLACK else context.config.backgroundColor) - setDoubleTapZoomScale(getDoubleTapZoomScale()) - } - - override fun onTileLoadError(e: Exception?) { - } - - override fun onPreviewReleased() { - } - - override fun onImageLoadError(e: Exception?) { - background = ColorDrawable(Color.TRANSPARENT) - beGone() - } - - override fun onPreviewLoadError(e: Exception?) { - background = ColorDrawable(Color.TRANSPARENT) - beGone() - } - }) - } - } - } - - private fun getDoubleTapZoomScale(): Float { - val displayAspectRatio = ViewPagerActivity.screenHeight / (ViewPagerActivity.screenWidth).toFloat() - - val bitmapOptions = BitmapFactory.Options() - bitmapOptions.inJustDecodeBounds = true - BitmapFactory.decodeFile(medium.path, bitmapOptions) - val width = bitmapOptions.outWidth - val height = bitmapOptions.outHeight - val bitmapAspectRatio = height / (width).toFloat() - - return if (Math.abs(displayAspectRatio - bitmapAspectRatio) < RATIO_THRESHOLD) { - 2f - } else if (bitmapAspectRatio > 1f) { - width / (height).toFloat() - } else { - bitmapAspectRatio - } - } - - override fun onDestroyView() { - super.onDestroyView() - Glide.clear(view.photo_view) - } - - fun rotateImageViewBy(degrees: Float) { - view.subsampling_view.beGone() - loadBitmap(degrees) - } - - override fun onConfigurationChanged(newConfig: Configuration?) { - super.onConfigurationChanged(newConfig) - loadImage() - } - - private fun photoClicked() { - listener?.fragmentClicked() - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt deleted file mode 100644 index 5359bcc67..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt +++ /dev/null @@ -1,364 +0,0 @@ -package com.simplemobiletools.gallery.fragments - -import android.content.res.Configuration -import android.media.AudioManager -import android.media.MediaPlayer -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.os.Handler -import android.util.DisplayMetrics -import android.util.Log -import android.view.* -import android.view.animation.AnimationUtils -import android.widget.SeekBar -import android.widget.TextView -import com.simplemobiletools.commons.extensions.getFormattedDuration -import com.simplemobiletools.commons.extensions.toast -import com.simplemobiletools.commons.extensions.updateTextColors -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.extensions.config -import com.simplemobiletools.gallery.extensions.getNavBarHeight -import com.simplemobiletools.gallery.extensions.hasNavBar -import com.simplemobiletools.gallery.helpers.MEDIUM -import com.simplemobiletools.gallery.models.Medium -import kotlinx.android.synthetic.main.pager_video_item.view.* -import java.io.IOException - -class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSeekBarChangeListener { - - private var mMediaPlayer: MediaPlayer? = null - private var mSurfaceView: SurfaceView? = null - private var mSurfaceHolder: SurfaceHolder? = null - private var mCurrTimeView: TextView? = null - private var mTimerHandler: Handler? = null - private var mSeekBar: SeekBar? = null - private var mTimeHolder: View? = null - - private var mIsPlaying = false - private var mIsDragged = false - private var mIsFullscreen = false - private var mIsFragmentVisible = false - private var mCurrTime = 0 - private var mDuration = 0 - - lateinit var mView: View - lateinit var medium: Medium - - companion object { - private val TAG = VideoFragment::class.java.simpleName - private val PROGRESS = "progress" - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - mView = inflater.inflate(R.layout.pager_video_item, container, false) - setupPlayer() - - medium = arguments.getSerializable(MEDIUM) as Medium - if (savedInstanceState != null) { - mCurrTime = savedInstanceState.getInt(PROGRESS) - } - - mIsFullscreen = activity.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN == View.SYSTEM_UI_FLAG_FULLSCREEN - - activity.window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> - val fullscreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0 - mIsFullscreen = fullscreen - checkFullscreen() - listener?.systemUiVisibilityChanged(visibility) - } - - return mView - } - - override fun onResume() { - super.onResume() - activity.updateTextColors(mView.video_holder) - } - - private fun setupPlayer() { - if (activity == null) - return - - mView.video_play_outline.setOnClickListener { togglePlayPause() } - - mSurfaceView = mView.video_surface - mSurfaceHolder = mSurfaceView!!.holder - mSurfaceHolder!!.addCallback(this) - mSurfaceView!!.setOnClickListener({ toggleFullscreen() }) - mView.video_holder.setOnClickListener { toggleFullscreen() } - - initTimeHolder() - } - - override fun setMenuVisibility(menuVisible: Boolean) { - super.setMenuVisibility(menuVisible) - mIsFragmentVisible = menuVisible - if (menuVisible) { - if (context?.config?.autoplayVideos == true) { - playVideo() - } - } else if (mIsPlaying) { - pauseVideo() - } - } - - override fun onConfigurationChanged(newConfig: Configuration?) { - super.onConfigurationChanged(newConfig) - setVideoSize() - initTimeHolder() - } - - private fun toggleFullscreen() { - mIsFullscreen = !mIsFullscreen - checkFullscreen() - listener?.fragmentClicked() - } - - private fun initTimeHolder() { - mTimeHolder = mView.video_time_holder - val res = resources - val height = res.getNavBarHeight() - val left = mTimeHolder!!.paddingLeft - val top = mTimeHolder!!.paddingTop - var right = res.getDimension(R.dimen.timer_padding).toInt() - var bottom = 0 - - if (activity.hasNavBar()) { - if (res.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { - bottom += height - } else { - right += height - } - mTimeHolder!!.setPadding(left, top, right, bottom) - } - - mCurrTimeView = mView.video_curr_time - mSeekBar = mView.video_seekbar - mSeekBar!!.setOnSeekBarChangeListener(this) - - if (mIsFullscreen) - mTimeHolder!!.visibility = View.INVISIBLE - } - - private fun setupTimeHolder() { - mSeekBar!!.max = mDuration - mView.video_duration.text = mDuration.getFormattedDuration() - mTimerHandler = Handler() - setupTimer() - } - - private fun setupTimer() { - activity.runOnUiThread(object : Runnable { - override fun run() { - if (mMediaPlayer != null && !mIsDragged && mIsPlaying) { - mCurrTime = mMediaPlayer!!.currentPosition / 1000 - mSeekBar!!.progress = mCurrTime - mCurrTimeView!!.text = mCurrTime.getFormattedDuration() - } - - mTimerHandler!!.postDelayed(this, 1000) - } - }) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putInt(PROGRESS, mCurrTime) - } - - private fun checkFullscreen() { - if (activity == null) - return - - var anim = android.R.anim.fade_in - if (mIsFullscreen) { - anim = android.R.anim.fade_out - mSeekBar!!.setOnSeekBarChangeListener(null) - } else { - mSeekBar!!.setOnSeekBarChangeListener(this) - } - - AnimationUtils.loadAnimation(activity, anim).apply { - duration = 150 - fillAfter = true - mTimeHolder!!.startAnimation(this) - } - } - - private fun togglePlayPause() { - if (activity == null || !isAdded) - return - - mIsPlaying = !mIsPlaying - if (mIsPlaying) { - playVideo() - } else { - pauseVideo() - } - } - - private fun playVideo() { - mIsPlaying = true - mMediaPlayer?.start() - mView.video_play_outline.setImageDrawable(null) - activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - - private fun pauseVideo() { - mIsPlaying = false - mMediaPlayer?.pause() - mView.video_play_outline.setImageDrawable(resources.getDrawable(R.drawable.img_play_outline_big)) - activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - - private fun initMediaPlayer() { - if (mMediaPlayer != null) - return - - try { - mMediaPlayer = MediaPlayer().apply { - setDataSource(context, Uri.parse(medium.path)) - setDisplay(mSurfaceHolder) - setOnCompletionListener { videoCompleted() } - setOnVideoSizeChangedListener({ mediaPlayer, width, height -> setVideoSize() }) - setOnPreparedListener { videoPrepared(it) } - setAudioStreamType(AudioManager.STREAM_MUSIC) - prepareAsync() - } - } catch (e: IOException) { - Log.e(TAG, "init media player failed $e") - } - } - - private fun setProgress(seconds: Int) { - mMediaPlayer!!.seekTo(seconds * 1000) - mSeekBar!!.progress = seconds - mCurrTimeView!!.text = seconds.getFormattedDuration() - } - - private fun addPreviewImage() { - mMediaPlayer!!.start() - mMediaPlayer!!.pause() - } - - override fun onPause() { - super.onPause() - pauseVideo() - mIsFragmentVisible = false - } - - override fun onDestroy() { - super.onDestroy() - if (activity?.isChangingConfigurations == false) { - cleanup() - } - } - - private fun cleanup() { - pauseVideo() - mCurrTimeView?.text = 0.getFormattedDuration() - mMediaPlayer?.release() - mMediaPlayer = null - mSeekBar?.progress = 0 - mTimerHandler?.removeCallbacksAndMessages(null) - } - - private fun videoPrepared(mediaPlayer: MediaPlayer) { - mDuration = mediaPlayer.duration / 1000 - addPreviewImage() - setupTimeHolder() - setProgress(mCurrTime) - - if (mIsFragmentVisible && context.config.autoplayVideos) - playVideo() - } - - private fun videoCompleted() { - if (context.config.loopVideos) { - playVideo() - } else { - mSeekBar!!.progress = mSeekBar!!.max - mCurrTimeView!!.text = mDuration.getFormattedDuration() - pauseVideo() - } - } - - override fun surfaceCreated(holder: SurfaceHolder) { - - } - - override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { - if (width != 0 && height != 0 && mSurfaceView != null) - setVideoSize() - } - - override fun surfaceDestroyed(holder: SurfaceHolder) { - mMediaPlayer?.release() - mMediaPlayer = null - } - - private fun setVideoSize() { - if (mSurfaceHolder == null) - mSurfaceHolder = mSurfaceView!!.holder - - if (activity == null || mSurfaceHolder == null || !mSurfaceHolder!!.surface.isValid) - return - - initMediaPlayer() - if (mMediaPlayer == null) { - activity.toast(R.string.unknown_error_occurred) - return - } - - val videoProportion = mMediaPlayer!!.videoWidth.toFloat() / mMediaPlayer!!.videoHeight.toFloat() - val display = activity.windowManager.defaultDisplay - val screenWidth: Int - val screenHeight: Int - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - val realMetrics = DisplayMetrics() - display.getRealMetrics(realMetrics) - screenWidth = realMetrics.widthPixels - screenHeight = realMetrics.heightPixels - } else { - screenWidth = display.width - screenHeight = display.height - } - - val screenProportion = screenWidth.toFloat() / screenHeight.toFloat() - - mSurfaceView!!.layoutParams.apply { - if (videoProportion > screenProportion) { - width = screenWidth - height = (screenWidth.toFloat() / videoProportion).toInt() - } else { - width = (videoProportion * screenHeight.toFloat()).toInt() - height = screenHeight - } - mSurfaceView!!.layoutParams = this - } - } - - override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { - if (mMediaPlayer != null && fromUser) { - setProgress(progress) - } - } - - override fun onStartTrackingTouch(seekBar: SeekBar) { - initMediaPlayer() - mMediaPlayer!!.pause() - mIsDragged = true - } - - override fun onStopTrackingTouch(seekBar: SeekBar) { - if (!mIsPlaying) { - togglePlayPause() - } else { - mMediaPlayer!!.start() - } - - mIsDragged = false - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/ViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/ViewPagerFragment.kt deleted file mode 100644 index bd6bc4a50..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/ViewPagerFragment.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.simplemobiletools.gallery.fragments - -import android.support.v4.app.Fragment - -abstract class ViewPagerFragment : Fragment() { - var listener: FragmentListener? = null - - interface FragmentListener { - fun fragmentClicked() - - fun systemUiVisibilityChanged(visibility: Int) - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Config.kt deleted file mode 100644 index 424721c88..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Config.kt +++ /dev/null @@ -1,182 +0,0 @@ -package com.simplemobiletools.gallery.helpers - -import android.content.Context -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import com.simplemobiletools.commons.helpers.BaseConfig -import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED -import com.simplemobiletools.commons.helpers.SORT_DESCENDING -import com.simplemobiletools.gallery.R -import com.simplemobiletools.gallery.models.AlbumCover -import java.util.* - -class Config(context: Context) : BaseConfig(context) { - companion object { - fun newInstance(context: Context) = Config(context) - } - - var fileSorting: Int - get() = prefs.getInt(SORT_ORDER, SORT_BY_DATE_MODIFIED or SORT_DESCENDING) - set(order) = prefs.edit().putInt(SORT_ORDER, order).apply() - - var directorySorting: Int - get() = prefs.getInt(DIRECTORY_SORT_ORDER, SORT_BY_DATE_MODIFIED or SORT_DESCENDING) - set(order) = prefs.edit().putInt(DIRECTORY_SORT_ORDER, order).apply() - - fun saveFileSorting(path: String, value: Int) { - if (path.isEmpty()) { - fileSorting = value - } else { - prefs.edit().putInt(SORT_FOLDER_PREFIX + path, value).apply() - } - } - - fun getFileSorting(path: String) = prefs.getInt(SORT_FOLDER_PREFIX + path, fileSorting) - - fun removeFileSorting(path: String) { - prefs.edit().remove(SORT_FOLDER_PREFIX + path).apply() - } - - fun hasCustomSorting(path: String) = prefs.contains(SORT_FOLDER_PREFIX + path) - - var wasHideFolderTooltipShown: Boolean - get() = prefs.getBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, false) - set(wasShown) = prefs.edit().putBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, wasShown).apply() - - var shouldShowHidden = showHiddenMedia || temporarilyShowHidden - - var showHiddenMedia: Boolean - get() = prefs.getBoolean(SHOW_HIDDEN_MEDIA, false) - set(showHiddenFolders) = prefs.edit().putBoolean(SHOW_HIDDEN_MEDIA, showHiddenFolders).apply() - - var temporarilyShowHidden: Boolean - get() = prefs.getBoolean(TEMPORARILY_SHOW_HIDDEN, false) - set(temporarilyShowHidden) = prefs.edit().putBoolean(TEMPORARILY_SHOW_HIDDEN, temporarilyShowHidden).apply() - - var pinnedFolders: Set - get() = prefs.getStringSet(PINNED_FOLDERS, HashSet()) - set(pinnedFolders) = prefs.edit().putStringSet(PINNED_FOLDERS, pinnedFolders).apply() - - var showAll: Boolean - get() = prefs.getBoolean(SHOW_ALL, false) - set(showAll) = prefs.edit().putBoolean(SHOW_ALL, showAll).apply() - - fun addPinnedFolders(paths: Set) { - val currPinnedFolders = HashSet(pinnedFolders) - currPinnedFolders.addAll(paths) - pinnedFolders = currPinnedFolders - } - - fun removePinnedFolders(paths: Set) { - val currPinnedFolders = HashSet(pinnedFolders) - currPinnedFolders.removeAll(paths) - pinnedFolders = currPinnedFolders - } - - fun addExcludedFolder(path: String) { - addExcludedFolders(HashSet(Arrays.asList(path))) - } - - fun addExcludedFolders(paths: Set) { - val currExcludedFolders = HashSet(excludedFolders) - currExcludedFolders.addAll(paths) - excludedFolders = currExcludedFolders - } - - fun removeExcludedFolder(path: String) { - val currExcludedFolders = HashSet(excludedFolders) - currExcludedFolders.remove(path) - excludedFolders = currExcludedFolders - } - - var excludedFolders: MutableSet - get() = prefs.getStringSet(EXCLUDED_FOLDERS, getDataFolder()) - set(excludedFolders) = prefs.edit().remove(EXCLUDED_FOLDERS).putStringSet(EXCLUDED_FOLDERS, excludedFolders).apply() - - private fun getDataFolder(): Set { - val folders = HashSet() - val dataFolder = context.externalCacheDir?.parentFile?.parent?.trimEnd('/') ?: "" - if (dataFolder.endsWith("data")) - folders.add(dataFolder) - return folders - } - - fun addIncludedFolder(path: String) { - val currIncludedFolders = HashSet(includedFolders) - currIncludedFolders.add(path) - includedFolders = currIncludedFolders - } - - fun removeIncludedFolder(path: String) { - val currIncludedFolders = HashSet(includedFolders) - currIncludedFolders.remove(path) - includedFolders = currIncludedFolders - } - - var includedFolders: MutableSet - get() = prefs.getStringSet(INCLUDED_FOLDERS, HashSet()) - set(includedFolders) = prefs.edit().remove(INCLUDED_FOLDERS).putStringSet(INCLUDED_FOLDERS, includedFolders).apply() - - fun saveFolderMedia(path: String, json: String) { - prefs.edit().putString(SAVE_FOLDER_PREFIX + path, json).apply() - } - - fun loadFolderMedia(path: String) = prefs.getString(SAVE_FOLDER_PREFIX + path, "") - - var autoplayVideos: Boolean - get() = prefs.getBoolean(AUTOPLAY_VIDEOS, false) - set(autoplay) = prefs.edit().putBoolean(AUTOPLAY_VIDEOS, autoplay).apply() - - var animateGifs: Boolean - get() = prefs.getBoolean(ANIMATE_GIFS, false) - set(animateGifs) = prefs.edit().putBoolean(ANIMATE_GIFS, animateGifs).apply() - - var maxBrightness: Boolean - get() = prefs.getBoolean(MAX_BRIGHTNESS, false) - set(maxBrightness) = prefs.edit().putBoolean(MAX_BRIGHTNESS, maxBrightness).apply() - - var cropThumbnails: Boolean - get() = prefs.getBoolean(CROP_THUMBNAILS, true) - set(cropThumbnails) = prefs.edit().putBoolean(CROP_THUMBNAILS, cropThumbnails).apply() - - var screenRotation: Int - get() = prefs.getInt(SCREEN_ROTATION, ROTATE_BY_SYSTEM_SETTING) - set(screenRotation) = prefs.edit().putInt(SCREEN_ROTATION, screenRotation).apply() - - var loopVideos: Boolean - get() = prefs.getBoolean(LOOP_VIDEOS, false) - set(loop) = prefs.edit().putBoolean(LOOP_VIDEOS, loop).apply() - - var displayFileNames: Boolean - get() = prefs.getBoolean(DISPLAY_FILE_NAMES, false) - set(display) = prefs.edit().putBoolean(DISPLAY_FILE_NAMES, display).apply() - - var darkBackground: Boolean - get() = prefs.getBoolean(DARK_BACKGROUND, false) - set(darkBackground) = prefs.edit().putBoolean(DARK_BACKGROUND, darkBackground).apply() - - var showMedia: Int - get() = prefs.getInt(SHOW_MEDIA, IMAGES_AND_VIDEOS) - set(showMedia) = prefs.edit().putInt(SHOW_MEDIA, showMedia).apply() - - var dirColumnCnt: Int - get() = prefs.getInt(DIR_COLUMN_CNT, context.resources.getInteger(R.integer.directory_columns)) - set(dirColumnCnt) = prefs.edit().putInt(DIR_COLUMN_CNT, dirColumnCnt).apply() - - var mediaColumnCnt: Int - get() = prefs.getInt(MEDIA_COLUMN_CNT, context.resources.getInteger(R.integer.media_columns)) - set(mediaColumnCnt) = prefs.edit().putInt(MEDIA_COLUMN_CNT, mediaColumnCnt).apply() - - var directories: String - get() = prefs.getString(DIRECTORIES, "") - set(directories) = prefs.edit().putString(DIRECTORIES, directories).apply() - - var albumCovers: String - get() = prefs.getString(ALBUM_COVERS, "") - set(albumCovers) = prefs.edit().putString(ALBUM_COVERS, albumCovers).apply() - - fun parseAlbumCovers(): ArrayList { - val listType = object : TypeToken>() {}.type - return Gson().fromJson>(albumCovers, listType) ?: ArrayList(1) - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Constants.kt deleted file mode 100644 index 21dc7c6de..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/Constants.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.simplemobiletools.gallery.helpers - -// shared preferences -val SORT_ORDER = "sort_order" -val DIRECTORY_SORT_ORDER = "directory_sort_order" -val SORT_FOLDER_PREFIX = "sort_folder_" -val SHOW_HIDDEN_MEDIA = "show_hidden_media" -val TEMPORARILY_SHOW_HIDDEN = "temporarily_show_hidden" -val AUTOPLAY_VIDEOS = "autoplay_videos" -val LOOP_VIDEOS = "loop_videos" -val ANIMATE_GIFS = "animate_gifs" -val MAX_BRIGHTNESS = "max_brightness" -val CROP_THUMBNAILS = "crop_thumbnails" -val SCREEN_ROTATION = "screen_rotation" -val DISPLAY_FILE_NAMES = "display_file_names" -val DARK_BACKGROUND = "dark_background" -val PINNED_FOLDERS = "pinned_folders" -val DIR_COLUMN_CNT = "dir_column_cnt" -val MEDIA_COLUMN_CNT = "media_column_cnt" -val SHOW_ALL = "show_all" // display images and videos from all folders together -val SHOW_MEDIA = "show_media" -val SAVE_FOLDER_PREFIX = "folder2_" -val HIDE_FOLDER_TOOLTIP_SHOWN = "hide_folder_tooltip_shown" -val EXCLUDED_FOLDERS = "excluded_folders" -val INCLUDED_FOLDERS = "included_folders" -val ALBUM_COVERS = "album_covers" - -val NOMEDIA = ".nomedia" - -val DIRECTORY = "directory" -val MEDIUM = "medium" -val GET_IMAGE_INTENT = "get_image_intent" -val GET_VIDEO_INTENT = "get_video_intent" -val GET_ANY_INTENT = "get_any_intent" -val SET_WALLPAPER_INTENT = "set_wallpaper_intent" -val DIRECTORIES = "directories2" -val IS_VIEW_INTENT = "is_view_intent" - -val REQUEST_EDIT_IMAGE = 1 -val REQUEST_SET_WALLPAPER = 2 - -// show media -val IMAGES_AND_VIDEOS = 0 -val IMAGES = 1 -val VIDEOS = 2 - -// rotations -val ROTATE_BY_SYSTEM_SETTING = 0 -val ROTATE_BY_DEVICE_ROTATION = 1 -val ROTATE_BY_ASPECT_RATIO = 2 - -val ORIENT_PORTRAIT = 0 -val ORIENT_LANDSCAPE_LEFT = 1 -val ORIENT_LANDSCAPE_RIGHT = 2 -val ORIENT_UPSIDE_DOWN = 3 diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt deleted file mode 100644 index 4e347dd9e..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.simplemobiletools.gallery.helpers - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Matrix -import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool -import com.bumptech.glide.load.resource.bitmap.BitmapTransformation - -class GlideRotateTransformation(context: Context, val rotateRotationAngle: Float) : BitmapTransformation(context) { - - override fun transform(pool: BitmapPool, bitmap: Bitmap, outWidth: Int, outHeight: Int): Bitmap { - if (rotateRotationAngle % 360 == 0f) - return bitmap - - val matrix = Matrix() - matrix.postRotate(rotateRotationAngle) - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) - } - - override fun getId() = "GlideRotateTransformation $rotateRotationAngle" -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Directory.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Directory.kt deleted file mode 100644 index 77cd94602..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Directory.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.simplemobiletools.gallery.models - -import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED -import com.simplemobiletools.commons.helpers.SORT_BY_NAME -import com.simplemobiletools.commons.helpers.SORT_BY_SIZE -import com.simplemobiletools.commons.helpers.SORT_DESCENDING -import java.io.Serializable - -data class Directory(val path: String, val tmb: String, val name: String, var mediaCnt: Int, val modified: Long, val taken: Long, - var size: Long) : Serializable, Comparable { - companion object { - private val serialVersionUID = -6553345863555455L - var sorting: Int = 0 - } - - fun addSize(bytes: Long) { - size += bytes - } - - override fun compareTo(other: Directory): Int { - var result: Int - if (sorting and SORT_BY_NAME != 0) { - result = name.toLowerCase().compareTo(other.name.toLowerCase()) - } else if (sorting and SORT_BY_SIZE != 0) { - result = if (size == other.size) - 0 - else if (size > other.size) - 1 - else - -1 - } else if (sorting and SORT_BY_DATE_MODIFIED != 0) { - result = if (modified == other.modified) - 0 - else if (modified > other.modified) - 1 - else - -1 - } else { - result = if (taken == other.taken) - 0 - else if (taken > other.taken) - 1 - else - -1 - } - - if (sorting and SORT_DESCENDING != 0) { - result *= -1 - } - return result - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt deleted file mode 100644 index c9afe29f0..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.simplemobiletools.gallery.models - -import com.simplemobiletools.commons.extensions.getMimeType -import com.simplemobiletools.commons.extensions.isGif -import com.simplemobiletools.commons.extensions.isPng -import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED -import com.simplemobiletools.commons.helpers.SORT_BY_NAME -import com.simplemobiletools.commons.helpers.SORT_BY_SIZE -import com.simplemobiletools.commons.helpers.SORT_DESCENDING -import java.io.File -import java.io.Serializable - -data class Medium(var name: String, var path: String, val video: Boolean, val modified: Long, val taken: Long, val size: Long) : Serializable, Comparable { - companion object { - private val serialVersionUID = -6553149366975455L - var sorting: Int = 0 - } - - fun isPng() = path.isPng() - - fun isGif() = path.isGif() - - fun isImage() = !isGif() && !video - - fun getMimeType() = File(path).getMimeType() - - override fun compareTo(other: Medium): Int { - var res: Int - if (sorting and SORT_BY_NAME != 0) { - res = name.toLowerCase().compareTo(other.name.toLowerCase()) - } else if (sorting and SORT_BY_SIZE != 0) { - res = if (size == other.size) - 0 - else if (size > other.size) - 1 - else - -1 - } else if (sorting and SORT_BY_DATE_MODIFIED != 0) { - res = if (modified == other.modified) - 0 - else if (modified > other.modified) - 1 - else - -1 - } else { - res = if (taken == other.taken) - 0 - else if (taken > other.taken) - 1 - else - -1 - } - - if (sorting and SORT_DESCENDING != 0) { - res *= -1 - } - return res - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/App.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/App.kt new file mode 100644 index 000000000..07e9a4097 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/App.kt @@ -0,0 +1,13 @@ +package com.simplemobiletools.gallery.pro + +import android.app.Application +import com.github.ajalt.reprint.core.Reprint +import com.simplemobiletools.commons.extensions.checkUseEnglish + +class App : Application() { + override fun onCreate() { + super.onCreate() + checkUseEnglish() + Reprint.initialize(this) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/EditActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/EditActivity.kt new file mode 100644 index 000000000..26cea909e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/EditActivity.kt @@ -0,0 +1,897 @@ +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.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.provider.MediaStore +import android.view.Menu +import android.view.MenuItem +import android.widget.RelativeLayout +import androidx.exifinterface.media.ExifInterface +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.dialogs.ConfirmationDialog +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.REAL_FILE_PATH +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.commons.helpers.isNougatPlus +import com.simplemobiletools.commons.models.FileDirItem +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.copyNonDimensionAttributesTo +import com.simplemobiletools.gallery.pro.extensions.fixDateTaken +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 +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.* + +class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener { + companion object { + init { + System.loadLibrary("NativeImageProcessor") + } + } + + private val TEMP_FOLDER_NAME = "images" + private val ASPECT_X = "aspectX" + private val ASPECT_Y = "aspectY" + private val CROP = "crop" + + // 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 + + private lateinit var saveUri: Uri + private var uri: Uri? = null + private var resizeWidth = 0 + private var resizeHeight = 0 + private var drawColor = 0 + private var lastOtherAspectRatio: Pair? = null + private var currPrimaryAction = PRIMARY_ACTION_NONE + private var currCropRotateAction = CROP_ROTATE_ASPECT_RATIO + private var currAspectRatio = ASPECT_RATIO_FREE + private var isCropIntent = false + private var isEditingWithThirdParty = false + private var isSharingBitmap = false + private var wasDrawCanvasPositioned = false + private var oldExif: ExifInterface? = null + private var filterInitialBitmap: Bitmap? = null + private var originalUri: Uri? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_edit) + + if (checkAppSideloading()) { + return + } + + initEditActivity() + } + + 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) + updateMenuItemColors(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) + finish() + return + } + + uri = intent.data!! + originalUri = uri + if (uri!!.scheme != "file" && uri!!.scheme != "content") { + toast(R.string.unknown_file_location) + finish() + return + } + + if (intent.extras?.containsKey(REAL_FILE_PATH) == true) { + val realPath = intent.extras!!.getString(REAL_FILE_PATH) + uri = when { + isPathOnOTG(realPath!!) -> uri + realPath.startsWith("file:/") -> Uri.parse(realPath) + else -> Uri.fromFile(File(realPath)) + } + } else { + (getRealPathFromURI(uri!!))?.apply { + uri = Uri.fromFile(File(this)) + } + } + + saveUri = when { + intent.extras?.containsKey(MediaStore.EXTRA_OUTPUT) == true -> intent.extras!!.get(MediaStore.EXTRA_OUTPUT) as Uri + else -> uri!! + } + + isCropIntent = intent.extras?.get(CROP) == "true" + if (isCropIntent) { + bottom_editor_primary_actions.beGone() + (bottom_editor_crop_rotate_actions.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 1) + } + + loadDefaultImageView() + setupBottomActions() + + if (config.lastEditorCropAspectRatio == ASPECT_RATIO_OTHER) { + if (config.lastEditorCropOtherAspectRatioX == 0f) { + config.lastEditorCropOtherAspectRatioX = 1f + } + + if (config.lastEditorCropOtherAspectRatioY == 0f) { + config.lastEditorCropOtherAspectRatioY = 1f + } + + lastOtherAspectRatio = Pair(config.lastEditorCropOtherAspectRatioX, config.lastEditorCropOtherAspectRatioY) + } + updateAspectRatio(config.lastEditorCropAspectRatio) + crop_image_view.guidelines = CropImageView.Guidelines.ON + bottom_aspect_ratios.beVisible() + } + + private fun loadDefaultImageView() { + default_image_view.beVisible() + crop_image_view.beGone() + editor_draw_canvas.beGone() + + val options = RequestOptions() + .skipMemoryCache(true) + .diskCacheStrategy(DiskCacheStrategy.NONE) + + Glide.with(this) + .asBitmap() + .load(uri) + .apply(options) + .listener(object : RequestListener { + override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { + if (uri != originalUri) { + uri = originalUri + Handler().post { + loadDefaultImageView() + } + } + return false + } + + override fun onResourceReady(bitmap: Bitmap?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { + val currentFilter = getFiltersAdapter()?.getCurrentFilter() + if (filterInitialBitmap == null) { + loadCropImageView() + bottomCropRotateClicked() + } + + if (filterInitialBitmap != null && currentFilter != null && currentFilter.filter.name != getString(R.string.none)) { + default_image_view.onGlobalLayout { + applyFilter(currentFilter) + } + } else { + filterInitialBitmap = bitmap + } + + if (isCropIntent) { + bottom_primary_filter.beGone() + bottom_primary_draw.beGone() + } + + return false + } + }).into(default_image_view) + } + + private fun loadCropImageView() { + default_image_view.beGone() + editor_draw_canvas.beGone() + crop_image_view.apply { + beVisible() + setOnCropImageCompleteListener(this@EditActivity) + setImageUriAsync(uri) + guidelines = CropImageView.Guidelines.ON + + if (isCropIntent && shouldCropSquare()) { + currAspectRatio = ASPECT_RATIO_ONE_ONE + setFixedAspectRatio(true) + bottom_aspect_ratio.beGone() + } + } + } + + private fun loadDrawCanvas() { + default_image_view.beGone() + crop_image_view.beGone() + editor_draw_canvas.beVisible() + + if (!wasDrawCanvasPositioned) { + wasDrawCanvasPositioned = true + editor_draw_canvas.onGlobalLayout { + ensureBackgroundThread { + fillCanvasBackground() + } + } + } + } + + 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() { + setOldExif() + + 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() + SaveAsDialog(this, filePathGetter.first, filePathGetter.second) { + toast(R.string.saving) + + // clean up everything to free as much memory as possible + default_image_view.setImageResource(0) + crop_image_view.setImageBitmap(null) + bottom_actions_filter_list.adapter = null + bottom_actions_filter_list.beGone() + + ensureBackgroundThread { + try { + val originalBitmap = Glide.with(applicationContext).asBitmap().load(uri).submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get() + currentFilter.filter.processFilter(originalBitmap) + saveBitmapToFile(originalBitmap, it, false) + } catch (e: OutOfMemoryError) { + toast(R.string.out_of_memory_error) + } + } + } + } + } + + @TargetApi(Build.VERSION_CODES.N) + private fun setOldExif() { + var inputStream: InputStream? = null + try { + if (isNougatPlus()) { + inputStream = contentResolver.openInputStream(uri!!) + oldExif = ExifInterface(inputStream!!) + } + } catch (e: Exception) { + } finally { + inputStream?.close() + } + } + + private fun shareImage() { + ensureBackgroundThread { + when { + default_image_view.isVisible() -> { + val currentFilter = getFiltersAdapter()?.getCurrentFilter() + if (currentFilter == null) { + toast(R.string.unknown_error_occurred) + return@ensureBackgroundThread + } + + 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()) + } + } + } + + private fun getTempImagePath(bitmap: Bitmap, callback: (path: String?) -> Unit) { + val bytes = ByteArrayOutputStream() + bitmap.compress(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() { + bottom_primary_filter.setOnClickListener { + bottomFilterClicked() + } + + bottom_primary_crop_rotate.setOnClickListener { + bottomCropRotateClicked() + } + + bottom_primary_draw.setOnClickListener { + bottomDrawClicked() + } + } + + private fun bottomFilterClicked() { + currPrimaryAction = if (currPrimaryAction == PRIMARY_ACTION_FILTER) { + PRIMARY_ACTION_NONE + } else { + PRIMARY_ACTION_FILTER + } + updatePrimaryActionButtons() + } + + private fun bottomCropRotateClicked() { + currPrimaryAction = if (currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE) { + PRIMARY_ACTION_NONE + } else { + PRIMARY_ACTION_CROP_ROTATE + } + 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) + } + + bottom_resize.beGoneIf(isCropIntent) + bottom_resize.setOnClickListener { + resizeImage() + } + + bottom_flip_horizontally.setOnClickListener { + crop_image_view.flipImageHorizontally() + } + + bottom_flip_vertically.setOnClickListener { + crop_image_view.flipImageVertically() + } + + bottom_aspect_ratio.setOnClickListener { + currCropRotateAction = if (currCropRotateAction == CROP_ROTATE_ASPECT_RATIO) { + crop_image_view.guidelines = CropImageView.Guidelines.OFF + bottom_aspect_ratios.beGone() + CROP_ROTATE_NONE + } else { + crop_image_view.guidelines = CropImageView.Guidelines.ON + bottom_aspect_ratios.beVisible() + CROP_ROTATE_ASPECT_RATIO + } + updateCropRotateActionButtons() + } + } + + private fun setupAspectRatioButtons() { + bottom_aspect_ratio_free.setOnClickListener { + updateAspectRatio(ASPECT_RATIO_FREE) + } + + bottom_aspect_ratio_one_one.setOnClickListener { + updateAspectRatio(ASPECT_RATIO_ONE_ONE) + } + + bottom_aspect_ratio_four_three.setOnClickListener { + updateAspectRatio(ASPECT_RATIO_FOUR_THREE) + } + + 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, 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(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) { + ensureBackgroundThread { + val thumbnailSize = resources.getDimension(R.dimen.bottom_filters_thumbnail_size).toInt() + + val bitmap = try { + Glide.with(this) + .asBitmap() + .load(uri).listener(object : RequestListener { + override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { + showErrorToast(e.toString()) + return false + } + + override fun onResourceReady(resource: Bitmap?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean) = false + }) + .submit(thumbnailSize, thumbnailSize) + .get() + } catch (e: GlideException) { + showErrorToast(e) + finish() + return@ensureBackgroundThread + } + + runOnUiThread { + val filterThumbnailsManager = FilterThumbnailsManager() + filterThumbnailsManager.clearThumbs() + + val noFilter = Filter(getString(R.string.none)) + filterThumbnailsManager.addThumb(FilterItem(bitmap, noFilter)) + + FilterPack.getFilterPack(this).forEach { + val filterItem = FilterItem(bitmap, it) + filterThumbnailsManager.addThumb(filterItem) + } + + val filterItems = filterThumbnailsManager.processThumbs() + val adapter = FiltersAdapter(applicationContext, filterItems) { + val layoutManager = bottom_actions_filter_list.layoutManager as LinearLayoutManager + applyFilter(filterItems[it]) + + if (it == layoutManager.findLastCompletelyVisibleItemPosition() || it == layoutManager.findLastVisibleItemPosition()) { + bottom_actions_filter_list.smoothScrollBy(thumbnailSize, 0) + } else if (it == layoutManager.findFirstCompletelyVisibleItemPosition() || it == layoutManager.findFirstVisibleItemPosition()) { + bottom_actions_filter_list.smoothScrollBy(-thumbnailSize, 0) + } + } + + bottom_actions_filter_list.adapter = adapter + adapter.notifyDataSetChanged() + } + } + } + + if (currPrimaryAction != PRIMARY_ACTION_CROP_ROTATE) { + bottom_aspect_ratios.beGone() + currCropRotateAction = CROP_ROTATE_NONE + } + updateCropRotateActionButtons() + } + + private fun applyFilter(filterItem: FilterItem) { + 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 { + if (aspectRatio == ASPECT_RATIO_FREE) { + setFixedAspectRatio(false) + } else { + val newAspectRatio = when (aspectRatio) { + ASPECT_RATIO_ONE_ONE -> Pair(1f, 1f) + ASPECT_RATIO_FOUR_THREE -> Pair(4f, 3f) + ASPECT_RATIO_SIXTEEN_NINE -> Pair(16f, 9f) + else -> Pair(lastOtherAspectRatio!!.first, lastOtherAspectRatio!!.second) + } + + setAspectRatio(newAspectRatio.first.toInt(), newAspectRatio.second.toInt()) + } + } + } + + private fun updateAspectRatioButtons() { + 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) + } + + val currentAspectRatioButton = when (currAspectRatio) { + 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 + ASPECT_RATIO_SIXTEEN_NINE -> bottom_aspect_ratio_sixteen_nine + else -> bottom_aspect_ratio_other + } + + currentAspectRatioButton.setTextColor(getAdjustedPrimaryColor()) + } + + private fun updateCropRotateActionButtons() { + arrayOf(bottom_aspect_ratio).forEach { + it.applyColorFilter(Color.WHITE) + } + + val primaryActionView = when (currCropRotateAction) { + CROP_ROTATE_ASPECT_RATIO -> bottom_aspect_ratio + else -> null + } + + 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() { + val point = getAreaSize() + if (point == null) { + toast(R.string.unknown_error_occurred) + return + } + + ResizeDialog(this, point) { + resizeWidth = it.x + resizeHeight = it.y + crop_image_view.getCroppedImageAsync() + } + } + + private fun shouldCropSquare(): Boolean { + val extras = intent.extras + return if (extras != null && extras.containsKey(ASPECT_X) && extras.containsKey(ASPECT_Y)) { + extras.getInt(ASPECT_X) == extras.getInt(ASPECT_Y) + } else { + false + } + } + + private fun getAreaSize(): Point? { + val rect = crop_image_view.cropRect ?: return null + val rotation = crop_image_view.rotatedDegrees + return if (rotation == 0 || rotation == 180) { + Point(rect.width(), rect.height()) + } else { + Point(rect.height(), rect.width()) + } + } + + override fun onCropImageComplete(view: CropImageView, result: CropImageView.CropResult) { + if (result.error == null) { + setOldExif() + + val bitmap = result.bitmap + if (isSharingBitmap) { + isSharingBitmap = false + shareBitmap(bitmap) + return + } + + if (isCropIntent) { + if (saveUri.scheme == "file") { + saveBitmapToFile(bitmap, saveUri.path!!, true) + } else { + var inputStream: InputStream? = null + var outputStream: OutputStream? = null + try { + val stream = ByteArrayOutputStream() + bitmap.compress(CompressFormat.JPEG, 100, stream) + inputStream = ByteArrayInputStream(stream.toByteArray()) + outputStream = contentResolver.openOutputStream(saveUri) + inputStream.copyTo(outputStream!!) + } finally { + inputStream?.close() + outputStream?.close() + } + + Intent().apply { + data = saveUri + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + setResult(RESULT_OK, this) + } + finish() + } + } else 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 { + toast(R.string.unknown_file_location) + } + } else { + toast("${getString(R.string.image_editing_failed)}: ${result.error.message}") + } + } + + private fun getNewFilePath(): Pair { + var newPath = applicationContext.getRealPathFromURI(saveUri) ?: "" + if (newPath.startsWith("/mnt/")) { + newPath = "" + } + + var shouldAppendFilename = true + if (newPath.isEmpty()) { + val filename = applicationContext.getFilenameFromContentUri(saveUri) ?: "" + if (filename.isNotEmpty()) { + val path = if (intent.extras?.containsKey(REAL_FILE_PATH) == true) intent.getStringExtra(REAL_FILE_PATH).getParentPath() else internalStoragePath + newPath = "$path/$filename" + shouldAppendFilename = false + } + } + + if (newPath.isEmpty()) { + newPath = "$internalStoragePath/${getCurrentFormattedDateTime()}.${saveUri.toString().getFilenameExtension()}" + shouldAppendFilename = false + } + + return Pair(newPath, shouldAppendFilename) + } + + private fun saveBitmapToFile(bitmap: Bitmap, path: String, showSavingToast: Boolean) { + if (!packageName.contains("slootelibomelpmis".reversed(), true)) { + if (baseConfig.appRunCount > 100) { + val label = "sknahT .moc.slootelibomelpmis.www morf eno lanigiro eht daolnwod ytefas nwo ruoy roF .ppa eht fo noisrev ekaf a gnisu era uoY".reversed() + runOnUiThread { + ConfirmationDialog(this, label, positive = com.simplemobiletools.commons.R.string.ok, negative = 0) { + launchViewIntent("6629852208836920709=di?ved/sppa/erots/moc.elgoog.yalp//:sptth".reversed()) + } + } + return + } + } + + try { + ensureBackgroundThread { + val file = File(path) + val fileDirItem = FileDirItem(path, path.getFilenameFromPath()) + getFileOutputStream(fileDirItem, true) { + if (it != null) { + saveBitmap(file, bitmap, it, showSavingToast) + } else { + toast(R.string.image_editing_failed) + } + } + } + } catch (e: Exception) { + showErrorToast(e) + } catch (e: OutOfMemoryError) { + toast(R.string.out_of_memory_error) + } + } + + @TargetApi(Build.VERSION_CODES.N) + private fun saveBitmap(file: File, bitmap: Bitmap, out: OutputStream, showSavingToast: Boolean) { + if (showSavingToast) { + toast(R.string.saving) + } + + if (resizeWidth > 0 && resizeHeight > 0) { + val resized = Bitmap.createScaledBitmap(bitmap, resizeWidth, resizeHeight, false) + resized.compress(file.absolutePath.getCompressionFormat(), 90, out) + } else { + bitmap.compress(file.absolutePath.getCompressionFormat(), 90, out) + } + + try { + if (isNougatPlus()) { + val newExif = ExifInterface(file.absolutePath) + oldExif?.copyNonDimensionAttributesTo(newExif) + } + } catch (e: Exception) { + } + + setResult(Activity.RESULT_OK, intent) + scanFinalPath(file.absolutePath) + out.close() + } + + private fun editWith() { + openEditor(uri.toString(), true) + isEditingWithThirdParty = true + } + + private fun scanFinalPath(path: String) { + val paths = arrayListOf(path) + rescanPaths(paths) { + fixDateTaken(paths, false) + setResult(Activity.RESULT_OK, intent) + toast(R.string.file_saved) + finish() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ExcludedFoldersActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ExcludedFoldersActivity.kt new file mode 100644 index 000000000..2d584ab73 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ExcludedFoldersActivity.kt @@ -0,0 +1,59 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.os.Bundle +import android.view.Menu +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.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 { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_manage_folders) + updateFolders() + } + + private fun updateFolders() { + val folders = ArrayList() + config.excludedFolders.mapTo(folders) { it } + manage_folders_placeholder.apply { + text = getString(R.string.excluded_activity_placeholder) + beVisibleIf(folders.isEmpty()) + setTextColor(config.textColor) + } + + val adapter = ManageFoldersAdapter(this, folders, true, this, manage_folders_list) {} + manage_folders_list.adapter = adapter + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_add_folder, menu) + updateMenuItemColors(menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.add_folder -> addFolder() + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun refreshItems() { + updateFolders() + } + + private fun addFolder() { + FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden, false, true, true) { + config.lastFilepickerPath = it + config.addExcludedFolder(it) + updateFolders() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/HiddenFoldersActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/HiddenFoldersActivity.kt new file mode 100644 index 000000000..4a5aaac85 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/HiddenFoldersActivity.kt @@ -0,0 +1,67 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import com.simplemobiletools.commons.dialogs.FilePickerDialog +import com.simplemobiletools.commons.extensions.beVisibleIf +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener +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 { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_manage_folders) + updateFolders() + } + + private fun updateFolders() { + getNoMediaFolders { + runOnUiThread { + manage_folders_placeholder.apply { + text = getString(R.string.hidden_folders_placeholder) + beVisibleIf(it.isEmpty()) + setTextColor(config.textColor) + } + + val adapter = ManageHiddenFoldersAdapter(this, it, this, manage_folders_list) {} + manage_folders_list.adapter = adapter + } + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_add_folder, menu) + updateMenuItemColors(menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.add_folder -> addFolder() + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun refreshItems() { + updateFolders() + } + + private fun addFolder() { + FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden, false, true) { + config.lastFilepickerPath = it + ensureBackgroundThread { + addNoMedia(it) { + updateFolders() + } + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/IncludedFoldersActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/IncludedFoldersActivity.kt new file mode 100644 index 000000000..e1f0d9a06 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/IncludedFoldersActivity.kt @@ -0,0 +1,56 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import com.simplemobiletools.commons.extensions.beVisibleIf +import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener +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 { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_manage_folders) + updateFolders() + } + + private fun updateFolders() { + val folders = ArrayList() + config.includedFolders.mapTo(folders) { it } + manage_folders_placeholder.apply { + text = getString(R.string.included_activity_placeholder) + beVisibleIf(folders.isEmpty()) + setTextColor(config.textColor) + } + + val adapter = ManageFoldersAdapter(this, folders, false, this, manage_folders_list) {} + manage_folders_list.adapter = adapter + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_add_folder, menu) + updateMenuItemColors(menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.add_folder -> addFolder() + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun refreshItems() { + updateFolders() + } + + private fun addFolder() { + showAddIncludedFolderDialog { + updateFolders() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MainActivity.kt new file mode 100644 index 000000000..496f0968d --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MainActivity.kt @@ -0,0 +1,1443 @@ +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.provider.MediaStore.Images +import android.provider.MediaStore.Video +import android.view.Menu +import android.view.MenuItem +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.RelativeLayout +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.extensions.* +import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.commons.models.FileDirItem +import com.simplemobiletools.commons.models.Release +import com.simplemobiletools.commons.views.MyGridLayoutManager +import com.simplemobiletools.commons.views.MyRecyclerView +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.DirectoryOperationsListener +import com.simplemobiletools.gallery.pro.jobs.NewPhotoFetcher +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.* +import kotlin.collections.ArrayList + +class MainActivity : SimpleActivity(), DirectoryOperationsListener { + private val PICK_MEDIA = 2 + private val PICK_WALLPAPER = 3 + private val LAST_MEDIA_CHECK_PERIOD = 3000L + + private var mIsPickImageIntent = false + private var mIsPickVideoIntent = false + private var mIsGetImageContentIntent = false + private var mIsGetVideoContentIntent = false + private var mIsGetAnyContentIntent = false + private var mIsSetWallpaperIntent = false + private var mAllowPickingMultiple = false + private var mIsThirdPartyIntent = false + private var mIsGettingDirs = false + private var mLoadedInitialPhotos = false + private var mIsPasswordProtectionPending = false + private var mWasProtectionHandled = false + private var mShouldStopFetching = false + private var mIsSearchOpen = false + private var mWasDefaultFolderChecked = 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 mDateFormat = "" + private var mTimeFormat = "" + private var mLastMediaHandler = Handler() + private var mTempShowHiddenHandler = Handler() + private var mZoomListener: MyRecyclerView.MyZoomListener? = null + private var mSearchMenuItem: MenuItem? = null + private var mLastMediaFetcher: MediaFetcher? = null + private var mDirs = ArrayList() + + private var mStoredAnimateGifs = true + private var mStoredCropThumbnails = true + private var mStoredScrollHorizontally = true + private var mStoredTextColor = 0 + private var mStoredPrimaryColor = 0 + private var mStoredStyleString = "" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + appLaunched(BuildConfig.APPLICATION_ID) + + if (savedInstanceState == null) { + config.temporarilyShowHidden = false + config.tempSkipDeleteConfirmation = false + removeTempFolder() + checkRecycleBinItems() + startNewPhotoFetcher() + } + + mIsPickImageIntent = isPickImageIntent(intent) + mIsPickVideoIntent = isPickVideoIntent(intent) + mIsGetImageContentIntent = isGetImageContentIntent(intent) + mIsGetVideoContentIntent = isGetVideoContentIntent(intent) + mIsGetAnyContentIntent = isGetAnyContentIntent(intent) + mIsSetWallpaperIntent = isSetWallpaperIntent(intent) + mAllowPickingMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) + mIsThirdPartyIntent = mIsPickImageIntent || mIsPickVideoIntent || mIsGetImageContentIntent || mIsGetVideoContentIntent || + mIsGetAnyContentIntent || mIsSetWallpaperIntent + + directories_refresh_layout.setOnRefreshListener { getDirectories() } + storeStateVariables() + checkWhatsNewDialog() + + mIsPasswordProtectionPending = config.isAppPasswordProtectionOn + setupLatestMediaId() + + if (!config.wereFavoritesPinned) { + config.addPinnedFolders(hashSetOf(FAVORITES)) + config.wereFavoritesPinned = true + } + + if (!config.wasRecycleBinPinned) { + config.addPinnedFolders(hashSetOf(RECYCLE_BIN)) + config.wasRecycleBinPinned = true + config.saveFolderGrouping(SHOW_ALL, GROUP_BY_DATE_TAKEN_DAILY or GROUP_DESCENDING) + } + + if (!config.wasSVGShowingHandled) { + config.wasSVGShowingHandled = true + if (config.filterMedia and TYPE_SVGS == 0) { + config.filterMedia += TYPE_SVGS + } + } + + if (!config.wasSortingByNumericValueAdded) { + config.wasSortingByNumericValueAdded = true + config.sorting = config.sorting or SORT_USE_NUMERIC_VALUE + } + + updateWidgets() + registerFileUpdateListener() + + directories_switch_searching.setOnClickListener { + launchSearchActivity() + } + + // just request the permission, tryLoadGallery will then trigger in onResume + handlePermission(PERMISSION_WRITE_STORAGE) { + if (!it) { + toast(R.string.no_storage_permissions) + finish() + } + } + } + + override fun onStart() { + super.onStart() + mTempShowHiddenHandler.removeCallbacksAndMessages(null) + } + + override fun onResume() { + super.onResume() + config.isThirdPartyIntent = false + mDateFormat = config.dateFormat + mTimeFormat = getTimeFormat() + + if (mStoredAnimateGifs != config.animateGifs) { + getRecyclerAdapter()?.updateAnimateGifs(config.animateGifs) + } + + if (mStoredCropThumbnails != config.cropThumbnails) { + getRecyclerAdapter()?.updateCropThumbnails(config.cropThumbnails) + } + + if (mStoredScrollHorizontally != config.scrollHorizontally) { + mLoadedInitialPhotos = false + directories_grid.adapter = null + getDirectories() + } + + if (mStoredTextColor != config.textColor) { + getRecyclerAdapter()?.updateTextColor(config.textColor) + } + + if (mStoredPrimaryColor != config.primaryColor) { + getRecyclerAdapter()?.updatePrimaryColor(config.primaryColor) + directories_vertical_fastscroller.updatePrimaryColor() + directories_horizontal_fastscroller.updatePrimaryColor() + } + + val styleString = "${config.folderStyle}${config.showFolderMediaCount}${config.limitFolderTitle}" + if (mStoredStyleString != styleString) { + setupAdapter(mDirs, forceRecreate = true) + } + + directories_horizontal_fastscroller.updateBubbleColors() + directories_vertical_fastscroller.updateBubbleColors() + directories_refresh_layout.isEnabled = config.enablePullToRefresh + + directories_empty_placeholder.setTextColor(config.textColor) + directories_empty_placeholder_2.setTextColor(getAdjustedPrimaryColor()) + directories_switch_searching.setTextColor(getAdjustedPrimaryColor()) + directories_switch_searching.underlineText() + + if (!mIsSearchOpen) { + invalidateOptionsMenu() + if (mIsPasswordProtectionPending && !mWasProtectionHandled) { + handleAppPasswordProtection { + mWasProtectionHandled = it + if (it) { + mIsPasswordProtectionPending = false + tryLoadGallery() + } else { + finish() + } + } + } else { + tryLoadGallery() + } + } + } + + override fun onPause() { + super.onPause() + directories_refresh_layout.isRefreshing = false + mIsGettingDirs = false + storeStateVariables() + mLastMediaHandler.removeCallbacksAndMessages(null) + } + + override fun onStop() { + super.onStop() + + if (config.temporarilyShowHidden || config.tempSkipDeleteConfirmation) { + mTempShowHiddenHandler.postDelayed({ + config.temporarilyShowHidden = false + config.tempSkipDeleteConfirmation = false + }, SHOW_TEMP_HIDDEN_DURATION) + } else { + mTempShowHiddenHandler.removeCallbacksAndMessages(null) + } + } + + override fun onDestroy() { + super.onDestroy() + if (!isChangingConfigurations) { + config.temporarilyShowHidden = false + config.tempSkipDeleteConfirmation = false + mTempShowHiddenHandler.removeCallbacksAndMessages(null) + removeTempFolder() + unregisterFileUpdateListener() + + if (!config.showAll) { + mLastMediaFetcher?.shouldStop = true + 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() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + if (mIsThirdPartyIntent) { + menuInflater.inflate(R.menu.menu_main_intent, menu) + } else { + menuInflater.inflate(R.menu.menu_main, menu) + val useBin = config.useRecycleBin + 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 + findItem(R.id.hide_the_recycle_bin).isVisible = useBin && config.showRecycleBinAtFolders + findItem(R.id.show_the_recycle_bin).isVisible = useBin && !config.showRecycleBinAtFolders + findItem(R.id.set_as_default_folder).isVisible = !config.defaultFolder.isEmpty() + setupSearch(this) + } + } + + menu.findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden + menu.findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden + + updateMenuItemColors(menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.sort -> showSortingDialog() + R.id.filter -> showFilterMediaDialog() + R.id.open_camera -> launchCamera() + R.id.show_all -> showAllMedia() + R.id.change_view_type -> changeViewType() + R.id.temporarily_show_hidden -> tryToggleTemporarilyShowHidden() + R.id.stop_showing_hidden -> tryToggleTemporarilyShowHidden() + R.id.create_new_folder -> createNewFolder() + R.id.show_the_recycle_bin -> toggleRecycleBin(true) + R.id.hide_the_recycle_bin -> toggleRecycleBin(false) + R.id.increase_column_count -> increaseColumnCount() + R.id.reduce_column_count -> reduceColumnCount() + R.id.set_as_default_folder -> setAsDefaultFolder() + R.id.settings -> launchSettings() + R.id.about -> launchAbout() + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putBoolean(WAS_PROTECTION_HANDLED, mWasProtectionHandled) + } + + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + super.onRestoreInstanceState(savedInstanceState) + mWasProtectionHandled = savedInstanceState.getBoolean(WAS_PROTECTION_HANDLED, false) + } + + private fun getRecyclerAdapter() = directories_grid.adapter as? DirectoryAdapter + + private fun storeStateVariables() { + config.apply { + mStoredAnimateGifs = animateGifs + mStoredCropThumbnails = cropThumbnails + mStoredScrollHorizontally = scrollHorizontally + mStoredTextColor = textColor + mStoredPrimaryColor = primaryColor + mStoredStyleString = "$folderStyle$showFolderMediaCount$limitFolderTitle" + } + } + + 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 { + directories_switch_searching.beVisible() + 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) { + directories_switch_searching.beGone() + mIsSearchOpen = false + directories_refresh_layout.isEnabled = config.enablePullToRefresh + setupAdapter(mDirs, "") + } + return true + } + }) + } + + private fun startNewPhotoFetcher() { + if (isNougatPlus()) { + val photoFetcher = NewPhotoFetcher() + if (!photoFetcher.isScheduled(applicationContext)) { + photoFetcher.scheduleJob(applicationContext) + } + } + } + + private fun removeTempFolder() { + if (config.tempFolderPath.isNotEmpty()) { + val newFolder = File(config.tempFolderPath) + if (getDoesFilePathExist(newFolder.absolutePath) && newFolder.isDirectory) { + if (newFolder.list()?.isEmpty() == true && newFolder.getProperSize(true) == 0L && newFolder.getFileCount(true) == 0) { + toast(String.format(getString(R.string.deleting_folder), config.tempFolderPath), Toast.LENGTH_LONG) + tryDeleteFileDirItem(newFolder.toFileDirItem(applicationContext), true, true) + } + } + config.tempFolderPath = "" + } + } + + private fun checkOTGPath() { + ensureBackgroundThread { + if (!config.wasOTGHandled && hasPermission(PERMISSION_WRITE_STORAGE) && hasOTGConnected() && config.OTGPath.isEmpty()) { + getStorageDirectories().firstOrNull { it.trimEnd('/') != internalStoragePath && it.trimEnd('/') != sdCardPath }?.apply { + config.wasOTGHandled = true + val otgPath = trimEnd('/') + config.OTGPath = otgPath + config.addIncludedFolder(otgPath) + } + } + } + } + + private fun checkDefaultSpamFolders() { + if (!config.spamFoldersChecked) { + val spamFolders = arrayListOf( + "/storage/emulated/0/Android/data/com.facebook.orca/files/stickers" + ) + + val OTGPath = config.OTGPath + spamFolders.forEach { + if (getDoesFilePathExist(it, OTGPath)) { + config.addExcludedFolder(it) + } + } + config.spamFoldersChecked = true + } + } + + private fun tryLoadGallery() { + handlePermission(PERMISSION_WRITE_STORAGE) { + if (it) { + if (!mWasDefaultFolderChecked) { + openDefaultFolder() + mWasDefaultFolderChecked = true + } + + if (!config.wasUpgradedFromFreeShown && isPackageInstalled("com.simplemobiletools.gallery")) { + ConfirmationDialog(this, "", R.string.upgraded_from_free, R.string.ok, 0) {} + config.wasUpgradedFromFreeShown = true + } + + checkOTGPath() + checkDefaultSpamFolders() + + if (config.showAll) { + showAllMedia() + } else { + getDirectories() + } + + setupLayoutManager() + } else { + toast(R.string.no_storage_permissions) + finish() + } + } + } + + private fun getDirectories() { + if (mIsGettingDirs) { + return + } + + mShouldStopFetching = true + mIsGettingDirs = true + val getImagesOnly = mIsPickImageIntent || mIsGetImageContentIntent + val getVideosOnly = mIsPickVideoIntent || mIsGetVideoContentIntent + + getCachedDirectories(getVideosOnly, getImagesOnly) { + gotDirectories(addTempFolderIfNeeded(it)) + } + } + + private fun launchSearchActivity() { + Intent(this, SearchActivity::class.java).apply { + startActivity(this) + } + } + + private fun showSortingDialog() { + ChangeSortingDialog(this, true, false) { + directories_grid.adapter = null + if (config.directorySorting and SORT_BY_DATE_MODIFIED != 0 || config.directorySorting and SORT_BY_DATE_TAKEN != 0) { + getDirectories() + } else { + ensureBackgroundThread { + gotDirectories(getCurrentlyDisplayedDirs()) + } + } + } + } + + private fun showFilterMediaDialog() { + FilterMediaDialog(this) { + mShouldStopFetching = true + directories_refresh_layout.isRefreshing = true + directories_grid.adapter = null + getDirectories() + } + } + + private fun showAllMedia() { + config.showAll = true + Intent(this, MediaActivity::class.java).apply { + putExtra(DIRECTORY, "") + + if (mIsThirdPartyIntent) { + handleMediaIntent(this) + } else { + startActivity(this) + finish() + } + } + } + + private fun changeViewType() { + ChangeViewTypeDialog(this, true) { + invalidateOptionsMenu() + setupLayoutManager() + directories_grid.adapter = null + setupAdapter(getRecyclerAdapter()?.dirs ?: mDirs) + } + } + + private fun tryToggleTemporarilyShowHidden() { + if (config.temporarilyShowHidden) { + toggleTemporarilyShowHidden(false) + } else { + handleHiddenFolderPasswordProtection { + toggleTemporarilyShowHidden(true) + } + } + } + + private fun toggleTemporarilyShowHidden(show: Boolean) { + mLoadedInitialPhotos = false + config.temporarilyShowHidden = show + directories_grid.adapter = null + getDirectories() + invalidateOptionsMenu() + } + + override fun deleteFolders(folders: ArrayList) { + val fileDirItems = folders.asSequence().filter { it.isDirectory }.map { FileDirItem(it.absolutePath, it.name, true) }.toMutableList() as ArrayList + when { + fileDirItems.isEmpty() -> return + fileDirItems.size == 1 -> { + try { + toast(String.format(getString(R.string.deleting_folder), fileDirItems.first().name)) + } catch (e: Exception) { + showErrorToast(e) + } + } + 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) + } + } + + val itemsToDelete = ArrayList() + val filter = config.filterMedia + val showHidden = config.shouldShowHidden + fileDirItems.filter { it.isDirectory }.forEach { + val files = File(it.path).listFiles() + 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(itemsToDelete) { it.toFileDirItem(applicationContext) } + } + + if (config.useRecycleBin) { + val pathsToDelete = ArrayList() + itemsToDelete.mapTo(pathsToDelete) { it.path } + + movePathsInRecycleBin(pathsToDelete) { + if (it) { + deleteFilteredFileDirItems(itemsToDelete, folders) + } else { + toast(R.string.unknown_error_occurred) + } + } + } else { + deleteFilteredFileDirItems(itemsToDelete, folders) + } + } + + private fun deleteFilteredFileDirItems(fileDirItems: ArrayList, folders: ArrayList) { + val OTGPath = config.OTGPath + deleteFiles(fileDirItems) { + runOnUiThread { + refreshItems() + } + + ensureBackgroundThread { + folders.filter { !getDoesFilePathExist(it.absolutePath, OTGPath) }.forEach { + directoryDao.deleteDirPath(it.absolutePath) + } + + if (config.deleteEmptyFolders) { + folders.filter { !it.absolutePath.isDownloadsFolder() && it.isDirectory && it.toFileDirItem(this).getProperFileCount(this, true) == 0 }.forEach { + tryDeleteFileDirItem(it.toFileDirItem(this), true, true) + } + } + } + } + } + + private fun setupLayoutManager() { + if (config.viewTypeFolders == VIEW_TYPE_GRID) { + setupGridLayoutManager() + } else { + setupListLayoutManager() + } + } + + private fun setupGridLayoutManager() { + val layoutManager = directories_grid.layoutManager as MyGridLayoutManager + (directories_grid.layoutParams as RelativeLayout.LayoutParams).apply { + topMargin = 0 + bottomMargin = 0 + } + + if (config.scrollHorizontally) { + layoutManager.orientation = RecyclerView.HORIZONTAL + directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT) + } else { + layoutManager.orientation = RecyclerView.VERTICAL + directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + layoutManager.spanCount = config.dirColumnCnt + } + + private fun setupListLayoutManager() { + val layoutManager = directories_grid.layoutManager as MyGridLayoutManager + layoutManager.spanCount = 1 + layoutManager.orientation = RecyclerView.VERTICAL + directories_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + val smallMargin = resources.getDimension(R.dimen.small_margin).toInt() + (directories_grid.layoutParams as RelativeLayout.LayoutParams).apply { + topMargin = smallMargin + bottomMargin = smallMargin + } + + mZoomListener = null + } + + private fun measureRecyclerViewContent(directories: ArrayList) { + directories_grid.onGlobalLayout { + if (config.scrollHorizontally) { + calculateContentWidth(directories) + } else { + calculateContentHeight(directories) + } + } + } + + private fun calculateContentWidth(directories: ArrayList) { + val layoutManager = directories_grid.layoutManager as MyGridLayoutManager + + val fullWidth = if (config.folderStyle == FOLDER_STYLE_SQUARE) { + val thumbnailWidth = layoutManager.getChildAt(0)?.width ?: 0 + ((directories.size - 1) / layoutManager.spanCount + 1) * thumbnailWidth + } else { + val thumbnailWidth = (layoutManager.getChildAt(0)?.width ?: 0) + resources.getDimension(R.dimen.medium_margin).toInt() * 2 + val columnCount = (directories.size - 1) / layoutManager.spanCount + 1 + columnCount * thumbnailWidth + } + + directories_horizontal_fastscroller.setContentWidth(fullWidth) + directories_horizontal_fastscroller.setScrollToX(directories_grid.computeHorizontalScrollOffset()) + } + + private fun calculateContentHeight(directories: ArrayList) { + val layoutManager = directories_grid.layoutManager as MyGridLayoutManager + + val fullHeight = if (config.folderStyle == FOLDER_STYLE_SQUARE) { + val thumbnailHeight = layoutManager.getChildAt(0)?.height ?: 0 + ((directories.size - 1) / layoutManager.spanCount + 1) * thumbnailHeight + } else { + var thumbnailHeight = (layoutManager.getChildAt(0)?.height ?: 0) + if (config.viewTypeFolders == VIEW_TYPE_GRID) { + thumbnailHeight += resources.getDimension(R.dimen.medium_margin).toInt() * 2 + } + + val rowCount = (directories.size - 1) / layoutManager.spanCount + 1 + rowCount * thumbnailHeight + } + + directories_vertical_fastscroller.setContentHeight(fullHeight) + directories_vertical_fastscroller.setScrollToY(directories_grid.computeVerticalScrollOffset()) + } + + private fun initZoomListener() { + if (config.viewTypeFolders == VIEW_TYPE_GRID) { + val layoutManager = directories_grid.layoutManager as MyGridLayoutManager + mZoomListener = object : MyRecyclerView.MyZoomListener { + override fun zoomIn() { + if (layoutManager.spanCount > 1) { + reduceColumnCount() + getRecyclerAdapter()?.finishActMode() + } + } + + override fun zoomOut() { + if (layoutManager.spanCount < MAX_COLUMN_COUNT) { + increaseColumnCount() + getRecyclerAdapter()?.finishActMode() + } + } + } + } else { + mZoomListener = null + } + } + + private fun toggleRecycleBin(show: Boolean) { + config.showRecycleBinAtFolders = show + invalidateOptionsMenu() + ensureBackgroundThread { + var dirs = getCurrentlyDisplayedDirs() + if (!show) { + dirs = dirs.filter { it.path != RECYCLE_BIN } as ArrayList + } + gotDirectories(dirs) + } + } + + private fun createNewFolder() { + FilePickerDialog(this, internalStoragePath, false, config.shouldShowHidden, false, true) { + CreateNewFolderDialog(this, it) { + config.tempFolderPath = it + ensureBackgroundThread { + gotDirectories(addTempFolderIfNeeded(getCurrentlyDisplayedDirs())) + } + } + } + } + + private fun increaseColumnCount() { + config.dirColumnCnt = ++(directories_grid.layoutManager as MyGridLayoutManager).spanCount + columnCountChanged() + } + + private fun reduceColumnCount() { + config.dirColumnCnt = --(directories_grid.layoutManager as MyGridLayoutManager).spanCount + columnCountChanged() + } + + private fun columnCountChanged() { + invalidateOptionsMenu() + getRecyclerAdapter()?.apply { + notifyItemRangeChanged(0, dirs.size) + measureRecyclerViewContent(dirs) + } + } + + private fun isPickImageIntent(intent: Intent) = isPickIntent(intent) && (hasImageContentData(intent) || isImageType(intent)) + + private fun isPickVideoIntent(intent: Intent) = isPickIntent(intent) && (hasVideoContentData(intent) || isVideoType(intent)) + + private fun isPickIntent(intent: Intent) = intent.action == Intent.ACTION_PICK + + private fun isGetContentIntent(intent: Intent) = intent.action == Intent.ACTION_GET_CONTENT && intent.type != null + + private fun isGetImageContentIntent(intent: Intent) = isGetContentIntent(intent) && + (intent.type!!.startsWith("image/") || intent.type == Images.Media.CONTENT_TYPE) + + private fun isGetVideoContentIntent(intent: Intent) = isGetContentIntent(intent) && + (intent.type!!.startsWith("video/") || intent.type == Video.Media.CONTENT_TYPE) + + private fun isGetAnyContentIntent(intent: Intent) = isGetContentIntent(intent) && intent.type == "*/*" + + private fun isSetWallpaperIntent(intent: Intent?) = intent?.action == Intent.ACTION_SET_WALLPAPER + + private fun hasImageContentData(intent: Intent) = (intent.data == Images.Media.EXTERNAL_CONTENT_URI || + intent.data == Images.Media.INTERNAL_CONTENT_URI) + + private fun hasVideoContentData(intent: Intent) = (intent.data == Video.Media.EXTERNAL_CONTENT_URI || + intent.data == Video.Media.INTERNAL_CONTENT_URI) + + private fun isImageType(intent: Intent) = (intent.type?.startsWith("image/") == true || intent.type == Images.Media.CONTENT_TYPE) + + private fun isVideoType(intent: Intent) = (intent.type?.startsWith("video/") == true || intent.type == Video.Media.CONTENT_TYPE) + + override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { + 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 && intent.flags and Intent.FLAG_GRANT_WRITE_URI_PERMISSION != 0 -> { + 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) { + setResult(Activity.RESULT_OK) + finish() + } + } + super.onActivityResult(requestCode, resultCode, resultData) + } + + 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) + 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) { + val paths = resultData.extras!!.getStringArrayList(PICKED_PATHS) + val uris = paths!!.map { getFilePublicUri(File(it), BuildConfig.APPLICATION_ID) } as ArrayList + val clipData = ClipData("Attachment", arrayOf("image/*", "video/*"), ClipData.Item(uris.removeAt(0))) + + uris.forEach { + clipData.addItem(ClipData.Item(it)) + } + + resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + resultIntent.clipData = clipData + } + + private fun fillIntentPath(resultData: Intent, resultIntent: Intent) { + val data = resultData.data + val path = if (data.toString().startsWith("/")) data.toString() else data!!.path + val uri = getFilePublicUri(File(path!!), BuildConfig.APPLICATION_ID) + val type = path.getMimeType() + resultIntent.setDataAndTypeAndNormalize(uri, type) + resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + private fun itemClicked(path: String) { + handleLockedFolderOpening(path) { success -> + if (success) { + Intent(this, MediaActivity::class.java).apply { + putExtra(SKIP_AUTHENTICATION, true) + putExtra(DIRECTORY, path) + handleMediaIntent(this) + } + } + } + } + + private fun handleMediaIntent(intent: Intent) { + intent.apply { + if (mIsSetWallpaperIntent) { + putExtra(SET_WALLPAPER_INTENT, true) + startActivityForResult(this, PICK_WALLPAPER) + } else { + putExtra(GET_IMAGE_INTENT, mIsPickImageIntent || mIsGetImageContentIntent) + putExtra(GET_VIDEO_INTENT, mIsPickVideoIntent || mIsGetVideoContentIntent) + putExtra(GET_ANY_INTENT, mIsGetAnyContentIntent) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, mAllowPickingMultiple) + startActivityForResult(this, PICK_MEDIA) + } + } + } + + private fun gotDirectories(newDirs: ArrayList) { + 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('.')) { + newDirs.remove(favoritesFolder) + } + } + + val dirs = getSortedDirectories(newDirs) + if (config.groupDirectSubfolders) { + mDirs = dirs.clone() as ArrayList + } + + var isPlaceholderVisible = dirs.isEmpty() + + runOnUiThread { + checkPlaceholderVisibility(dirs) + + 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.clone() as ArrayList) + } + + // cached folders have been loaded, recheck folders one by one starting with the first displayed + mLastMediaFetcher?.shouldStop = true + mLastMediaFetcher = MediaFetcher(applicationContext) + val getImagesOnly = mIsPickImageIntent || mIsGetImageContentIntent + val getVideosOnly = mIsPickVideoIntent || mIsGetVideoContentIntent + val hiddenString = getString(R.string.hidden) + val albumCovers = config.parseAlbumCovers() + val includedFolders = config.includedFolders + val noMediaFolders = getNoMediaFoldersSync() + val tempFolderPath = config.tempFolderPath + val getProperFileSize = config.directorySorting and SORT_BY_SIZE != 0 + val favoritePaths = getFavoritePaths() + val dirPathsToRemove = ArrayList() + val lastModifieds = mLastMediaFetcher!!.getLastModifieds() + val dateTakens = mLastMediaFetcher!!.getDateTakens() + + try { + for (directory in dirs) { + if (mShouldStopFetching || isDestroyed || isFinishing) { + return + } + + val sorting = config.getFolderSorting(directory.path) + val grouping = config.getFolderGrouping(directory.path) + val getProperDateTaken = config.directorySorting and SORT_BY_DATE_TAKEN != 0 || + sorting and SORT_BY_DATE_TAKEN != 0 || + grouping and GROUP_BY_DATE_TAKEN_DAILY != 0 || + grouping and GROUP_BY_DATE_TAKEN_MONTHLY != 0 + + val getProperLastModified = config.directorySorting and SORT_BY_DATE_MODIFIED != 0 || + sorting and SORT_BY_DATE_MODIFIED != 0 || + grouping and GROUP_BY_LAST_MODIFIED_DAILY != 0 || + grouping and GROUP_BY_LAST_MODIFIED_MONTHLY != 0 + + val curMedia = mLastMediaFetcher!!.getFilesFrom(directory.path, getImagesOnly, getVideosOnly, getProperDateTaken, getProperLastModified, + getProperFileSize, favoritePaths, false, lastModifieds, dateTakens) + + val newDir = if (curMedia.isEmpty()) { + if (directory.path != tempFolderPath) { + dirPathsToRemove.add(directory.path) + } + directory + } else { + createDirectoryFromMedia(directory.path, curMedia, albumCovers, hiddenString, includedFolders, getProperFileSize, noMediaFolders) + } + + // we are looping through the already displayed folders looking for changes, do not do anything if nothing changed + if (directory.copy(subfoldersCount = 0, subfoldersMediaCount = 0) == newDir) { + continue + } + + directory.apply { + tmb = newDir.tmb + name = newDir.name + mediaCnt = newDir.mediaCnt + modified = newDir.modified + taken = newDir.taken + this@apply.size = newDir.size + types = newDir.types + sortValue = getDirectorySortingValue(curMedia, path, name, size) + } + + setupAdapter(dirs) + + // update directories and media files in the local db, delete invalid items. Intentionally creating a new thread + updateDBDirectory(directory) + if (!directory.isRecycleBin()) { + Thread { + try { + mediaDB.insertAll(curMedia) + } catch (ignored: Exception) { + } + }.start() + } + + if (!directory.isRecycleBin()) { + getCachedMedia(directory.path, getVideosOnly, getImagesOnly) { + val mediaToDelete = ArrayList() + it.forEach { + if (!curMedia.contains(it)) { + val medium = it as? Medium + val path = medium?.path + if (path != null) { + mediaToDelete.add(medium) + } + } + } + mediaDB.deleteMedia(*mediaToDelete.toTypedArray()) + } + } + } + + if (dirPathsToRemove.isNotEmpty()) { + val dirsToRemove = dirs.filter { dirPathsToRemove.contains(it.path) } + dirsToRemove.forEach { + directoryDao.deleteDirPath(it.path) + } + dirs.removeAll(dirsToRemove) + setupAdapter(dirs) + } + } catch (ignored: Exception) { + } + + val foldersToScan = mLastMediaFetcher!!.getFoldersToScan() + foldersToScan.add(FAVORITES) + if (config.showRecycleBinAtFolders) { + foldersToScan.add(RECYCLE_BIN) + } else { + foldersToScan.remove(RECYCLE_BIN) + } + + dirs.forEach { + foldersToScan.remove(it.path) + } + + // check the remaining folders which were not cached at all yet + for (folder in foldersToScan) { + if (mShouldStopFetching || isDestroyed || isFinishing) { + return + } + + val sorting = config.getFolderSorting(folder) + val grouping = config.getFolderGrouping(folder) + val getProperDateTaken = config.directorySorting and SORT_BY_DATE_TAKEN != 0 || + sorting and SORT_BY_DATE_TAKEN != 0 || + grouping and GROUP_BY_DATE_TAKEN_DAILY != 0 || + grouping and GROUP_BY_DATE_TAKEN_MONTHLY != 0 + + val getProperLastModified = config.directorySorting and SORT_BY_DATE_MODIFIED != 0 || + sorting and SORT_BY_DATE_MODIFIED != 0 || + grouping and GROUP_BY_LAST_MODIFIED_DAILY != 0 || + grouping and GROUP_BY_LAST_MODIFIED_MONTHLY != 0 + + val newMedia = mLastMediaFetcher!!.getFilesFrom(folder, getImagesOnly, getVideosOnly, getProperDateTaken, getProperLastModified, + getProperFileSize, favoritePaths, false, lastModifieds, dateTakens) + + if (newMedia.isEmpty()) { + continue + } + + if (isPlaceholderVisible) { + isPlaceholderVisible = false + runOnUiThread { + directories_empty_placeholder.beGone() + directories_empty_placeholder_2.beGone() + directories_grid.beVisible() + } + } + + val newDir = createDirectoryFromMedia(folder, newMedia, albumCovers, hiddenString, includedFolders, getProperFileSize, noMediaFolders) + dirs.add(newDir) + setupAdapter(dirs) + + // make sure to create a new thread for these operations, dont just use the common bg thread + Thread { + try { + directoryDao.insert(newDir) + if (folder != RECYCLE_BIN) { + mediaDB.insertAll(newMedia) + } + } catch (ignored: Exception) { + } + }.start() + } + + mLoadedInitialPhotos = true + checkLastMediaChanged() + + runOnUiThread { + directories_refresh_layout.isRefreshing = false + checkPlaceholderVisibility(dirs) + } + + checkInvalidDirectories(dirs) + if (mDirs.size > 50) { + excludeSpamFolders() + } + + val excludedFolders = config.excludedFolders + val everShownFolders = config.everShownFolders.toMutableSet() as HashSet + + // do not add excluded folders and their subfolders at everShownFolders + dirs.filter { dir -> + if (excludedFolders.any { dir.path.startsWith(it) }) { + return@filter false + } + return@filter true + }.mapTo(everShownFolders) { it.path } + + try { + // scan the internal storage from time to time for new folders + if (config.appRunCount == 1 || config.appRunCount % 30 == 0) { + everShownFolders.addAll(getFoldersWithMedia(config.internalStoragePath)) + } + + // catch some extreme exceptions like too many everShownFolders for storing, shouldnt really happen + config.everShownFolders = everShownFolders + } catch (e: Exception) { + config.everShownFolders = HashSet() + } + + mDirs = dirs.clone() as ArrayList + } + + private fun setAsDefaultFolder() { + config.defaultFolder = "" + invalidateOptionsMenu() + } + + private fun openDefaultFolder() { + if (config.defaultFolder.isEmpty()) { + return + } + + val defaultDir = File(config.defaultFolder) + + if ((!defaultDir.exists() || !defaultDir.isDirectory) && (config.defaultFolder != RECYCLE_BIN && config.defaultFolder != FAVORITES)) { + config.defaultFolder = "" + return + } + + Intent(this, MediaActivity::class.java).apply { + putExtra(DIRECTORY, config.defaultFolder) + handleMediaIntent(this) + } + } + + private fun checkPlaceholderVisibility(dirs: ArrayList) { + directories_empty_placeholder.beVisibleIf(dirs.isEmpty() && mLoadedInitialPhotos) + directories_empty_placeholder_2.beVisibleIf(dirs.isEmpty() && mLoadedInitialPhotos) + + if (mIsSearchOpen) { + directories_empty_placeholder.text = getString(R.string.no_items_found) + directories_empty_placeholder_2.beGone() + } else if (dirs.isEmpty() && config.filterMedia == getDefaultFileFilter()) { + directories_empty_placeholder.text = getString(R.string.no_media_add_included) + directories_empty_placeholder_2.text = getString(R.string.add_folder) + + directories_empty_placeholder_2.setOnClickListener { + showAddIncludedFolderDialog { + refreshItems() + } + } + } else { + directories_empty_placeholder.text = getString(R.string.no_media_with_filters) + directories_empty_placeholder_2.text = getString(R.string.change_filters_underlined) + + directories_empty_placeholder_2.setOnClickListener { + showFilterMediaDialog() + } + } + + directories_empty_placeholder_2.underlineText() + directories_grid.beVisibleIf(directories_empty_placeholder.isGone()) + } + + private fun setupAdapter(dirs: ArrayList, textToSearch: String = "", forceRecreate: Boolean = false) { + val currAdapter = directories_grid.adapter + val distinctDirs = dirs.distinctBy { it.path.getDistinctPath() }.toMutableList() as ArrayList + val sortedDirs = getSortedDirectories(distinctDirs) + var dirsToShow = getDirsToShow(sortedDirs, mDirs, mCurrentPathPrefix).clone() as ArrayList + + if (currAdapter == null || forceRecreate) { + initZoomListener() + val fastscroller = if (config.scrollHorizontally) directories_horizontal_fastscroller else directories_vertical_fastscroller + 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) + runOnUiThread { + directories_grid.adapter = this + setupScrollDirection() + } + } + measureRecyclerViewContent(dirsToShow) + } else { + runOnUiThread { + if (textToSearch.isNotEmpty()) { + dirsToShow = dirsToShow.filter { it.name.contains(textToSearch, true) }.sortedBy { !it.name.startsWith(textToSearch, true) }.toMutableList() as ArrayList + } + checkPlaceholderVisibility(dirsToShow) + + (directories_grid.adapter as? DirectoryAdapter)?.updateDirs(dirsToShow) + measureRecyclerViewContent(dirsToShow) + } + } + + // 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() { + val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFolders == VIEW_TYPE_GRID + directories_vertical_fastscroller.isHorizontal = false + directories_vertical_fastscroller.beGoneIf(allowHorizontalScroll) + + directories_horizontal_fastscroller.isHorizontal = true + directories_horizontal_fastscroller.beVisibleIf(allowHorizontalScroll) + + if (allowHorizontalScroll) { + directories_horizontal_fastscroller.setViews(directories_grid, directories_refresh_layout) { + directories_horizontal_fastscroller.updateBubbleText(getBubbleTextItem(it)) + } + } else { + directories_vertical_fastscroller.setViews(directories_grid, directories_refresh_layout) { + directories_vertical_fastscroller.updateBubbleText(getBubbleTextItem(it)) + } + } + } + + private fun checkInvalidDirectories(dirs: ArrayList) { + val invalidDirs = ArrayList() + val OTGPath = config.OTGPath + dirs.filter { !it.areFavorites() && !it.isRecycleBin() }.forEach { + if (!getDoesFilePathExist(it.path, OTGPath)) { + invalidDirs.add(it) + } else if (it.path != config.tempFolderPath) { + val children = if (isPathOnOTG(it.path)) getOTGFolderChildrenNames(it.path) else File(it.path).list()?.asList() + val hasMediaFile = children?.any { + it != null && (it.isMediaFile() || (it.startsWith("img_", true) && File(it).isDirectory)) + } ?: false + + if (!hasMediaFile) { + invalidDirs.add(it) + } + } + } + + if (getFavoritePaths().isEmpty()) { + val favoritesFolder = dirs.firstOrNull { it.areFavorites() } + if (favoritesFolder != null) { + invalidDirs.add(favoritesFolder) + } + } + + if (config.useRecycleBin) { + try { + val binFolder = dirs.firstOrNull { it.path == RECYCLE_BIN } + if (binFolder != null && mediaDB.getDeletedMedia().isEmpty()) { + invalidDirs.add(binFolder) + } + } catch (ignored: Exception) { + } + } + + if (invalidDirs.isNotEmpty()) { + dirs.removeAll(invalidDirs) + setupAdapter(dirs) + invalidDirs.forEach { + try { + directoryDao.deleteDirPath(it.path) + } catch (ignored: Exception) { + } + } + } + } + + private fun getCurrentlyDisplayedDirs() = getRecyclerAdapter()?.dirs ?: ArrayList() + + private fun getBubbleTextItem(index: Int) = getRecyclerAdapter()?.dirs?.getOrNull(index)?.getBubbleText(config.directorySorting, this, mDateFormat, mTimeFormat) + ?: "" + + private fun setupLatestMediaId() { + ensureBackgroundThread { + if (hasPermission(PERMISSION_READ_STORAGE)) { + mLatestMediaId = getLatestMediaId() + mLatestMediaDateId = getLatestMediaByDateId() + } + } + } + + private fun checkLastMediaChanged() { + if (isDestroyed) { + return + } + + mLastMediaHandler.postDelayed({ + ensureBackgroundThread { + val mediaId = getLatestMediaId() + val mediaDateId = getLatestMediaByDateId() + if (mLatestMediaId != mediaId || mLatestMediaDateId != mediaDateId) { + mLatestMediaId = mediaId + mLatestMediaDateId = mediaDateId + runOnUiThread { + getDirectories() + } + } else { + mLastMediaHandler.removeCallbacksAndMessages(null) + checkLastMediaChanged() + } + } + }, LAST_MEDIA_CHECK_PERIOD) + } + + private fun checkRecycleBinItems() { + if (config.useRecycleBin && config.lastBinCheck < System.currentTimeMillis() - DAY_SECONDS * 1000) { + config.lastBinCheck = System.currentTimeMillis() + Handler().postDelayed({ + ensureBackgroundThread { + try { + mediaDB.deleteOldRecycleBinItems(System.currentTimeMillis() - MONTH_MILLISECONDS) + } catch (e: Exception) { + } + } + }, 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() { + ensureBackgroundThread { + try { + val internalPath = internalStoragePath + val checkedPaths = ArrayList() + val oftenRepeatedPaths = ArrayList() + val paths = mDirs.map { it.path.removePrefix(internalPath) }.toMutableList() as ArrayList + 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) + val OTGPath = config.OTGPath + oftenRepeatedPaths.forEach { + val file = File("$internalPath/$it") + if (getDoesFilePathExist(file.absolutePath, OTGPath)) { + config.addExcludedFolder(file.absolutePath) + } + } + } catch (e: Exception) { + } + } + } + + private fun getFoldersWithMedia(path: String): HashSet { + val folders = HashSet() + try { + val files = File(path).listFiles() + if (files != null) { + files.sortBy { !it.isDirectory } + for (file in files) { + if (file.isDirectory && !file.startsWith("${config.internalStoragePath}/Android")) { + folders.addAll(getFoldersWithMedia(file.absolutePath)) + } else if (file.isFile && file.isMediaFile()) { + folders.add(file.parent ?: "") + break + } + } + } + } catch (ignored: Exception) { + } + + return folders + } + + override fun refreshItems() { + getDirectories() + } + + override fun recheckPinnedFolders() { + ensureBackgroundThread { + gotDirectories(movePinnedDirectoriesToFront(getCurrentlyDisplayedDirs())) + } + } + + override fun updateDirectories(directories: ArrayList) { + ensureBackgroundThread { + storeDirectoryItems(directories) + removeInvalidDBDirectories() + } + } + + private fun checkWhatsNewDialog() { + arrayListOf().apply { + 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)) + add(Release(258, R.string.release_258)) + add(Release(277, R.string.release_277)) + add(Release(295, R.string.release_295)) + add(Release(327, R.string.release_327)) + checkWhatsNew(this, BuildConfig.VERSION_CODE) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MediaActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MediaActivity.kt new file mode 100644 index 000000000..731ac3e6d --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MediaActivity.kt @@ -0,0 +1,1019 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.app.Activity +import android.app.SearchManager +import android.app.WallpaperManager +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.view.Menu +import android.view.MenuItem +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.RelativeLayout +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.CreateNewFolderDialog +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.commons.models.FileDirItem +import com.simplemobiletools.commons.views.MyGridLayoutManager +import com.simplemobiletools.commons.views.MyRecyclerView +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.ChangeGroupingDialog +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.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.activity_media.* +import java.io.File +import java.io.IOException +import java.util.* +import kotlin.collections.ArrayList + +class MediaActivity : SimpleActivity(), MediaOperationsListener { + private val LAST_MEDIA_CHECK_PERIOD = 3000L + + private var mPath = "" + private var mIsGetImageIntent = false + private var mIsGetVideoIntent = false + private var mIsGetAnyIntent = false + private var mIsGettingMedia = false + private var mAllowPickingMultiple = false + private var mShowAll = false + private var mLoadedInitialPhotos = false + private var mIsSearchOpen = false + private var mLastSearchedText = "" + private var mDateFormat = "" + private var mTimeFormat = "" + private var mLatestMediaId = 0L + private var mLatestMediaDateId = 0L + private var mLastMediaHandler = Handler() + private var mTempShowHiddenHandler = Handler() + private var mCurrAsyncTask: GetMediaAsynctask? = null + private var mZoomListener: MyRecyclerView.MyZoomListener? = null + private var mSearchMenuItem: MenuItem? = null + + private var mStoredAnimateGifs = true + private var mStoredCropThumbnails = true + private var mStoredScrollHorizontally = true + private var mStoredShowFileTypes = true + private var mStoredRoundedCorners = false + private var mStoredTextColor = 0 + private var mStoredPrimaryColor = 0 + private var mStoredThumbnailSpacing = 0 + + companion object { + var mMedia = ArrayList() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_media) + + intent.apply { + mIsGetImageIntent = getBooleanExtra(GET_IMAGE_INTENT, false) + mIsGetVideoIntent = getBooleanExtra(GET_VIDEO_INTENT, false) + mIsGetAnyIntent = getBooleanExtra(GET_ANY_INTENT, false) + mAllowPickingMultiple = getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) + } + + media_refresh_layout.setOnRefreshListener { getMedia() } + try { + mPath = intent.getStringExtra(DIRECTORY) + } catch (e: Exception) { + showErrorToast(e) + finish() + return + } + + storeStateVariables() + + if (mShowAll) { + supportActionBar?.setDisplayHomeAsUpEnabled(false) + registerFileUpdateListener() + } + + media_empty_text_placeholder_2.setOnClickListener { + showFilterMediaDialog() + } + + updateWidgets() + } + + override fun onStart() { + super.onStart() + mTempShowHiddenHandler.removeCallbacksAndMessages(null) + } + + override fun onResume() { + super.onResume() + mDateFormat = config.dateFormat + mTimeFormat = getTimeFormat() + + if (mStoredAnimateGifs != config.animateGifs) { + getMediaAdapter()?.updateAnimateGifs(config.animateGifs) + } + + if (mStoredCropThumbnails != config.cropThumbnails) { + getMediaAdapter()?.updateCropThumbnails(config.cropThumbnails) + } + + if (mStoredScrollHorizontally != config.scrollHorizontally) { + mLoadedInitialPhotos = false + media_grid.adapter = null + getMedia() + } + + if (mStoredShowFileTypes != config.showThumbnailFileTypes) { + getMediaAdapter()?.updateShowFileTypes(config.showThumbnailFileTypes) + } + + if (mStoredTextColor != config.textColor) { + getMediaAdapter()?.updateTextColor(config.textColor) + } + + if (mStoredPrimaryColor != config.primaryColor) { + getMediaAdapter()?.updatePrimaryColor(config.primaryColor) + media_horizontal_fastscroller.updatePrimaryColor() + media_vertical_fastscroller.updatePrimaryColor() + } + + if (mStoredThumbnailSpacing != config.thumbnailSpacing) { + media_grid.adapter = null + setupAdapter() + } + + if (mStoredRoundedCorners != config.fileRoundedCorners) { + media_grid.adapter = null + setupAdapter() + } + + media_horizontal_fastscroller.updateBubbleColors() + media_vertical_fastscroller.updateBubbleColors() + media_refresh_layout.isEnabled = config.enablePullToRefresh + media_empty_text_placeholder.setTextColor(config.textColor) + media_empty_text_placeholder_2.setTextColor(getAdjustedPrimaryColor()) + + if (!mIsSearchOpen) { + invalidateOptionsMenu() + } + + if (mMedia.isEmpty() || config.getFolderSorting(mPath) and SORT_BY_RANDOM == 0) { + if (shouldSkipAuthentication()) { + tryLoadGallery() + } else { + handleLockedFolderOpening(mPath) { success -> + if (success) { + tryLoadGallery() + } else { + finish() + } + } + } + } + } + + override fun onPause() { + super.onPause() + mIsGettingMedia = false + media_refresh_layout.isRefreshing = false + storeStateVariables() + mLastMediaHandler.removeCallbacksAndMessages(null) + + if (!mMedia.isEmpty()) { + mCurrAsyncTask?.stopFetching() + } + } + + override fun onStop() { + super.onStop() + + if (config.temporarilyShowHidden || config.tempSkipDeleteConfirmation) { + mTempShowHiddenHandler.postDelayed({ + config.temporarilyShowHidden = false + config.tempSkipDeleteConfirmation = false + }, SHOW_TEMP_HIDDEN_DURATION) + } else { + mTempShowHiddenHandler.removeCallbacksAndMessages(null) + } + } + + override fun onDestroy() { + super.onDestroy() + if (config.showAll && !isChangingConfigurations) { + config.temporarilyShowHidden = false + config.tempSkipDeleteConfirmation = false + unregisterFileUpdateListener() + GalleryDatabase.destroyInstance() + } + + mTempShowHiddenHandler.removeCallbacksAndMessages(null) + mMedia.clear() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_media, menu) + + val isDefaultFolder = !config.defaultFolder.isEmpty() && File(config.defaultFolder).compareTo(File(mPath)) == 0 + + menu.apply { + findItem(R.id.group).isVisible = !config.scrollHorizontally + + findItem(R.id.empty_recycle_bin).isVisible = mPath == RECYCLE_BIN + findItem(R.id.empty_disable_recycle_bin).isVisible = mPath == RECYCLE_BIN + findItem(R.id.restore_all_files).isVisible = mPath == RECYCLE_BIN + + findItem(R.id.folder_view).isVisible = mShowAll + findItem(R.id.open_camera).isVisible = mShowAll + findItem(R.id.about).isVisible = mShowAll + findItem(R.id.create_new_folder).isVisible = !mShowAll && mPath != RECYCLE_BIN && mPath != FAVORITES + + findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden + findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden + + findItem(R.id.set_as_default_folder).isVisible = !isDefaultFolder + findItem(R.id.unset_as_default_folder).isVisible = isDefaultFolder + + 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) + updateMenuItemColors(menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.sort -> showSortingDialog() + R.id.filter -> showFilterMediaDialog() + R.id.empty_recycle_bin -> emptyRecycleBin() + R.id.empty_disable_recycle_bin -> emptyAndDisableRecycleBin() + R.id.restore_all_files -> restoreAllFiles() + R.id.toggle_filename -> toggleFilenameVisibility() + R.id.open_camera -> launchCamera() + R.id.folder_view -> switchToFolderView() + R.id.change_view_type -> changeViewType() + R.id.group -> showGroupByDialog() + R.id.create_new_folder -> createNewFolder() + R.id.temporarily_show_hidden -> tryToggleTemporarilyShowHidden() + R.id.stop_showing_hidden -> tryToggleTemporarilyShowHidden() + R.id.increase_column_count -> increaseColumnCount() + R.id.reduce_column_count -> reduceColumnCount() + R.id.set_as_default_folder -> setAsDefaultFolder() + R.id.unset_as_default_folder -> unsetAsDefaultFolder() + R.id.slideshow -> startSlideshow() + R.id.settings -> launchSettings() + R.id.about -> launchAbout() + else -> return super.onOptionsItemSelected(item) + } + 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(SKIP_AUTHENTICATION, shouldSkipAuthentication()) + putExtra(PATH, item.path) + putExtra(SHOW_ALL, mShowAll) + putExtra(SLIDESHOW_START_ON_ENTER, true) + startActivity(this) + } + } + } + + private fun storeStateVariables() { + config.apply { + mStoredAnimateGifs = animateGifs + mStoredCropThumbnails = cropThumbnails + mStoredScrollHorizontally = scrollHorizontally + mStoredShowFileTypes = showThumbnailFileTypes + mStoredTextColor = textColor + mStoredPrimaryColor = primaryColor + mStoredThumbnailSpacing = thumbnailSpacing + mStoredRoundedCorners = fileRoundedCorners + mShowAll = showAll + } + } + + 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) { + mLastSearchedText = newText + searchQueryChanged(newText) + } + return true + } + }) + } + + MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, object : MenuItemCompat.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem?): Boolean { + mIsSearchOpen = true + media_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 + mLastSearchedText = "" + + media_refresh_layout.isEnabled = config.enablePullToRefresh + searchQueryChanged("") + } + return true + } + }) + } + + private fun searchQueryChanged(text: String) { + ensureBackgroundThread { + 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, mPath) + runOnUiThread { + if (grouped.isEmpty()) { + media_empty_text_placeholder.text = getString(R.string.no_items_found) + media_empty_text_placeholder.beVisible() + } else { + media_empty_text_placeholder.beGone() + } + + handleGridSpacing(grouped) + getMediaAdapter()?.updateMedia(grouped) + measureRecyclerViewContent(grouped) + } + } catch (ignored: Exception) { + } + } + } + + 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 == config.OTGPath -> getString(R.string.usb) + else -> getHumanizedFilename(mPath) + } + updateActionBarTitle(if (mShowAll) resources.getString(R.string.all_folders) else dirName) + getMedia() + setupLayoutManager() + } else { + toast(R.string.no_storage_permissions) + finish() + } + } + } + + private fun getMediaAdapter() = media_grid.adapter as? MediaAdapter + + private fun setupAdapter() { + if (!mShowAll && isDirEmpty()) { + return + } + + val currAdapter = media_grid.adapter + if (currAdapter == null) { + initZoomListener() + val fastscroller = if (config.scrollHorizontally) media_horizontal_fastscroller else media_vertical_fastscroller + MediaAdapter(this, mMedia.clone() as ArrayList, this, mIsGetImageIntent || mIsGetVideoIntent || mIsGetAnyIntent, + mAllowPickingMultiple, mPath, media_grid, fastscroller) { + if (it is Medium && !isFinishing) { + itemClicked(it.path) + } + }.apply { + setupZoomListener(mZoomListener) + media_grid.adapter = this + } + setupLayoutManager() + handleGridSpacing() + measureRecyclerViewContent(mMedia) + } else if (mLastSearchedText.isEmpty()) { + (currAdapter as MediaAdapter).updateMedia(mMedia) + handleGridSpacing() + measureRecyclerViewContent(mMedia) + } else { + searchQueryChanged(mLastSearchedText) + } + + setupScrollDirection() + } + + private fun setupScrollDirection() { + 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) + + media_horizontal_fastscroller.isHorizontal = true + media_horizontal_fastscroller.beVisibleIf(allowHorizontalScroll) + + val sorting = config.getFolderSorting(if (mShowAll) SHOW_ALL else mPath) + if (allowHorizontalScroll) { + media_horizontal_fastscroller.setViews(media_grid, media_refresh_layout) { + media_horizontal_fastscroller.updateBubbleText(getBubbleTextItem(it, sorting)) + } + } else { + media_vertical_fastscroller.setViews(media_grid, media_refresh_layout) { + media_vertical_fastscroller.updateBubbleText(getBubbleTextItem(it, sorting)) + } + } + } + + private fun getBubbleTextItem(index: Int, sorting: Int): String { + var realIndex = index + val mediaAdapter = getMediaAdapter() + if (mediaAdapter?.isASectionTitle(index) == true) { + realIndex++ + } + return mediaAdapter?.getItemBubbleText(realIndex, sorting, mDateFormat, mTimeFormat) ?: "" + } + + private fun checkLastMediaChanged() { + if (isDestroyed || config.getFolderSorting(mPath) and SORT_BY_RANDOM != 0) { + return + } + + mLastMediaHandler.removeCallbacksAndMessages(null) + mLastMediaHandler.postDelayed({ + ensureBackgroundThread { + val mediaId = getLatestMediaId() + val mediaDateId = getLatestMediaByDateId() + if (mLatestMediaId != mediaId || mLatestMediaDateId != mediaDateId) { + mLatestMediaId = mediaId + mLatestMediaDateId = mediaDateId + runOnUiThread { + getMedia() + } + } else { + checkLastMediaChanged() + } + } + }, LAST_MEDIA_CHECK_PERIOD) + } + + private fun showSortingDialog() { + ChangeSortingDialog(this, false, true, mPath) { + mLoadedInitialPhotos = false + media_grid.adapter = null + getMedia() + } + } + + private fun showFilterMediaDialog() { + FilterMediaDialog(this) { + mLoadedInitialPhotos = false + media_refresh_layout.isRefreshing = true + media_grid.adapter = null + getMedia() + } + } + + private fun emptyRecycleBin() { + showRecycleBinEmptyingDialog { + emptyTheRecycleBin { + finish() + } + } + } + + private fun emptyAndDisableRecycleBin() { + showRecycleBinEmptyingDialog { + emptyAndDisableTheRecycleBin { + finish() + } + } + } + + private fun restoreAllFiles() { + val paths = mMedia.filter { it is Medium }.map { (it as Medium).path } as ArrayList + restoreRecycleBinPaths(paths) { + ensureBackgroundThread { + directoryDao.deleteDirPath(RECYCLE_BIN) + } + finish() + } + } + + private fun toggleFilenameVisibility() { + config.displayFileNames = !config.displayFileNames + getMediaAdapter()?.updateDisplayFilenames(config.displayFileNames) + } + + private fun switchToFolderView() { + config.showAll = false + startActivity(Intent(this, MainActivity::class.java)) + finish() + } + + private fun changeViewType() { + ChangeViewTypeDialog(this, false, mPath) { + invalidateOptionsMenu() + setupLayoutManager() + media_grid.adapter = null + setupAdapter() + } + } + + private fun showGroupByDialog() { + ChangeGroupingDialog(this, mPath) { + mLoadedInitialPhotos = false + media_grid.adapter = null + getMedia() + } + } + + private fun deleteDirectoryIfEmpty() { + val fileDirItem = FileDirItem(mPath, mPath.getFilenameFromPath(), true) + if (config.deleteEmptyFolders && !fileDirItem.isDownloadsFolder() && fileDirItem.isDirectory && fileDirItem.getProperFileCount(this, true) == 0) { + tryDeleteFileDirItem(fileDirItem, true, true) + } + } + + private fun getMedia() { + if (mIsGettingMedia) { + return + } + + mIsGettingMedia = true + if (mLoadedInitialPhotos) { + startAsyncTask() + } else { + getCachedMedia(mPath, mIsGetVideoIntent, mIsGetImageIntent) { + if (it.isEmpty()) { + runOnUiThread { + media_refresh_layout.isRefreshing = true + } + } else { + gotMedia(it, true) + } + startAsyncTask() + } + } + + mLoadedInitialPhotos = true + } + + private fun startAsyncTask() { + mCurrAsyncTask?.stopFetching() + mCurrAsyncTask = GetMediaAsynctask(applicationContext, mPath, mIsGetImageIntent, mIsGetVideoIntent, mShowAll) { + ensureBackgroundThread { + val oldMedia = mMedia.clone() as ArrayList + val newMedia = it + try { + gotMedia(newMedia, false) + oldMedia.filter { !newMedia.contains(it) }.mapNotNull { it as? Medium }.filter { !getDoesFilePathExist(it.path) }.forEach { + mediaDB.deleteMediumPath(it.path) + } + } catch (e: Exception) { + } + } + } + + mCurrAsyncTask!!.execute() + } + + private fun isDirEmpty(): Boolean { + return if (mMedia.size <= 0 && config.filterMedia > 0) { + if (mPath != FAVORITES && mPath != RECYCLE_BIN) { + deleteDirectoryIfEmpty() + deleteDBDirectory() + } + + if (mPath == FAVORITES) { + ensureBackgroundThread { + directoryDao.deleteDirPath(FAVORITES) + } + } + + finish() + true + } else { + false + } + } + + private fun deleteDBDirectory() { + ensureBackgroundThread { + try { + directoryDao.deleteDirPath(mPath) + } catch (ignored: Exception) { + } + } + } + + private fun createNewFolder() { + CreateNewFolderDialog(this, mPath) { + config.tempFolderPath = it + } + } + + private fun tryToggleTemporarilyShowHidden() { + if (config.temporarilyShowHidden) { + toggleTemporarilyShowHidden(false) + } else { + handleHiddenFolderPasswordProtection { + toggleTemporarilyShowHidden(true) + } + } + } + + private fun toggleTemporarilyShowHidden(show: Boolean) { + mLoadedInitialPhotos = false + config.temporarilyShowHidden = show + getMedia() + invalidateOptionsMenu() + } + + private fun setupLayoutManager() { + val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath) + if (viewType == VIEW_TYPE_GRID) { + setupGridLayoutManager() + } else { + setupListLayoutManager() + } + } + + private fun setupGridLayoutManager() { + val layoutManager = media_grid.layoutManager as MyGridLayoutManager + (media_grid.layoutParams as RelativeLayout.LayoutParams).apply { + topMargin = 0 + bottomMargin = 0 + } + + if (config.scrollHorizontally) { + layoutManager.orientation = RecyclerView.HORIZONTAL + media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT) + } else { + layoutManager.orientation = RecyclerView.VERTICAL + media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + layoutManager.spanCount = config.mediaColumnCnt + val adapter = getMediaAdapter() + layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (adapter?.isASectionTitle(position) == true) { + layoutManager.spanCount + } else { + 1 + } + } + } + } + + private fun measureRecyclerViewContent(media: ArrayList) { + media_grid.onGlobalLayout { + if (config.scrollHorizontally) { + calculateContentWidth(media) + } else { + calculateContentHeight(media) + } + } + } + + private fun calculateContentWidth(media: ArrayList) { + val layoutManager = media_grid.layoutManager as MyGridLayoutManager + val thumbnailWidth = layoutManager.getChildAt(0)?.width ?: 0 + val spacing = config.thumbnailSpacing + val fullWidth = ((media.size - 1) / layoutManager.spanCount + 1) * (thumbnailWidth + spacing) - spacing + media_horizontal_fastscroller.setContentWidth(fullWidth) + media_horizontal_fastscroller.setScrollToX(media_grid.computeHorizontalScrollOffset()) + } + + private fun calculateContentHeight(media: ArrayList) { + val layoutManager = media_grid.layoutManager as MyGridLayoutManager + 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 + + var fullHeight = 0 + var curSectionItems = 0 + media.forEach { + if (it is ThumbnailSection) { + fullHeight += sectionTitleHeight + if (curSectionItems != 0) { + val rows = ((curSectionItems - 1) / layoutManager.spanCount + 1) + fullHeight += rows * thumbnailHeight + } + curSectionItems = 0 + } else { + curSectionItems++ + } + } + + val spacing = config.thumbnailSpacing + fullHeight += ((curSectionItems - 1) / layoutManager.spanCount + 1) * (thumbnailHeight + spacing) - spacing + media_vertical_fastscroller.setContentHeight(fullHeight) + media_vertical_fastscroller.setScrollToY(media_grid.computeVerticalScrollOffset()) + } + + private fun handleGridSpacing(media: ArrayList = mMedia) { + val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath) + if (viewType == VIEW_TYPE_GRID) { + val spanCount = config.mediaColumnCnt + val spacing = config.thumbnailSpacing + val useGridPosition = media.firstOrNull() is ThumbnailSection + + var currentGridDecoration: GridSpacingItemDecoration? = null + if (media_grid.itemDecorationCount > 0) { + currentGridDecoration = media_grid.getItemDecorationAt(0) as GridSpacingItemDecoration + currentGridDecoration.items = media + } + + val newGridDecoration = GridSpacingItemDecoration(spanCount, spacing, config.scrollHorizontally, config.fileRoundedCorners, media, useGridPosition) + if (currentGridDecoration.toString() != newGridDecoration.toString()) { + if (currentGridDecoration != null) { + media_grid.removeItemDecoration(currentGridDecoration) + } + media_grid.addItemDecoration(newGridDecoration) + } + } + } + + private fun initZoomListener() { + 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() { + if (layoutManager.spanCount > 1) { + reduceColumnCount() + getMediaAdapter()?.finishActMode() + } + } + + override fun zoomOut() { + if (layoutManager.spanCount < MAX_COLUMN_COUNT) { + increaseColumnCount() + getMediaAdapter()?.finishActMode() + } + } + } + } else { + mZoomListener = null + } + } + + private fun setupListLayoutManager() { + val layoutManager = media_grid.layoutManager as MyGridLayoutManager + layoutManager.spanCount = 1 + layoutManager.orientation = RecyclerView.VERTICAL + media_refresh_layout.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + val smallMargin = resources.getDimension(R.dimen.small_margin).toInt() + (media_grid.layoutParams as RelativeLayout.LayoutParams).apply { + topMargin = smallMargin + bottomMargin = smallMargin + } + + mZoomListener = null + } + + private fun increaseColumnCount() { + config.mediaColumnCnt = ++(media_grid.layoutManager as MyGridLayoutManager).spanCount + columnCountChanged() + } + + private fun reduceColumnCount() { + config.mediaColumnCnt = --(media_grid.layoutManager as MyGridLayoutManager).spanCount + columnCountChanged() + } + + private fun columnCountChanged() { + handleGridSpacing() + invalidateOptionsMenu() + getMediaAdapter()?.apply { + notifyItemRangeChanged(0, media.size) + measureRecyclerViewContent(media) + } + } + + private fun isSetWallpaperIntent() = intent.getBooleanExtra(SET_WALLPAPER_INTENT, false) + + override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { + if (requestCode == REQUEST_EDIT_IMAGE) { + if (resultCode == Activity.RESULT_OK && resultData != null) { + mMedia.clear() + refreshItems() + } + } + super.onActivityResult(requestCode, resultCode, resultData) + } + + private fun itemClicked(path: String) { + if (isSetWallpaperIntent()) { + toast(R.string.setting_wallpaper) + + val wantedWidth = wallpaperDesiredMinimumWidth + val wantedHeight = wallpaperDesiredMinimumHeight + val ratio = wantedWidth.toFloat() / wantedHeight + + val options = RequestOptions() + .override((wantedWidth * ratio).toInt(), wantedHeight) + .fitCenter() + + Glide.with(this) + .asBitmap() + .load(File(path)) + .apply(options) + .into(object : SimpleTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + try { + WallpaperManager.getInstance(applicationContext).setBitmap(resource) + setResult(Activity.RESULT_OK) + } catch (ignored: IOException) { + } + + finish() + } + }) + } else if (mIsGetImageIntent || mIsGetVideoIntent || mIsGetAnyIntent) { + Intent().apply { + data = Uri.parse(path) + setResult(Activity.RESULT_OK, this) + } + finish() + } else { + val isVideo = path.isVideoFast() + if (isVideo) { + val extras = HashMap() + extras[SHOW_FAVORITES] = mPath == FAVORITES + + if (shouldSkipAuthentication()) { + extras[SKIP_AUTHENTICATION] = true + } + openPath(path, false, extras) + } else { + Intent(this, ViewPagerActivity::class.java).apply { + putExtra(SKIP_AUTHENTICATION, shouldSkipAuthentication()) + putExtra(PATH, path) + putExtra(SHOW_ALL, mShowAll) + putExtra(SHOW_FAVORITES, mPath == FAVORITES) + putExtra(SHOW_RECYCLE_BIN, mPath == RECYCLE_BIN) + startActivity(this) + } + } + } + } + + private fun gotMedia(media: ArrayList, isFromCache: Boolean) { + mIsGettingMedia = false + checkLastMediaChanged() + mMedia = media + + runOnUiThread { + media_refresh_layout.isRefreshing = false + media_empty_text_placeholder.beVisibleIf(media.isEmpty() && !isFromCache) + media_empty_text_placeholder_2.beVisibleIf(media.isEmpty() && !isFromCache) + + if (media_empty_text_placeholder.isVisible()) { + media_empty_text_placeholder.text = getString(R.string.no_media_with_filters) + } + media_grid.beVisibleIf(media_empty_text_placeholder.isGone()) + + 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() + } + + mLatestMediaId = getLatestMediaId() + mLatestMediaDateId = getLatestMediaByDateId() + if (!isFromCache) { + val mediaToInsert = (mMedia).filter { it is Medium && it.deletedTS == 0L }.map { it as Medium } + Thread { + try { + mediaDB.insertAll(mediaToInsert) + } catch (e: Exception) { + } + }.start() + } + } + + override fun tryDeleteFiles(fileDirItems: ArrayList) { + val filtered = fileDirItems.filter { !getIsPathDirectory(it.path) && 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) + + movePathsInRecycleBin(filtered.map { it.path } as ArrayList) { + if (it) { + deleteFilteredFiles(filtered) + } else { + toast(R.string.unknown_error_occurred) + } + } + } else { + val deletingItems = resources.getQuantityString(R.plurals.deleting_items, filtered.size, filtered.size) + toast(deletingItems) + deleteFilteredFiles(filtered) + } + } + + private fun shouldSkipAuthentication() = intent.getBooleanExtra(SKIP_AUTHENTICATION, false) + + private fun deleteFilteredFiles(filtered: ArrayList) { + deleteFiles(filtered) { + if (!it) { + toast(R.string.unknown_error_occurred) + return@deleteFiles + } + + mMedia.removeAll { filtered.map { it.path }.contains((it as? Medium)?.path) } + + ensureBackgroundThread { + val useRecycleBin = config.useRecycleBin + filtered.forEach { + if (it.path.startsWith(recycleBinPath) || !useRecycleBin) { + deleteDBPath(it.path) + } + } + } + + if (mMedia.isEmpty()) { + deleteDirectoryIfEmpty() + deleteDBDirectory() + finish() + } + } + } + + override fun refreshItems() { + getMedia() + } + + override fun selectedPaths(paths: ArrayList) { + Intent().apply { + putExtra(PICKED_PATHS, paths) + setResult(Activity.RESULT_OK, this) + } + finish() + } + + override fun updateMediaGridDecoration(media: ArrayList) { + var currentGridPosition = 0 + media.forEach { + if (it is Medium) { + it.gridPosition = currentGridPosition++ + } else if (it is ThumbnailSection) { + currentGridPosition = 0 + } + } + + if (media_grid.itemDecorationCount > 0) { + val currentGridDecoration = media_grid.getItemDecorationAt(0) as GridSpacingItemDecoration + currentGridDecoration.items = media + } + } + + private fun setAsDefaultFolder() { + config.defaultFolder = mPath + invalidateOptionsMenu() + } + + private fun unsetAsDefaultFolder() { + config.defaultFolder = "" + invalidateOptionsMenu() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PanoramaPhotoActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PanoramaPhotoActivity.kt new file mode 100644 index 000000000..362bd6f8b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PanoramaPhotoActivity.kt @@ -0,0 +1,182 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Color +import android.net.Uri +import android.os.Bundle +import android.view.View +import android.view.Window +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.* +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +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 PanoramaPhotoActivity : SimpleActivity() { + private val CARDBOARD_DISPLAY_MODE = 3 + + private var isFullscreen = false + private var isExploreEnabled = true + private var isRendering = false + + public override fun onCreate(savedInstanceState: Bundle?) { + useDynamicTheme = false + requestWindowFeature(Window.FEATURE_NO_TITLE) + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_panorama_photo) + supportActionBar?.hide() + + checkNotchSupport() + setupButtonMargins() + + cardboard.setOnClickListener { + panorama_view.displayMode = CARDBOARD_DISPLAY_MODE + } + + explore.setOnClickListener { + isExploreEnabled = !isExploreEnabled + panorama_view.setPureTouchTracking(isExploreEnabled) + explore.setImageResource(if (isExploreEnabled) R.drawable.ic_explore_vector else R.drawable.ic_explore_off_vector) + } + + checkIntent() + } + + override fun onResume() { + super.onResume() + panorama_view.resumeRendering() + isRendering = true + if (config.blackBackground) { + updateStatusbarColor(Color.BLACK) + } + + window.statusBarColor = resources.getColor(R.color.circle_black_background) + } + + override fun onPause() { + super.onPause() + panorama_view.pauseRendering() + isRendering = false + } + + override fun onDestroy() { + super.onDestroy() + if (isRendering) { + panorama_view.shutdown() + } + } + + private fun checkIntent() { + val path = intent.getStringExtra(PATH) + if (path == null) { + toast(R.string.invalid_image_path) + finish() + return + } + + intent.removeExtra(PATH) + + try { + val options = VrPanoramaView.Options() + options.inputType = VrPanoramaView.Options.TYPE_MONO + ensureBackgroundThread { + val bitmap = getBitmapToLoad(path) + runOnUiThread { + panorama_view.apply { + beVisible() + loadImageFromBitmap(bitmap, options) + 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 : VrPanoramaEventListener() { + override fun onClick() { + handleClick() + } + }) + } + } + } + } catch (e: Exception) { + showErrorToast(e) + } + + window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> + isFullscreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0 + toggleButtonVisibility() + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + setupButtonMargins() + } + + private fun getBitmapToLoad(path: String): Bitmap? { + val options = BitmapFactory.Options() + options.inSampleSize = 1 + var bitmap: Bitmap? = null + + for (i in 0..10) { + try { + bitmap = if (path.startsWith("content://")) { + val inputStream = contentResolver.openInputStream(Uri.parse(path)) + BitmapFactory.decodeStream(inputStream) + } else { + BitmapFactory.decodeFile(path, options) + } + break + } catch (e: OutOfMemoryError) { + options.inSampleSize *= 2 + } + } + + return bitmap + } + + private fun setupButtonMargins() { + val navBarHeight = navigationBarHeight + (cardboard.layoutParams as RelativeLayout.LayoutParams).apply { + bottomMargin = navBarHeight + rightMargin = navigationBarWidth + } + + (explore.layoutParams as RelativeLayout.LayoutParams).bottomMargin = navigationBarHeight + + cardboard.onGlobalLayout { + panorama_gradient_background.layoutParams.height = navBarHeight + cardboard.height + } + } + + private fun toggleButtonVisibility() { + arrayOf(cardboard, explore, panorama_gradient_background).forEach { + it.animate().alpha(if (isFullscreen) 0f else 1f) + it.isClickable = !isFullscreen + } + } + + private fun handleClick() { + isFullscreen = !isFullscreen + toggleButtonVisibility() + if (isFullscreen) { + hideSystemUI(false) + } else { + showSystemUI(false) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PanoramaVideoActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PanoramaVideoActivity.kt new file mode 100644 index 000000000..7098e3e4a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PanoramaVideoActivity.kt @@ -0,0 +1,319 @@ +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.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() + checkIntent() + } + + 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 + val uri = if (path.startsWith("content://")) { + Uri.parse(path) + } else { + Uri.fromFile(File(path)) + } + + vr_video_view.apply { + loadVideo(uri, 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_vector) + } + 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_vector) + 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_vector) + 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_vector) + + 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_vector else R.drawable.ic_explore_off_vector) + } + } + + 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 + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PhotoActivity.kt similarity index 65% rename from app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoActivity.kt rename to app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PhotoActivity.kt index ef309a26d..95c439d3a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/PhotoActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PhotoActivity.kt @@ -1,10 +1,10 @@ -package com.simplemobiletools.gallery.activities +package com.simplemobiletools.gallery.pro.activities import android.os.Bundle class PhotoActivity : PhotoVideoActivity() { override fun onCreate(savedInstanceState: Bundle?) { - PhotoVideoActivity.mIsVideo = false + mIsVideo = false super.onCreate(savedInstanceState) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PhotoVideoActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PhotoVideoActivity.kt new file mode 100644 index 000000000..ff328654b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PhotoVideoActivity.kt @@ -0,0 +1,354 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.content.Intent +import android.content.res.Configuration +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.net.Uri +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import com.simplemobiletools.commons.dialogs.PropertiesDialog +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.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 + private var mFragment: ViewPagerFragment? = null + private var mUri: Uri? = null + + var mIsVideo = false + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.fragment_holder) + + if (checkAppSideloading()) { + return + } + + handlePermission(PERMISSION_WRITE_STORAGE) { + if (it) { + checkIntent(savedInstanceState) + } else { + toast(R.string.no_storage_permissions) + finish() + } + } + } + + override fun onResume() { + super.onResume() + supportActionBar?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.statusBarColor = Color.TRANSPARENT + + if (config.bottomActions) { + window.navigationBarColor = Color.TRANSPARENT + } else { + setTranslucentNavigation() + } + + if (config.blackBackground) { + updateStatusbarColor(Color.BLACK) + } + } + + private fun checkIntent(savedInstanceState: Bundle? = null) { + if (intent.data == null && intent.action == Intent.ACTION_VIEW) { + startActivity(Intent(this, MainActivity::class.java)) + finish() + return + } + + mUri = intent.data ?: return + val uri = mUri.toString() + if (uri.startsWith("content:/") && uri.contains("/storage/")) { + val guessedPath = uri.substring(uri.indexOf("/storage/")) + if (getDoesFilePathExist(guessedPath)) { + val extras = intent.extras ?: Bundle() + extras.apply { + putString(REAL_FILE_PATH, guessedPath) + intent.putExtras(this) + } + } + } + + var filename = getFilenameFromUri(mUri!!) + mIsFromGallery = intent.getBooleanExtra(IS_FROM_GALLERY, false) + if (mIsFromGallery && filename.isVideoFast() && config.openVideosOnSeparateScreen) { + launchVideoPlayer() + return + } + + if (intent.extras?.containsKey(REAL_FILE_PATH) == true) { + val realPath = intent.extras!!.getString(REAL_FILE_PATH) + if (realPath != null && getDoesFilePathExist(realPath)) { + if (realPath.getFilenameFromPath().contains('.') || filename.contains('.')) { + if (isFileTypeVisible(realPath)) { + bottom_actions.beGone() + sendViewPagerIntent(realPath) + finish() + return + } + } else { + filename = realPath.getFilenameFromPath() + } + } + } + + if (mUri!!.scheme == "file") { + if (filename.contains('.')) { + bottom_actions.beGone() + rescanPaths(arrayListOf(mUri!!.path!!)) + sendViewPagerIntent(mUri!!.path!!) + finish() + } + return + } else { + val path = applicationContext.getRealPathFromURI(mUri!!) ?: "" + if (path != mUri.toString() && path.isNotEmpty() && mUri!!.authority != "mms" && filename.contains('.') && getDoesFilePathExist(path)) { + if (isFileTypeVisible(path)) { + bottom_actions.beGone() + rescanPaths(arrayListOf(mUri!!.path!!)) + sendViewPagerIntent(path) + finish() + return + } + } + } + + checkNotchSupport() + showSystemUI(true) + val bundle = Bundle() + val file = File(mUri.toString()) + val intentType = intent.type ?: "" + val type = when { + filename.isVideoFast() || intentType.startsWith("video/") -> TYPE_VIDEOS + filename.isGif() || intentType.equals("image/gif", true) -> TYPE_GIFS + filename.isRawFast() -> TYPE_RAWS + filename.isSvg() -> TYPE_SVGS + file.isPortrait() -> TYPE_PORTRAITS + else -> TYPE_IMAGES + } + + 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) + + if (savedInstanceState == null) { + mFragment = if (mIsVideo) VideoFragment() else PhotoFragment() + mFragment!!.listener = this + mFragment!!.arguments = bundle + supportFragmentManager.beginTransaction().replace(R.id.fragment_placeholder, mFragment!!).commit() + } + + if (config.blackBackground) { + fragment_holder.background = ColorDrawable(Color.BLACK) + } + + if (config.maxBrightness) { + val attributes = window.attributes + attributes.screenBrightness = 1f + window.attributes = attributes + } + + window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> + val isFullscreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0 + mFragment?.fullscreenToggled(isFullscreen) + } + + 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() + } + + private fun sendViewPagerIntent(path: String) { + Intent(this, ViewPagerActivity::class.java).apply { + putExtra(SKIP_AUTHENTICATION, intent.getBooleanExtra(SKIP_AUTHENTICATION, false)) + putExtra(SHOW_FAVORITES, intent.getBooleanExtra(SHOW_FAVORITES, false)) + putExtra(IS_VIEW_INTENT, true) + putExtra(IS_FROM_GALLERY, mIsFromGallery) + putExtra(PATH, path) + startActivity(this) + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.photo_video_menu, menu) + val visibleBottomActions = if (config.bottomActions) config.visibleBottomActions else 0 + + menu.apply { + findItem(R.id.menu_set_as).isVisible = mMedium?.isImage() == true && visibleBottomActions and BOTTOM_ACTION_SET_AS == 0 + findItem(R.id.menu_edit).isVisible = mMedium?.isImage() == true && mUri?.scheme == "file" && visibleBottomActions and BOTTOM_ACTION_EDIT == 0 + findItem(R.id.menu_properties).isVisible = mUri?.scheme == "file" && visibleBottomActions and BOTTOM_ACTION_PROPERTIES == 0 + findItem(R.id.menu_share).isVisible = visibleBottomActions and BOTTOM_ACTION_SHARE == 0 + findItem(R.id.menu_show_on_map).isVisible = visibleBottomActions and BOTTOM_ACTION_SHOW_ON_MAP == 0 + } + + updateMenuItemColors(menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (mMedium == null || mUri == null) { + return true + } + + when (item.itemId) { + R.id.menu_set_as -> setAs(mUri!!.toString()) + R.id.menu_open_with -> openPath(mUri!!.toString(), true) + R.id.menu_share -> sharePath(mUri!!.toString()) + R.id.menu_edit -> openEditor(mUri!!.toString()) + R.id.menu_properties -> showProperties() + R.id.menu_show_on_map -> showFileOnMap(mUri!!.toString()) + else -> return super.onOptionsItemSelected(item) + } + return true + } + + private fun showProperties() { + PropertiesDialog(this, mUri!!.path!!) + } + + private fun isFileTypeVisible(path: String): Boolean { + val filter = config.filterMedia + return !(path.isImageFast() && filter and TYPE_IMAGES == 0 || + path.isVideoFast() && filter and TYPE_VIDEOS == 0 || + path.isGif() && filter and TYPE_GIFS == 0 || + path.isRawFast() && filter and TYPE_RAWS == 0 || + path.isSvg() && filter and TYPE_SVGS == 0 || + path.isPortrait() && filter and TYPE_PORTRAITS == 0) + } + + private fun initBottomActions() { + initBottomActionButtons() + initBottomActionsLayout() + } + + private fun initBottomActionsLayout() { + bottom_actions.layoutParams.height = resources.getDimension(R.dimen.bottom_actions_height).toInt() + navigationBarHeight + if (config.bottomActions) { + bottom_actions.beVisible() + } else { + bottom_actions.beGone() + } + } + + 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, bottom_rename, bottom_copy, bottom_move, bottom_resize).forEach { + it.beGone() + } + + val visibleBottomActions = if (config.bottomActions) config.visibleBottomActions else 0 + bottom_edit.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_EDIT != 0 && mMedium?.isImage() == true) + bottom_edit.setOnClickListener { + if (mUri != null && bottom_actions.alpha == 1f) { + openEditor(mUri!!.toString()) + } + } + + bottom_share.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_SHARE != 0) + bottom_share.setOnClickListener { + if (mUri != null && bottom_actions.alpha == 1f) { + sharePath(mUri!!.toString()) + } + } + + bottom_set_as.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_SET_AS != 0 && mMedium?.isImage() == true) + bottom_set_as.setOnClickListener { + setAs(mUri!!.toString()) + } + + bottom_show_on_map.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_SHOW_ON_MAP != 0) + bottom_show_on_map.setOnClickListener { + showFileOnMap(mUri!!.toString()) + } + } + + override fun fragmentClicked() { + mIsFullScreen = !mIsFullScreen + if (mIsFullScreen) { + hideSystemUI(true) + } else { + showSystemUI(true) + } + + val newAlpha = if (mIsFullScreen) 0f else 1f + top_shadow.animate().alpha(newAlpha).start() + if (!bottom_actions.isGone()) { + bottom_actions.animate().alpha(newAlpha).start() + } + } + + override fun videoEnded() = false + + override fun goToPrevItem() {} + + override fun goToNextItem() {} + + override fun launchViewVideoIntent(path: String) {} + + override fun isSlideShowActive() = false +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SearchActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SearchActivity.kt new file mode 100644 index 000000000..769d65e90 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SearchActivity.kt @@ -0,0 +1,385 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.app.SearchManager +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.ViewGroup +import android.widget.RelativeLayout +import androidx.appcompat.widget.SearchView +import androidx.core.view.MenuItemCompat +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.VIEW_TYPE_GRID +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.commons.models.FileDirItem +import com.simplemobiletools.commons.views.MyGridLayoutManager +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.* +import com.simplemobiletools.gallery.pro.helpers.* +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.activity_media.* +import kotlinx.android.synthetic.main.activity_search.* +import kotlinx.android.synthetic.main.activity_search.media_empty_text_placeholder +import kotlinx.android.synthetic.main.activity_search.media_grid +import kotlinx.android.synthetic.main.activity_search.media_horizontal_fastscroller +import kotlinx.android.synthetic.main.activity_search.media_vertical_fastscroller +import java.io.File + +class SearchActivity : SimpleActivity(), MediaOperationsListener { + private var mIsSearchOpen = false + private var mLastSearchedText = "" + private var mDateFormat = "" + private var mTimeFormat = "" + + private var mSearchMenuItem: MenuItem? = null + private var mCurrAsyncTask: GetMediaAsynctask? = null + private var mAllMedia = ArrayList() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_search) + media_empty_text_placeholder.setTextColor(config.textColor) + mDateFormat = config.dateFormat + mTimeFormat = getTimeFormat() + getAllMedia() + } + + override fun onDestroy() { + super.onDestroy() + mCurrAsyncTask?.stopFetching() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_search, menu) + setupSearch(menu) + updateMenuItemColors(menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.toggle_filename -> toggleFilenameVisibility() + else -> return super.onOptionsItemSelected(item) + } + return true + } + + 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) { + mLastSearchedText = newText + textChanged(newText) + } + return true + } + }) + } + + MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, object : MenuItemCompat.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem?): Boolean { + mIsSearchOpen = true + return true + } + + // this triggers on device rotation too, avoid doing anything + override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { + if (mIsSearchOpen) { + mIsSearchOpen = false + mLastSearchedText = "" + } + return true + } + }) + mSearchMenuItem?.expandActionView() + } + + private fun textChanged(text: String) { + ensureBackgroundThread { + try { + val filtered = mAllMedia.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, "") + runOnUiThread { + if (grouped.isEmpty()) { + media_empty_text_placeholder.text = getString(R.string.no_items_found) + media_empty_text_placeholder.beVisible() + } else { + media_empty_text_placeholder.beGone() + } + + handleGridSpacing(grouped) + getMediaAdapter()?.updateMedia(grouped) + measureRecyclerViewContent(grouped) + } + } catch (ignored: Exception) { + } + } + } + + private fun setupAdapter() { + val currAdapter = media_grid.adapter + if (currAdapter == null) { + val fastscroller = if (config.scrollHorizontally) media_horizontal_fastscroller else media_vertical_fastscroller + MediaAdapter(this, ArrayList(), this, false, false, "", media_grid, fastscroller) { + if (it is Medium) { + itemClicked(it.path) + } + }.apply { + media_grid.adapter = this + } + setupLayoutManager() + handleGridSpacing(mAllMedia) + measureRecyclerViewContent(mAllMedia) + } else if (mLastSearchedText.isEmpty()) { + (currAdapter as MediaAdapter).updateMedia(mAllMedia) + handleGridSpacing(mAllMedia) + measureRecyclerViewContent(mAllMedia) + } else { + textChanged(mLastSearchedText) + } + + setupScrollDirection() + } + + private fun handleGridSpacing(media: ArrayList) { + val viewType = config.getFolderViewType(SHOW_ALL) + if (viewType == VIEW_TYPE_GRID) { + if (media_grid.itemDecorationCount > 0) { + media_grid.removeItemDecorationAt(0) + } + + val spanCount = config.mediaColumnCnt + val spacing = config.thumbnailSpacing + val decoration = GridSpacingItemDecoration(spanCount, spacing, config.scrollHorizontally, config.fileRoundedCorners, media, true) + media_grid.addItemDecoration(decoration) + } + } + + private fun getMediaAdapter() = media_grid.adapter as? MediaAdapter + + private fun toggleFilenameVisibility() { + config.displayFileNames = !config.displayFileNames + getMediaAdapter()?.updateDisplayFilenames(config.displayFileNames) + } + + private fun itemClicked(path: String) { + val isVideo = path.isVideoFast() + if (isVideo) { + openPath(path, false) + } else { + Intent(this, ViewPagerActivity::class.java).apply { + putExtra(PATH, path) + putExtra(SHOW_ALL, false) + startActivity(this) + } + } + } + + private fun setupLayoutManager() { + val viewType = config.getFolderViewType(SHOW_ALL) + if (viewType == VIEW_TYPE_GRID) { + setupGridLayoutManager() + } else { + setupListLayoutManager() + } + } + + private fun setupGridLayoutManager() { + val layoutManager = media_grid.layoutManager as MyGridLayoutManager + if (config.scrollHorizontally) { + layoutManager.orientation = RecyclerView.HORIZONTAL + media_grid.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT) + } else { + layoutManager.orientation = RecyclerView.VERTICAL + media_grid.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + layoutManager.spanCount = config.mediaColumnCnt + val adapter = getMediaAdapter() + layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (adapter?.isASectionTitle(position) == true) { + layoutManager.spanCount + } else { + 1 + } + } + } + } + + private fun setupListLayoutManager() { + val layoutManager = media_grid.layoutManager as MyGridLayoutManager + layoutManager.spanCount = 1 + layoutManager.orientation = RecyclerView.VERTICAL + } + + private fun setupScrollDirection() { + val viewType = config.getFolderViewType(SHOW_ALL) + val allowHorizontalScroll = config.scrollHorizontally && viewType == VIEW_TYPE_GRID + media_vertical_fastscroller.isHorizontal = false + media_vertical_fastscroller.beGoneIf(allowHorizontalScroll) + + media_horizontal_fastscroller.isHorizontal = true + media_horizontal_fastscroller.beVisibleIf(allowHorizontalScroll) + + val sorting = config.getFolderSorting(SHOW_ALL) + if (allowHorizontalScroll) { + media_horizontal_fastscroller.setViews(media_grid) { + media_horizontal_fastscroller.updateBubbleText(getBubbleTextItem(it, sorting)) + } + } else { + media_vertical_fastscroller.setViews(media_grid) { + media_vertical_fastscroller.updateBubbleText(getBubbleTextItem(it, sorting)) + } + } + } + + private fun getBubbleTextItem(index: Int, sorting: Int): String { + var realIndex = index + val mediaAdapter = getMediaAdapter() + if (mediaAdapter?.isASectionTitle(index) == true) { + realIndex++ + } + return mediaAdapter?.getItemBubbleText(realIndex, sorting, mDateFormat, mTimeFormat) ?: "" + } + + private fun measureRecyclerViewContent(media: ArrayList) { + media_grid.onGlobalLayout { + if (config.scrollHorizontally) { + calculateContentWidth(media) + } else { + calculateContentHeight(media) + } + } + } + + private fun calculateContentWidth(media: ArrayList) { + val layoutManager = media_grid.layoutManager as MyGridLayoutManager + val thumbnailWidth = layoutManager.getChildAt(0)?.width ?: 0 + val fullWidth = ((media.size - 1) / layoutManager.spanCount + 1) * thumbnailWidth + media_horizontal_fastscroller.setContentWidth(fullWidth) + media_horizontal_fastscroller.setScrollToX(media_grid.computeHorizontalScrollOffset()) + } + + private fun calculateContentHeight(media: ArrayList) { + val layoutManager = media_grid.layoutManager as MyGridLayoutManager + val pathToCheck = SHOW_ALL + 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 + + var fullHeight = 0 + var curSectionItems = 0 + media.forEach { + if (it is ThumbnailSection) { + fullHeight += sectionTitleHeight + if (curSectionItems != 0) { + val rows = ((curSectionItems - 1) / layoutManager.spanCount + 1) + fullHeight += rows * thumbnailHeight + } + curSectionItems = 0 + } else { + curSectionItems++ + } + } + + fullHeight += ((curSectionItems - 1) / layoutManager.spanCount + 1) * thumbnailHeight + media_vertical_fastscroller.setContentHeight(fullHeight) + media_vertical_fastscroller.setScrollToY(media_grid.computeVerticalScrollOffset()) + } + + private fun getAllMedia() { + getCachedMedia("") { + if (it.isNotEmpty()) { + mAllMedia = it.clone() as ArrayList + } + runOnUiThread { + setupAdapter() + } + startAsyncTask(false) + } + } + + private fun startAsyncTask(updateItems: Boolean) { + mCurrAsyncTask?.stopFetching() + mCurrAsyncTask = GetMediaAsynctask(applicationContext, "", showAll = true) { + mAllMedia = it.clone() as ArrayList + if (updateItems) { + textChanged(mLastSearchedText) + } + } + + mCurrAsyncTask!!.execute() + } + + override fun refreshItems() { + startAsyncTask(true) + } + + override fun tryDeleteFiles(fileDirItems: ArrayList) { + 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) + + movePathsInRecycleBin(filtered.map { it.path } as ArrayList) { + if (it) { + deleteFilteredFiles(filtered) + } else { + toast(R.string.unknown_error_occurred) + } + } + } else { + val deletingItems = resources.getQuantityString(R.plurals.deleting_items, filtered.size, filtered.size) + toast(deletingItems) + deleteFilteredFiles(filtered) + } + } + + private fun deleteFilteredFiles(filtered: ArrayList) { + deleteFiles(filtered) { + if (!it) { + toast(R.string.unknown_error_occurred) + return@deleteFiles + } + + mAllMedia.removeAll { filtered.map { it.path }.contains((it as? Medium)?.path) } + + ensureBackgroundThread { + val useRecycleBin = config.useRecycleBin + filtered.forEach { + if (it.path.startsWith(recycleBinPath) || !useRecycleBin) { + deleteDBPath(it.path) + } + } + } + } + } + + override fun selectedPaths(paths: ArrayList) { + } + + override fun updateMediaGridDecoration(media: ArrayList) { + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SetWallpaperActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SetWallpaperActivity.kt similarity index 58% rename from app/src/main/kotlin/com/simplemobiletools/gallery/activities/SetWallpaperActivity.kt rename to app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SetWallpaperActivity.kt index 9d2fddb3c..a6941c2b4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/SetWallpaperActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SetWallpaperActivity.kt @@ -1,29 +1,40 @@ -package com.simplemobiletools.gallery.activities +package com.simplemobiletools.gallery.pro.activities +import android.annotation.SuppressLint import android.app.Activity import android.app.WallpaperManager import android.content.Intent import android.graphics.Bitmap import android.net.Uri -import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem +import com.simplemobiletools.commons.dialogs.RadioGroupDialog +import com.simplemobiletools.commons.extensions.checkAppSideloading import com.simplemobiletools.commons.extensions.toast -import com.simplemobiletools.gallery.R +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.commons.helpers.isNougatPlus +import com.simplemobiletools.commons.models.RadioItem +import com.simplemobiletools.gallery.pro.R import com.theartofdev.edmodo.cropper.CropImageView -import kotlinx.android.synthetic.main.view_crop_image.* +import kotlinx.android.synthetic.main.activity_set_wallpaper.* +import kotlinx.android.synthetic.main.bottom_set_wallpaper_actions.* class SetWallpaperActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener { private val PICK_IMAGE = 1 private var isLandscapeRatio = true + private var wallpaperFlag = -1 lateinit var uri: Uri lateinit var wallpaperManager: WallpaperManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.view_crop_image) + setContentView(R.layout.activity_set_wallpaper) + + if (checkAppSideloading()) { + return + } if (intent.data == null) { val pickIntent = Intent(applicationContext, MainActivity::class.java) @@ -34,10 +45,25 @@ class SetWallpaperActivity : SimpleActivity(), CropImageView.OnCropImageComplete } handleImage(intent) + setupBottomActions() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_set_wallpaper, menu) + updateMenuItemColors(menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.save -> confirmWallpaper() + else -> return super.onOptionsItemSelected(item) + } + return true } private fun handleImage(intent: Intent) { - uri = intent.data + uri = intent.data!! if (uri.scheme != "file" && uri.scheme != "content") { toast(R.string.unknown_file_location) finish() @@ -53,56 +79,70 @@ class SetWallpaperActivity : SimpleActivity(), CropImageView.OnCropImageComplete setupAspectRatio() } + private fun setupBottomActions() { + bottom_set_wallpaper_aspect_ratio.setOnClickListener { + changeAspectRatio(!isLandscapeRatio) + } + + bottom_set_wallpaper_rotate.setOnClickListener { + crop_image_view.rotateImage(90) + } + } + private fun setupAspectRatio() { val wallpaperWidth = if (isLandscapeRatio) wallpaperManager.desiredMinimumWidth else wallpaperManager.desiredMinimumWidth / 2 crop_image_view.setAspectRatio(wallpaperWidth, wallpaperManager.desiredMinimumHeight) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_set_wallpaper, menu) - - menu.findItem(R.id.portrait_aspect_ratio).isVisible = isLandscapeRatio - menu.findItem(R.id.landscape_aspect_ratio).isVisible = !isLandscapeRatio - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.save -> crop_image_view.getCroppedImageAsync() - R.id.rotate -> crop_image_view.rotateImage(90) - R.id.portrait_aspect_ratio -> changeAspectRatio(false) - R.id.landscape_aspect_ratio -> changeAspectRatio(true) - else -> return super.onOptionsItemSelected(item) - } - return true + bottom_set_wallpaper_aspect_ratio.setImageResource(if (isLandscapeRatio) R.drawable.ic_minimize else R.drawable.ic_maximize) } private fun changeAspectRatio(isLandscape: Boolean) { isLandscapeRatio = isLandscape setupAspectRatio() - invalidateOptionsMenu() } + @SuppressLint("InlinedApi") + private fun confirmWallpaper() { + if (isNougatPlus()) { + val items = arrayListOf( + RadioItem(WallpaperManager.FLAG_SYSTEM, getString(R.string.home_screen)), + RadioItem(WallpaperManager.FLAG_LOCK, getString(R.string.lock_screen)), + RadioItem(WallpaperManager.FLAG_SYSTEM or WallpaperManager.FLAG_LOCK, getString(R.string.home_and_lock_screen))) + + RadioGroupDialog(this, items) { + wallpaperFlag = it as Int + crop_image_view.getCroppedImageAsync() + } + } else { + crop_image_view.getCroppedImageAsync() + } + } + + @SuppressLint("NewApi") override fun onCropImageComplete(view: CropImageView?, result: CropImageView.CropResult) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed) + if (isDestroyed) return if (result.error == null) { toast(R.string.setting_wallpaper) - Thread({ + ensureBackgroundThread { val bitmap = result.bitmap val wantedHeight = wallpaperManager.desiredMinimumHeight val ratio = wantedHeight / bitmap.height.toFloat() val wantedWidth = (bitmap.width * ratio).toInt() try { - wallpaperManager.setBitmap(Bitmap.createScaledBitmap(bitmap, wantedWidth, wantedHeight, true)) + val scaledBitmap = Bitmap.createScaledBitmap(bitmap, wantedWidth, wantedHeight, true) + if (isNougatPlus()) { + wallpaperManager.setBitmap(scaledBitmap, null, true, wallpaperFlag) + } else { + wallpaperManager.setBitmap(scaledBitmap) + } setResult(Activity.RESULT_OK) } catch (e: OutOfMemoryError) { toast(R.string.out_of_memory_error) setResult(Activity.RESULT_CANCELED) } finish() - }).start() + } } else { toast("${getString(R.string.image_editing_failed)}: ${result.error.message}") } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SettingsActivity.kt new file mode 100644 index 000000000..45279a23d --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SettingsActivity.kt @@ -0,0 +1,852 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.text.TextUtils +import android.view.Menu +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.simplemobiletools.commons.dialogs.* +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.ChangeFileThumbnailStyleDialog +import com.simplemobiletools.gallery.pro.dialogs.ChangeFolderThumbnailStyleDialog +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.mediaDB +import com.simplemobiletools.gallery.pro.extensions.showRecycleBinEmptyingDialog +import com.simplemobiletools.gallery.pro.helpers.* +import com.simplemobiletools.gallery.pro.models.AlbumCover +import kotlinx.android.synthetic.main.activity_settings.* +import java.io.File +import java.io.InputStream +import java.util.* + +class SettingsActivity : SimpleActivity() { + private val PICK_IMPORT_SOURCE_INTENT = 1 + 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() + setupChangeDateTimeFormat() + setupFileLoadingPriority() + setupManageIncludedFolders() + setupManageExcludedFolders() + setupManageHiddenFolders() + setupShowHiddenItems() + setupAutoplayVideos() + setupRememberLastVideo() + setupLoopVideos() + setupOpenVideosOnSeparateScreen() + setupMaxBrightness() + setupCropThumbnails() + setupDarkBackground() + setupScrollHorizontally() + setupScreenRotation() + setupHideSystemUI() + setupHiddenItemPasswordProtection() + setupAppPasswordProtection() + setupFileDeletionPasswordProtection() + setupDeleteEmptyFolders() + setupAllowDownGesture() + setupAllowRotatingWithGestures() + setupShowNotch() + setupBottomActions() + setupFileThumbnailStyle() + setupFolderThumbnailStyle() + setupKeepLastModified() + setupEnablePullToRefresh() + setupAllowZoomingImages() + setupShowHighestQuality() + setupAllowOneToOneZoom() + setupAllowInstantChange() + setupShowExtendedDetails() + setupHideExtendedDetails() + setupManageExtendedDetails() + setupSkipDeleteConfirmation() + setupManageBottomActions() + setupUseRecycleBin() + setupShowRecycleBin() + setupShowRecycleBinLast() + setupEmptyRecycleBin() + updateTextColors(settings_holder) + setupSectionColors() + setupClearCache() + setupExportSettings() + setupImportSettings() + invalidateOptionsMenu() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + updateMenuItemColors(menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { + super.onActivityResult(requestCode, resultCode, resultData) + if (requestCode == PICK_IMPORT_SOURCE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) { + val inputStream = contentResolver.openInputStream(resultData.data!!) + parseFile(inputStream) + } + } + + 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 setupChangeDateTimeFormat() { + settings_change_date_time_format_holder.setOnClickListener { + ChangeDateTimeFormatDialog(this) {} + } + } + + private fun setupFileLoadingPriority() { + settings_file_loading_priority.text = getFileLoadingPriorityText() + settings_file_loading_priority_holder.setOnClickListener { + val items = arrayListOf( + RadioItem(PRIORITY_SPEED, getString(R.string.speed)), + RadioItem(PRIORITY_COMPROMISE, getString(R.string.compromise)), + RadioItem(PRIORITY_VALIDITY, getString(R.string.avoid_showing_invalid_files))) + + RadioGroupDialog(this@SettingsActivity, items, config.fileLoadingPriority) { + config.fileLoadingPriority = it as Int + settings_file_loading_priority.text = getFileLoadingPriorityText() + } + } + } + + private fun getFileLoadingPriorityText() = getString(when (config.fileLoadingPriority) { + PRIORITY_SPEED -> R.string.speed + PRIORITY_COMPROMISE -> R.string.compromise + else -> R.string.avoid_showing_invalid_files + }) + + 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.beVisibleIf(!isQPlus()) + 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 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 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 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 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 setupAllowRotatingWithGestures() { + settings_allow_rotating_with_gestures.isChecked = config.allowRotatingWithGestures + settings_allow_rotating_with_gestures_holder.setOnClickListener { + settings_allow_rotating_with_gestures.toggle() + config.allowRotatingWithGestures = settings_allow_rotating_with_gestures.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 setupFileThumbnailStyle() { + settings_file_thumbnail_style_holder.setOnClickListener { + ChangeFileThumbnailStyleDialog(this) + } + } + + private fun setupFolderThumbnailStyle() { + settings_folder_thumbnail_style.text = getFolderStyleText() + settings_folder_thumbnail_style_holder.setOnClickListener { + ChangeFolderThumbnailStyleDialog(this) { + settings_folder_thumbnail_style.text = getFolderStyleText() + } + } + } + + private fun getFolderStyleText() = getString(when (config.folderStyle) { + FOLDER_STYLE_SQUARE -> R.string.square + else -> R.string.rounded_corners + }) + + 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 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_allow_rotating_with_gestures_holder.beVisibleIf(config.allowZoomingImages) + 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() { + ensureBackgroundThread { + try { + mRecycleBinContentSize = mediaDB.getDeletedMedia().sumByLong { it.size } + } catch (ignored: Exception) { + } + runOnUiThread { + settings_empty_recycle_bin_size.text = mRecycleBinContentSize.formatSize() + } + } + + 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 setupClearCache() { + ensureBackgroundThread { + runOnUiThread { + settings_clear_cache_size.text = cacheDir.getProperSize(true).formatSize() + } + } + + settings_clear_cache_holder.setOnClickListener { + ensureBackgroundThread { + cacheDir.deleteRecursively() + runOnUiThread { + settings_clear_cache_size.text = cacheDir.getProperSize(true).formatSize() + } + } + } + } + + private fun setupExportSettings() { + settings_export_holder.setOnClickListener { + val configItems = LinkedHashMap().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(WIDGET_BG_COLOR, config.widgetBgColor) + put(WIDGET_TEXT_COLOR, config.widgetTextColor) + put(DATE_FORMAT, config.dateFormat) + put(USE_24_HOUR_FORMAT, config.use24HourFormat) + put(INCLUDED_FOLDERS, TextUtils.join(",", config.includedFolders)) + put(EXCLUDED_FOLDERS, TextUtils.join(",", config.excludedFolders)) + put(SHOW_HIDDEN_MEDIA, config.showHiddenMedia) + put(FILE_LOADING_PRIORITY, config.fileLoadingPriority) + put(AUTOPLAY_VIDEOS, config.autoplayVideos) + put(REMEMBER_LAST_VIDEO_POSITION, config.rememberLastVideoPosition) + 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_THUMBNAIL_FILE_TYPES, config.showThumbnailFileTypes) + 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(ALLOW_ROTATING_WITH_GESTURES, config.allowRotatingWithGestures) + 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(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_CONFLICT_RESOLUTION, config.lastConflictResolution) + put(LAST_CONFLICT_APPLY_TO_ALL, config.lastConflictApplyToAll) + put(EDITOR_BRUSH_COLOR, config.editorBrushColor) + put(EDITOR_BRUSH_HARDNESS, config.editorBrushHardness) + put(EDITOR_BRUSH_SIZE, config.editorBrushSize) + put(ALBUM_COVERS, config.albumCovers) + put(FOLDER_THUMBNAIL_STYLE, config.folderStyle) + put(FOLDER_MEDIA_COUNT, config.showFolderMediaCount) + put(LIMIT_FOLDER_TITLE, config.limitFolderTitle) + put(THUMBNAIL_SPACING, config.thumbnailSpacing) + put(FILE_ROUNDED_CORNERS, config.fileRoundedCorners) + } + + exportSettings(configItems) + } + } + + private fun setupImportSettings() { + settings_import_holder.setOnClickListener { + if (isQPlus()) { + Intent(Intent.ACTION_GET_CONTENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "text/plain" + startActivityForResult(this, PICK_IMPORT_SOURCE_INTENT) + } + } else { + handlePermission(PERMISSION_READ_STORAGE) { + if (it) { + FilePickerDialog(this) { + ensureBackgroundThread { + parseFile(File(it).inputStream()) + } + } + } + } + } + } + } + + private fun parseFile(inputStream: InputStream?) { + if (inputStream == null) { + toast(R.string.unknown_error_occurred) + return + } + + var importedItems = 0 + val configValues = LinkedHashMap() + 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() + WIDGET_BG_COLOR -> config.widgetBgColor = value.toInt() + WIDGET_TEXT_COLOR -> config.widgetTextColor = value.toInt() + DATE_FORMAT -> config.dateFormat = value.toString() + USE_24_HOUR_FORMAT -> config.use24HourFormat = value.toBoolean() + INCLUDED_FOLDERS -> config.addIncludedFolders(value.toStringSet()) + EXCLUDED_FOLDERS -> config.addExcludedFolders(value.toStringSet()) + SHOW_HIDDEN_MEDIA -> config.showHiddenMedia = value.toBoolean() + FILE_LOADING_PRIORITY -> config.fileLoadingPriority = value.toInt() + AUTOPLAY_VIDEOS -> config.autoplayVideos = value.toBoolean() + REMEMBER_LAST_VIDEO_POSITION -> config.rememberLastVideoPosition = value.toBoolean() + 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_THUMBNAIL_FILE_TYPES -> config.showThumbnailFileTypes = 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() + ALLOW_ROTATING_WITH_GESTURES -> config.allowRotatingWithGestures = 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() + 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.toString().toFloat() + LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y -> config.lastEditorCropOtherAspectRatioY = value.toString().toFloat() + LAST_CONFLICT_RESOLUTION -> config.lastConflictResolution = value.toInt() + LAST_CONFLICT_APPLY_TO_ALL -> config.lastConflictApplyToAll = value.toBoolean() + EDITOR_BRUSH_COLOR -> config.editorBrushColor = value.toInt() + EDITOR_BRUSH_HARDNESS -> config.editorBrushHardness = value.toString().toFloat() + EDITOR_BRUSH_SIZE -> config.editorBrushSize = value.toString().toFloat() + FOLDER_THUMBNAIL_STYLE -> config.folderStyle = value.toInt() + FOLDER_MEDIA_COUNT -> config.showFolderMediaCount = value.toInt() + LIMIT_FOLDER_TITLE -> config.limitFolderTitle = value.toBoolean() + THUMBNAIL_SPACING -> config.thumbnailSpacing = value.toInt() + FILE_ROUNDED_CORNERS -> config.fileRoundedCorners = value.toBoolean() + ALBUM_COVERS -> { + val existingCovers = config.parseAlbumCovers() + val existingCoverPaths = existingCovers.map { it.path }.toMutableList() as ArrayList + + val listType = object : TypeToken>() {}.type + val covers = Gson().fromJson>(value.toString(), listType) ?: ArrayList(1) + covers.filter { !existingCoverPaths.contains(it.path) && getDoesFilePathExist(it.tmb) }.forEach { + existingCovers.add(it) + } + + config.albumCovers = Gson().toJson(existingCovers) + } + } + } + + toast(if (configValues.size > 0) R.string.settings_imported_successfully else R.string.no_entries_for_importing) + runOnUiThread { + setupSettingItems() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SimpleActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SimpleActivity.kt new file mode 100644 index 000000000..6824b7038 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SimpleActivity.kt @@ -0,0 +1,97 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.annotation.SuppressLint +import android.database.ContentObserver +import android.net.Uri +import android.provider.MediaStore.Images +import android.provider.MediaStore.Video +import android.view.WindowManager +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.dialogs.FilePickerDialog +import com.simplemobiletools.commons.extensions.getParentPath +import com.simplemobiletools.commons.extensions.getRealPathFromURI +import com.simplemobiletools.commons.extensions.scanPathRecursively +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.commons.helpers.isPiePlus +import com.simplemobiletools.gallery.pro.R +import com.simplemobiletools.gallery.pro.extensions.addPathToDB +import com.simplemobiletools.gallery.pro.extensions.config +import com.simplemobiletools.gallery.pro.extensions.updateDirectoryPath + +open class SimpleActivity : BaseSimpleActivity() { + val observer = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean, uri: Uri) { + super.onChange(selfChange, uri) + val path = getRealPathFromURI(uri) + if (path != null) { + updateDirectoryPath(path.getParentPath()) + addPathToDB(path) + } + } + } + + override fun getAppIconIDs() = arrayListOf( + R.mipmap.ic_launcher_red, + R.mipmap.ic_launcher_pink, + R.mipmap.ic_launcher_purple, + R.mipmap.ic_launcher_deep_purple, + R.mipmap.ic_launcher_indigo, + R.mipmap.ic_launcher_blue, + R.mipmap.ic_launcher_light_blue, + R.mipmap.ic_launcher_cyan, + R.mipmap.ic_launcher_teal, + R.mipmap.ic_launcher_green, + R.mipmap.ic_launcher_light_green, + R.mipmap.ic_launcher_lime, + R.mipmap.ic_launcher_yellow, + R.mipmap.ic_launcher_amber, + R.mipmap.ic_launcher, + R.mipmap.ic_launcher_deep_orange, + R.mipmap.ic_launcher_brown, + R.mipmap.ic_launcher_blue_grey, + R.mipmap.ic_launcher_grey_black + ) + + 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) + } + } + } + + protected fun registerFileUpdateListener() { + try { + contentResolver.registerContentObserver(Images.Media.EXTERNAL_CONTENT_URI, true, observer) + contentResolver.registerContentObserver(Video.Media.EXTERNAL_CONTENT_URI, true, observer) + } catch (ignored: Exception) { + } + } + + protected fun unregisterFileUpdateListener() { + try { + contentResolver.unregisterContentObserver(observer) + } catch (ignored: Exception) { + } + } + + protected fun showAddIncludedFolderDialog(callback: () -> Unit) { + FilePickerDialog(this, config.lastFilepickerPath, false, config.shouldShowHidden, false, true) { + config.lastFilepickerPath = it + config.addIncludedFolder(it) + callback() + ensureBackgroundThread { + scanPathRecursively(it) + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SplashActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SplashActivity.kt new file mode 100644 index 000000000..9d0c0cc79 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SplashActivity.kt @@ -0,0 +1,44 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.content.Intent +import com.simplemobiletools.commons.activities.BaseSplashActivity +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.gallery.pro.extensions.config +import com.simplemobiletools.gallery.pro.extensions.favoritesDB +import com.simplemobiletools.gallery.pro.extensions.getFavoriteFromPath +import com.simplemobiletools.gallery.pro.extensions.mediaDB +import com.simplemobiletools.gallery.pro.models.Favorite + +class SplashActivity : BaseSplashActivity() { + override fun initActivity() { + + // check if previously selected favorite items have been properly migrated into the new Favorites table + if (config.wereFavoritesMigrated) { + launchActivity() + } else { + if (config.appRunCount == 0) { + config.wereFavoritesMigrated = true + launchActivity() + } else { + config.wereFavoritesMigrated = true + ensureBackgroundThread { + val favorites = ArrayList() + val favoritePaths = mediaDB.getFavorites().map { it.path }.toMutableList() as ArrayList + favoritePaths.forEach { + favorites.add(getFavoriteFromPath(it)) + } + favoritesDB.insertAll(favorites) + + runOnUiThread { + launchActivity() + } + } + } + } + } + + private fun launchActivity() { + startActivity(Intent(this, MainActivity::class.java)) + finish() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/VideoActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/VideoActivity.kt similarity index 66% rename from app/src/main/kotlin/com/simplemobiletools/gallery/activities/VideoActivity.kt rename to app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/VideoActivity.kt index fa38734e3..78fa61955 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/VideoActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/VideoActivity.kt @@ -1,11 +1,11 @@ -package com.simplemobiletools.gallery.activities +package com.simplemobiletools.gallery.pro.activities import android.os.Bundle class VideoActivity : PhotoVideoActivity() { override fun onCreate(savedInstanceState: Bundle?) { - PhotoVideoActivity.mIsVideo = true + mIsVideo = true super.onCreate(savedInstanceState) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/VideoPlayerActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/VideoPlayerActivity.kt new file mode 100644 index 000000000..b1c319a1e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/VideoPlayerActivity.kt @@ -0,0 +1,633 @@ +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.ensureBackgroundThread +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() + initPlayer() + } + + 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 { doSkip(false) } + video_duration.setOnClickListener { doSkip(true) } + video_toggle_play_pause.setOnClickListener { togglePlayPause() } + video_surface_frame.setOnClickListener { toggleFullscreen() } + video_surface_frame.controller.settings.swallowDoubleTaps = true + + 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() } + + + val gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() { + override fun onDoubleTap(e: MotionEvent?): Boolean { + if (e != null) { + handleDoubleTap(e.rawX) + } + + return true + } + }) + + video_surface_frame.setOnTouchListener { view, event -> + handleEvent(event) + gestureDetector.onTouchEvent(event) + false + } + + initExoPlayer() + video_surface.surfaceTextureListener = this + + if (config.allowVideoGestures) { + video_brightness_controller.initialize(this, slide_info, true, video_player_holder, singleTap = { x, y -> + toggleFullscreen() + }, doubleTap = { x, y -> + doSkip(false) + }) + + video_volume_controller.initialize(this, slide_info, false, video_player_holder, singleTap = { x, y -> + toggleFullscreen() + }, doubleTap = { x, y -> + doSkip(true) + }) + } 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 + if (config.loopVideos) { + repeatMode = Player.REPEAT_MODE_ONE + } + 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) { + // Reset progress views when video loops. + if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) { + video_seekbar.progress = 0 + video_curr_time.text = 0.getFormattedDuration() + } + } + + 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_vector) + } + } + } + + private fun handleDoubleTap(x: Float) { + val instantWidth = mScreenWidth / 7 + when { + x <= instantWidth -> doSkip(false) + x >= mScreenWidth - instantWidth -> doSkip(true) + else -> togglePlayPause() + } + } + + private fun resumeVideo() { + video_toggle_play_pause.setImageResource(R.drawable.ic_pause_outline_vector) + 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_vector) + 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() { + val pos = config.getLastVideoPosition(mUri.toString()) + if (pos > 0) { + setPosition(pos) + } + } + + private fun videoCompleted() { + if (mExoPlayer == null) { + return + } + + clearLastVideoSavedProgress() + mCurrTime = (mExoPlayer!!.duration / 1000).toInt() + 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.saveLastVideoPosition(mUri.toString(), mExoPlayer!!.currentPosition.toInt() / 1000) + } + } + + private fun clearLastVideoSavedProgress() { + config.removeLastVideoPosition(mUri.toString()) + } + + 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 doSkip(forward: Boolean) { + if (mExoPlayer == null) { + return + } + + val curr = mExoPlayer!!.currentPosition + val newProgress = if (forward) curr + FAST_FORWARD_VIDEO_MS else curr - FAST_FORWARD_VIDEO_MS + val roundProgress = Math.round(newProgress / 1000f) + val limitedProgress = Math.max(Math.min(mExoPlayer!!.duration.toInt() / 1000, 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() + ensureBackgroundThread { + mExoPlayer?.release() + mExoPlayer = null + } + } + + 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) { + ensureBackgroundThread { + mExoPlayer?.setVideoSurface(Surface(video_surface!!.surfaceTexture)) + } + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {} +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ViewPagerActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ViewPagerActivity.kt new file mode 100644 index 000000000..264133ecd --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ViewPagerActivity.kt @@ -0,0 +1,1309 @@ +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.pm.ShortcutInfo +import android.content.pm.ShortcutManager +import android.content.res.Configuration +import android.database.Cursor +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Icon +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.provider.MediaStore.Images +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.WindowManager +import android.view.animation.DecelerateInterpolator +import android.widget.Toast +import androidx.exifinterface.media.ExifInterface +import androidx.print.PrintHelper +import androidx.viewpager.widget.ViewPager +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +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.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.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.ResizeWithPathDialog +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.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 mPrevHashcode = 0 + + private var mSlideshowHandler = Handler() + private var mSlideshowInterval = SLIDESHOW_DEFAULT_INTERVAL + private var mSlideshowMoveBackwards = false + private var mSlideshowMedia = mutableListOf() + private var mAreSlideShowMediaVisible = false + + private var mIsOrientationLocked = false + + private var mMediaFiles = ArrayList() + private var mFavoritePaths = ArrayList() + private var mIgnoredPaths = ArrayList() + + override fun onCreate(savedInstanceState: Bundle?) { + useDynamicTheme = false + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_medium) + + window.decorView.setBackgroundColor(config.backgroundColor) + top_shadow.layoutParams.height = statusBarHeight + actionBarHeight + checkNotchSupport() + (MediaActivity.mMedia.clone() as ArrayList).filter { it is Medium }.mapTo(mMediaFiles) { it as Medium } + + handlePermission(PERMISSION_WRITE_STORAGE) { + if (it) { + initViewPager() + } else { + toast(R.string.no_storage_permissions) + finish() + } + } + + initFavorites() + } + + override fun onResume() { + super.onResume() + if (!hasPermission(PERMISSION_WRITE_STORAGE)) { + finish() + return + } + + if (config.bottomActions) { + window.navigationBarColor = Color.TRANSPARENT + } else { + setTranslucentNavigation() + } + + initBottomActions() + + if (config.maxBrightness) { + val attributes = window.attributes + attributes.screenBrightness = 1f + window.attributes = attributes + } + + setupOrientation() + invalidateOptionsMenu() + + supportActionBar?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + val filename = getCurrentMedium()?.name ?: mPath.getFilenameFromPath() + supportActionBar?.title = filename + window.statusBarColor = Color.TRANSPARENT + } + + override fun onPause() { + super.onPause() + stopSlideshow() + } + + override fun onDestroy() { + super.onDestroy() + if (intent.extras?.containsKey(IS_VIEW_INTENT) == true) { + config.temporarilyShowHidden = false + } + + if (config.isThirdPartyIntent) { + config.isThirdPartyIntent = false + + if (intent.extras == null || !intent.getBooleanExtra(IS_FROM_GALLERY, false)) { + mMediaFiles.clear() + } + } + } + + 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_move_to).isVisible = visibleBottomActions and BOTTOM_ACTION_MOVE == 0 + findItem(R.id.menu_save_as).isVisible = rotationDegrees != 0 + findItem(R.id.menu_print).isVisible = currentMedium.isImage() || currentMedium.isRaw() + findItem(R.id.menu_resize).isVisible = visibleBottomActions and BOTTOM_ACTION_RESIZE == 0 && currentMedium.isImage() + 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 && !currentMedium.getIsInRecycleBin() + findItem(R.id.menu_remove_from_favorites).isVisible = currentMedium.isFavorite && visibleBottomActions and BOTTOM_ACTION_TOGGLE_FAVORITE == 0 && !currentMedium.getIsInRecycleBin() + findItem(R.id.menu_restore_file).isVisible = currentMedium.path.startsWith(recycleBinPath) + findItem(R.id.menu_create_shortcut).isVisible = isOreoPlus() + 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) + } + + updateMenuItemColors(menu, baseColor = Color.BLACK) + 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_print -> printFile() + R.id.menu_edit -> openEditor(getCurrentPath()) + R.id.menu_properties -> showProperties() + R.id.menu_show_on_map -> showFileOnMap(getCurrentPath()) + 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_create_shortcut -> createShortcut() + R.id.menu_resize -> resizeImage() + R.id.menu_settings -> launchSettings() + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { + 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) + } + + private fun initViewPager() { + val uri = intent.data + if (uri != null) { + var cursor: Cursor? = null + try { + val proj = arrayOf(Images.Media.DATA) + cursor = contentResolver.query(uri, proj, null, null, null) + if (cursor?.moveToFirst() == true) { + mPath = cursor.getStringValue(Images.Media.DATA) + } + } finally { + cursor?.close() + } + } else { + try { + mPath = intent.getStringExtra(PATH) + mShowAll = config.showAll + } catch (e: Exception) { + showErrorToast(e) + finish() + return + } + } + + if (intent.extras?.containsKey(REAL_FILE_PATH) == true) { + mPath = intent.extras!!.getString(REAL_FILE_PATH)!! + } + + if (mPath.isEmpty()) { + toast(R.string.unknown_error_occurred) + finish() + return + } + + if (mPath.isPortrait() && getPortraitPath() == "") { + val newIntent = Intent(this, ViewPagerActivity::class.java) + newIntent.putExtras(intent!!.extras!!) + newIntent.putExtra(PORTRAIT_PATH, mPath) + newIntent.putExtra(PATH, "${mPath.getParentPath().getParentPath()}/${mPath.getFilenameFromPath()}") + + startActivity(newIntent) + finish() + return + } + + if (!getDoesFilePathExist(mPath) && getPortraitPath() == "") { + finish() + return + } + + showSystemUI(true) + + if (intent.getBooleanExtra(SKIP_AUTHENTICATION, false)) { + initContinue() + } else { + handleLockedFolderOpening(mPath.getParentPath()) { success -> + if (success) { + initContinue() + } else { + finish() + } + } + } + } + + private fun initContinue() { + if (intent.extras?.containsKey(IS_VIEW_INTENT) == true) { + if (isShowHiddenFlagNeeded()) { + if (!config.isHiddenPasswordProtectionOn) { + config.temporarilyShowHidden = true + } + } + + config.isThirdPartyIntent = true + } + + val isShowingFavorites = intent.getBooleanExtra(SHOW_FAVORITES, false) + val isShowingRecycleBin = intent.getBooleanExtra(SHOW_RECYCLE_BIN, false) + mDirectory = when { + isShowingFavorites -> FAVORITES + isShowingRecycleBin -> RECYCLE_BIN + else -> mPath.getParentPath() + } + supportActionBar?.title = mPath.getFilenameFromPath() + + view_pager.onGlobalLayout { + if (!isDestroyed) { + if (mMediaFiles.isNotEmpty()) { + gotMedia(mMediaFiles as ArrayList) + checkSlideshowOnEnter() + } + } + } + + refreshViewPager() + view_pager.offscreenPageLimit = 2 + + if (config.blackBackground) { + view_pager.background = ColorDrawable(Color.BLACK) + } + + if (config.hideSystemUI) { + view_pager.onGlobalLayout { + Handler().postDelayed({ + fragmentClicked() + }, HIDE_SYSTEM_UI_DELAY) + } + } + + window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> + mIsFullScreen = if (visibility and View.SYSTEM_UI_FLAG_LOW_PROFILE == 0) { + false + } else { + visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0 + } + + checkSystemUI() + fullscreenToggled() + } + + if (intent.action == "com.android.camera.action.REVIEW") { + ensureBackgroundThread { + if (mediaDB.getMediaFromPath(mPath).isEmpty()) { + val type = when { + mPath.isVideoFast() -> TYPE_VIDEOS + mPath.isGif() -> TYPE_GIFS + mPath.isSvg() -> TYPE_SVGS + mPath.isRawFast() -> TYPE_RAWS + mPath.isPortrait() -> TYPE_PORTRAITS + else -> TYPE_IMAGES + } + + val isFavorite = favoritesDB.isFavorite(mPath) + val duration = if (type == TYPE_VIDEOS) getDuration(mPath) ?: 0 else 0 + val ts = System.currentTimeMillis() + val medium = Medium(null, mPath.getFilenameFromPath(), mPath, mPath.getParentPath(), ts, ts, File(mPath).length(), type, duration, isFavorite, 0) + mediaDB.insert(medium) + } + } + } + } + + private fun initBottomActions() { + initBottomActionButtons() + initBottomActionsLayout() + } + + private fun initFavorites() { + ensureBackgroundThread { + mFavoritePaths = getFavoritePaths() + } + } + + 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 updatePagerItems(media: MutableList) { + val pagerAdapter = MyPagerAdapter(this, supportFragmentManager, media) + if (!isDestroyed) { + pagerAdapter.shouldInitFragment = mPos < 5 + view_pager.apply { + adapter = pagerAdapter + pagerAdapter.shouldInitFragment = true + currentItem = mPos + removeOnPageChangeListener(this@ViewPagerActivity) + addOnPageChangeListener(this@ViewPagerActivity) + } + } + } + + private fun checkSlideshowOnEnter() { + if (intent.getBooleanExtra(SLIDESHOW_START_ON_ENTER, false)) { + initSlideshow() + } + } + + private fun initSlideshow() { + SlideshowDialog(this) { + startSlideshow() + } + } + + private fun startSlideshow() { + if (getMediaForSlideshow()) { + view_pager.onGlobalLayout { + if (!isDestroyed) { + if (config.slideshowAnimation == SLIDESHOW_ANIMATION_FADE) { + view_pager.setPageTransformer(false, FadePageTransformer()) + } + + hideSystemUI(true) + mSlideshowInterval = config.slideshowInterval + mSlideshowMoveBackwards = config.slideshowMoveBackwards + mIsSlideshowActive = true + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + scheduleSwipe() + } + } + } + } + + private fun goToNextMedium(forward: Boolean) { + val oldPosition = view_pager.currentItem + val newPosition = if (forward) oldPosition + 1 else oldPosition - 1 + if (newPosition == -1 || newPosition > view_pager.adapter!!.count - 1) { + slideshowEnded(forward) + } else { + view_pager.setCurrentItem(newPosition, false) + } + } + + private fun animatePagerTransition(forward: Boolean) { + val oldPosition = view_pager.currentItem + val animator = ValueAnimator.ofInt(0, view_pager.width) + animator.addListener(object : Animator.AnimatorListener { + override fun onAnimationRepeat(animation: Animator?) { + } + + override fun onAnimationEnd(animation: Animator?) { + if (view_pager.isFakeDragging) { + try { + view_pager.endFakeDrag() + } catch (ignored: Exception) { + stopSlideshow() + } + + if (view_pager.currentItem == oldPosition) { + slideshowEnded(forward) + } + } + } + + override fun onAnimationCancel(animation: Animator?) { + view_pager.endFakeDrag() + } + + override fun onAnimationStart(animation: Animator?) { + } + }) + + if (config.slideshowAnimation == SLIDESHOW_ANIMATION_SLIDE) { + animator.interpolator = DecelerateInterpolator() + animator.duration = SLIDESHOW_SLIDE_DURATION + } else { + animator.duration = SLIDESHOW_FADE_DURATION + } + + animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { + var oldDragPosition = 0 + override fun onAnimationUpdate(animation: ValueAnimator) { + if (view_pager?.isFakeDragging == true) { + val dragPosition = animation.animatedValue as Int + val dragOffset = dragPosition - oldDragPosition + oldDragPosition = dragPosition + try { + view_pager.fakeDragBy(dragOffset * (if (forward) -1f else 1f)) + } catch (e: Exception) { + stopSlideshow() + } + } + } + }) + + view_pager.beginFakeDrag() + animator.start() + } + + private fun slideshowEnded(forward: Boolean) { + if (config.loopSlideshow) { + if (forward) { + view_pager.setCurrentItem(0, false) + } else { + view_pager.setCurrentItem(view_pager.adapter!!.count - 1, false) + } + } else { + stopSlideshow() + toast(R.string.slideshow_ended) + } + } + + private fun stopSlideshow() { + if (mIsSlideshowActive) { + view_pager.setPageTransformer(false, DefaultPageTransformer()) + mIsSlideshowActive = false + showSystemUI(true) + mSlideshowHandler.removeCallbacksAndMessages(null) + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + } + + private fun scheduleSwipe() { + mSlideshowHandler.removeCallbacksAndMessages(null) + if (mIsSlideshowActive) { + if (getCurrentMedium()!!.isImage() || getCurrentMedium()!!.isGIF() || getCurrentMedium()!!.isPortrait()) { + mSlideshowHandler.postDelayed({ + if (mIsSlideshowActive && !isDestroyed) { + swipeToNextMedium() + } + }, mSlideshowInterval * 1000L) + } else { + (getCurrentFragment() as? VideoFragment)!!.playVideo() + } + } + } + + private fun swipeToNextMedium() { + if (config.slideshowAnimation == SLIDESHOW_ANIMATION_NONE) { + goToNextMedium(!mSlideshowMoveBackwards) + } else { + animatePagerTransition(!mSlideshowMoveBackwards) + } + } + + private fun getMediaForSlideshow(): Boolean { + mSlideshowMedia = mMediaFiles.filter { + it.isImage() || it.isPortrait() || (config.slideshowIncludeVideos && it.isVideo() || (config.slideshowIncludeGIFs && it.isGIF())) + }.toMutableList() + + if (config.slideshowRandomOrder) { + mSlideshowMedia.shuffle() + mPos = 0 + } else { + mPath = getCurrentPath() + mPos = getPositionInList(mSlideshowMedia) + } + + return if (mSlideshowMedia.isEmpty()) { + toast(R.string.no_media_for_slideshow) + false + } else { + updatePagerItems(mSlideshowMedia) + mAreSlideShowMediaVisible = true + true + } + } + + 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) { + val newPath = "$it/${currPath.getFilenameFromPath()}" + rescanPaths(arrayListOf(newPath)) { + fixDateTaken(arrayListOf(newPath), false) + } + + config.tempFolderPath = "" + if (!isCopyOperation) { + refreshViewPager() + updateFavoritePaths(fileDirItems, it) + } + } + } + + private fun toggleFileVisibility(hide: Boolean, callback: (() -> Unit)? = null) { + toggleFileVisibility(getCurrentPath(), hide) { + val newFileName = it.getFilenameFromPath() + supportActionBar?.title = newFileName + + getCurrentMedium()!!.apply { + name = newFileName + path = it + getCurrentMedia()[mPos] = this + } + invalidateOptionsMenu() + callback?.invoke() + } + } + + private fun rotateImage(degrees: Int) { + val currentPath = getCurrentPath() + if (needsStupidWritePermissions(currentPath)) { + handleSAFDialog(currentPath) { + if (it) { + rotateBy(degrees) + } + } + } else { + rotateBy(degrees) + } + } + + private fun rotateBy(degrees: Int) { + getCurrentPhotoFragment()?.rotateImageViewBy(degrees) + supportInvalidateOptionsMenu() + } + + private fun toggleOrientation(orientation: Int) { + requestedOrientation = orientation + mIsOrientationLocked = orientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + invalidateOptionsMenu() + } + + private fun getChangeOrientationIcon(): Int { + return if (mIsOrientationLocked) { + if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { + R.drawable.ic_orientation_portrait_vector + } else { + R.drawable.ic_orientation_landscape_vector + } + } else { + R.drawable.ic_orientation_auto_vector + } + } + + private fun saveImageAs() { + val currPath = getCurrentPath() + SaveAsDialog(this, currPath, false) { + val newPath = it + handleSAFDialog(it) { + if (!it) { + return@handleSAFDialog + } + + toast(R.string.saving) + ensureBackgroundThread { + val photoFragment = getCurrentPhotoFragment() ?: return@ensureBackgroundThread + saveRotatedImageToFile(currPath, newPath, photoFragment.mCurrentRotationDegrees, true) { + toast(R.string.file_saved) + getCurrentPhotoFragment()?.mCurrentRotationDegrees = 0 + invalidateOptionsMenu() + } + } + } + } + } + + @SuppressLint("NewApi") + private fun createShortcut() { + val manager = getSystemService(ShortcutManager::class.java) + if (manager.isRequestPinShortcutSupported) { + val medium = getCurrentMedium() ?: return + val path = medium.path + val drawable = resources.getDrawable(R.drawable.shortcut_image).mutate() + getShortcutImage(path, drawable) { + val intent = Intent(this, ViewPagerActivity::class.java).apply { + putExtra(PATH, path) + putExtra(SHOW_ALL, config.showAll) + putExtra(SHOW_FAVORITES, path == FAVORITES) + putExtra(SHOW_RECYCLE_BIN, path == RECYCLE_BIN) + action = Intent.ACTION_VIEW + flags = flags or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + + val shortcut = ShortcutInfo.Builder(this, path) + .setShortLabel(medium.name) + .setIcon(Icon.createWithBitmap(drawable.convertToBitmap())) + .setIntent(intent) + .build() + + manager.requestPinShortcut(shortcut, null) + } + } + } + + private fun getCurrentPhotoFragment() = getCurrentFragment() as? PhotoFragment + + private fun getPortraitPath() = intent.getStringExtra(PORTRAIT_PATH) ?: "" + + private fun isShowHiddenFlagNeeded(): Boolean { + val file = File(mPath) + if (file.isHidden) { + return true + } + + var parent = file.parentFile ?: return false + while (true) { + if (parent.isHidden || parent.list()?.any { it.startsWith(NOMEDIA) } == true) { + return true + } + + if (parent.absolutePath == "/") { + break + } + parent = parent.parentFile ?: return false + } + + return false + } + + private fun getCurrentFragment() = (view_pager.adapter as? MyPagerAdapter)?.getCurrentFragment(view_pager.currentItem) + + private fun showProperties() { + if (getCurrentMedium() != null) { + PropertiesDialog(this, getCurrentPath(), false) + } + } + + private fun initBottomActionsLayout() { + bottom_actions.layoutParams.height = resources.getDimension(R.dimen.bottom_actions_height).toInt() + navigationBarHeight + if (config.bottomActions) { + bottom_actions.beVisible() + } else { + bottom_actions.beGone() + } + } + + private fun initBottomActionButtons() { + val currentMedium = getCurrentMedium() + val visibleBottomActions = if (config.bottomActions) config.visibleBottomActions else 0 + bottom_favorite.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_TOGGLE_FAVORITE != 0 && currentMedium?.getIsInRecycleBin() == false) + bottom_favorite.setOnClickListener { + toggleFavorite() + } + + bottom_edit.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_EDIT != 0 && currentMedium?.isSVG() == false) + bottom_edit.setOnClickListener { + openEditor(getCurrentPath()) + } + + bottom_share.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_SHARE != 0) + bottom_share.setOnClickListener { + shareMediumPath(getCurrentPath()) + } + + bottom_delete.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_DELETE != 0) + bottom_delete.setOnClickListener { + checkDeleteConfirmation() + } + + bottom_rotate.setOnClickListener { + rotateImage(90) + } + + bottom_properties.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_PROPERTIES != 0) + bottom_properties.setOnClickListener { + showProperties() + } + + bottom_change_orientation.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_CHANGE_ORIENTATION != 0) + bottom_change_orientation.setOnClickListener { + requestedOrientation = when (requestedOrientation) { + ActivityInfo.SCREEN_ORIENTATION_PORTRAIT -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + else -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + mIsOrientationLocked = requestedOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + updateBottomActionIcons(currentMedium) + } + + bottom_slideshow.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_SLIDESHOW != 0) + bottom_slideshow.setOnClickListener { + initSlideshow() + } + + bottom_show_on_map.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_SHOW_ON_MAP != 0) + bottom_show_on_map.setOnClickListener { + showFileOnMap(getCurrentPath()) + } + + bottom_toggle_file_visibility.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_TOGGLE_VISIBILITY != 0) + bottom_toggle_file_visibility.setOnClickListener { + currentMedium?.apply { + toggleFileVisibility(!isHidden()) { + updateBottomActionIcons(currentMedium) + } + } + } + + bottom_rename.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_RENAME != 0 && currentMedium?.getIsInRecycleBin() == false) + bottom_rename.setOnClickListener { + renameFile() + } + + bottom_set_as.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_SET_AS != 0) + bottom_set_as.setOnClickListener { + setAs(getCurrentPath()) + } + + bottom_copy.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_COPY != 0) + bottom_copy.setOnClickListener { + copyMoveTo(true) + } + + bottom_move.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_MOVE != 0) + bottom_move.setOnClickListener { + moveFileTo() + } + + bottom_resize.beVisibleIf(visibleBottomActions and BOTTOM_ACTION_RESIZE != 0 && currentMedium?.isImage() == true) + bottom_resize.setOnClickListener { + resizeImage() + } + } + + private fun updateBottomActionIcons(medium: Medium?) { + if (medium == null) { + return + } + + val favoriteIcon = if (medium.isFavorite) R.drawable.ic_star_on_vector else R.drawable.ic_star_off_vector + bottom_favorite.setImageResource(favoriteIcon) + + val hideIcon = if (medium.isHidden()) R.drawable.ic_unhide_vector else R.drawable.ic_hide + bottom_toggle_file_visibility.setImageResource(hideIcon) + + bottom_rotate.beVisibleIf(config.visibleBottomActions and BOTTOM_ACTION_ROTATE != 0 && getCurrentMedium()?.isImage() == true) + bottom_change_orientation.setImageResource(getChangeOrientationIcon()) + } + + private fun toggleFavorite() { + val medium = getCurrentMedium() ?: return + medium.isFavorite = !medium.isFavorite + ensureBackgroundThread { + updateFavorite(medium.path, medium.isFavorite) + if (medium.isFavorite) { + mFavoritePaths.add(medium.path) + } else { + mFavoritePaths.remove(medium.path) + } + invalidateOptionsMenu() + } + } + + private fun printFile() { + sendPrintIntent(getCurrentPath()) + } + + private fun sendPrintIntent(path: String) { + val printHelper = PrintHelper(this) + printHelper.scaleMode = PrintHelper.SCALE_MODE_FIT + printHelper.orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + + try { + val resolution = path.getImageResolution() + if (resolution == null) { + toast(R.string.unknown_error_occurred) + return + } + + var requestedWidth = resolution.x + var requestedHeight = resolution.y + + if (requestedWidth >= MAX_PRINT_SIDE_SIZE) { + requestedHeight = (requestedHeight / (requestedWidth / MAX_PRINT_SIDE_SIZE.toFloat())).toInt() + requestedWidth = MAX_PRINT_SIDE_SIZE + } else if (requestedHeight >= MAX_PRINT_SIDE_SIZE) { + requestedWidth = (requestedWidth / (requestedHeight / MAX_PRINT_SIDE_SIZE.toFloat())).toInt() + requestedHeight = MAX_PRINT_SIDE_SIZE + } + + val options = RequestOptions() + .skipMemoryCache(true) + .diskCacheStrategy(DiskCacheStrategy.NONE) + + Glide.with(this) + .asBitmap() + .load(path) + .apply(options) + .listener(object : RequestListener { + override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { + showErrorToast(e?.localizedMessage ?: "") + return false + } + + override fun onResourceReady(bitmap: Bitmap?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { + if (bitmap != null) { + printHelper.printBitmap(path.getFilenameFromPath(), bitmap) + } + + return false + } + }).submit(requestedWidth, requestedHeight) + } catch (e: Exception) { + } + } + + private fun restoreFile() { + restoreRecycleBinPath(getCurrentPath()) { + refreshViewPager() + } + } + + @TargetApi(Build.VERSION_CODES.N) + private fun resizeImage() { + val oldPath = getCurrentPath() + val originalSize = oldPath.getImageResolution() ?: return + ResizeWithPathDialog(this, originalSize, oldPath) { newSize, newPath -> + ensureBackgroundThread { + try { + var oldExif: ExifInterface? = null + if (isNougatPlus()) { + val inputStream = contentResolver.openInputStream(Uri.fromFile(File(oldPath))) + oldExif = ExifInterface(inputStream!!) + } + + val newBitmap = Glide.with(applicationContext).asBitmap().load(oldPath).submit(newSize.x, newSize.y).get() + + val newFile = File(newPath) + val newFileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath()) + getFileOutputStream(newFileDirItem, true) { + if (it != null) { + saveBitmap(newFile, newBitmap, it, oldExif, File(oldPath).lastModified()) + } else { + toast(R.string.image_editing_failed) + } + } + } catch (e: OutOfMemoryError) { + toast(R.string.out_of_memory_error) + } catch (e: Exception) { + showErrorToast(e) + } + } + } + } + + @TargetApi(Build.VERSION_CODES.N) + private fun saveBitmap(file: File, bitmap: Bitmap, out: OutputStream, oldExif: ExifInterface?, lastModified: Long) { + try { + bitmap.compress(file.absolutePath.getCompressionFormat(), 90, out) + + if (isNougatPlus()) { + val newExif = ExifInterface(file.absolutePath) + oldExif?.copyNonDimensionAttributesTo(newExif) + } + } catch (e: Exception) { + } + + toast(R.string.file_saved) + val paths = arrayListOf(file.absolutePath) + rescanPaths(paths) { + fixDateTaken(paths, false) + + if (config.keepLastModified) { + File(file.absolutePath).setLastModified(lastModified) + updateLastModified(file.absolutePath, lastModified) + } + } + out.close() + } + + private fun checkDeleteConfirmation() { + if (getCurrentMedium() == null) { + return + } + + if (config.isDeletePasswordProtectionOn) { + handleDeletePasswordProtection { + deleteConfirmed() + } + } else if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) { + deleteConfirmed() + } else { + askConfirmDelete() + } + } + + private fun askConfirmDelete() { + 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() + } + } + + private fun deleteConfirmed() { + val path = getCurrentMedia().getOrNull(mPos)?.path ?: return + if (getIsPathDirectory(path) || !path.isMediaFile()) { + return + } + + val fileDirItem = FileDirItem(path, path.getFilenameFromPath()) + if (config.useRecycleBin && !getCurrentMedium()!!.getIsInRecycleBin()) { + mIgnoredPaths.add(fileDirItem.path) + val media = mMediaFiles.filter { !mIgnoredPaths.contains(it.path) } as ArrayList + runOnUiThread { + gotMedia(media, true) + } + + movePathsInRecycleBin(arrayListOf(path)) { + if (it) { + tryDeleteFileDirItem(fileDirItem, false, false) { + mIgnoredPaths.remove(fileDirItem.path) + deleteDirectoryIfEmpty() + } + } else { + toast(R.string.unknown_error_occurred) + } + } + } else { + handleDeletion(fileDirItem) + } + } + + private fun handleDeletion(fileDirItem: FileDirItem) { + mIgnoredPaths.add(fileDirItem.path) + val media = mMediaFiles.filter { !mIgnoredPaths.contains(it.path) } as ArrayList + runOnUiThread { + gotMedia(media, true) + } + + tryDeleteFileDirItem(fileDirItem, false, true) { + mIgnoredPaths.remove(fileDirItem.path) + deleteDirectoryIfEmpty() + } + } + + private fun isDirEmpty(media: ArrayList): Boolean { + return if (media.isEmpty()) { + deleteDirectoryIfEmpty() + finish() + true + } else { + false + } + } + + private fun renameFile() { + val oldPath = getCurrentPath() + RenameItemDialog(this, oldPath) { + getCurrentMedia()[mPos].apply { + path = it + name = it.getFilenameFromPath() + } + + ensureBackgroundThread { + updateDBMediaPath(oldPath, it) + } + updateActionbarTitle() + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + initBottomActionsLayout() + } + + private fun refreshViewPager() { + if (config.getFolderSorting(mDirectory) and SORT_BY_RANDOM == 0) { + GetMediaAsynctask(applicationContext, mDirectory, false, false, mShowAll) { + gotMedia(it) + }.execute() + } + } + + private fun gotMedia(thumbnailItems: ArrayList, ignorePlayingVideos: Boolean = false) { + val media = thumbnailItems.asSequence().filter { it is Medium && !mIgnoredPaths.contains(it.path) }.map { it as Medium }.toMutableList() as ArrayList + if (isDirEmpty(media) || media.hashCode() == mPrevHashcode) { + return + } + + if (!ignorePlayingVideos && (getCurrentFragment() as? VideoFragment)?.mIsPlaying == true) { + return + } + + mPrevHashcode = media.hashCode() + mMediaFiles = media + mPos = if (mPos == -1) { + getPositionInList(media) + } else { + Math.min(mPos, mMediaFiles.size - 1) + } + + updateActionbarTitle() + updatePagerItems(mMediaFiles.toMutableList()) + invalidateOptionsMenu() + checkOrientation() + initBottomActions() + } + + private fun getPositionInList(items: MutableList): Int { + mPos = 0 + for ((i, medium) in items.withIndex()) { + val portraitPath = getPortraitPath() + if (portraitPath != "") { + val portraitPaths = File(portraitPath).parentFile?.list() + if (portraitPaths != null) { + for (path in portraitPaths) { + if (medium.name == path) { + return i + } + } + } + } else if (medium.path == mPath) { + return i + } + } + return mPos + } + + private fun deleteDirectoryIfEmpty() { + val fileDirItem = FileDirItem(mDirectory, mDirectory.getFilenameFromPath(), File(mDirectory).isDirectory) + if (config.deleteEmptyFolders && !fileDirItem.isDownloadsFolder() && fileDirItem.isDirectory && fileDirItem.getProperFileCount(this, true) == 0) { + tryDeleteFileDirItem(fileDirItem, true, true) + scanPathRecursively(mDirectory) + } + } + + @SuppressLint("SourceLockedOrientationActivity") + private fun checkOrientation() { + if (!mIsOrientationLocked && config.screenRotation == ROTATE_BY_ASPECT_RATIO) { + var flipSides = false + try { + val pathToLoad = getCurrentPath() + val exif = ExifInterface(pathToLoad) + val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1) + flipSides = orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270 + } catch (e: Exception) { + } + val resolution = applicationContext.getResolution(getCurrentPath()) ?: return + val width = if (flipSides) resolution.y else resolution.x + val height = if (flipSides) resolution.x else resolution.y + if (width > height) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } else if (width < height) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + } + } + + override fun fragmentClicked() { + mIsFullScreen = !mIsFullScreen + checkSystemUI() + fullscreenToggled() + } + + override fun videoEnded(): Boolean { + if (mIsSlideshowActive) { + swipeToNextMedium() + } + return mIsSlideshowActive + } + + override fun isSlideShowActive() = mIsSlideshowActive + + override fun goToPrevItem() { + view_pager.setCurrentItem(view_pager.currentItem - 1, false) + checkOrientation() + } + + override fun goToNextItem() { + view_pager.setCurrentItem(view_pager.currentItem + 1, false) + checkOrientation() + } + + override fun launchViewVideoIntent(path: String) { + ensureBackgroundThread { + val newUri = getFinalUriFromPath(path, BuildConfig.APPLICATION_ID) ?: return@ensureBackgroundThread + 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) + } + } + } + } + } + + private fun checkSystemUI() { + if (mIsFullScreen) { + hideSystemUI(true) + } else { + stopSlideshow() + showSystemUI(true) + } + } + + private fun fullscreenToggled() { + view_pager.adapter?.let { + (it as MyPagerAdapter).toggleFullscreen(mIsFullScreen) + 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, bottom_set_as, bottom_copy, bottom_move, + bottom_resize).forEach { + it.isClickable = !mIsFullScreen + } + } + } + } + + private fun updateActionbarTitle() { + runOnUiThread { + if (mPos < getCurrentMedia().size) { + supportActionBar?.title = getCurrentMedia()[mPos].path.getFilenameFromPath() + } + } + } + + private fun getCurrentMedium(): Medium? { + return if (getCurrentMedia().isEmpty() || mPos == -1) { + null + } else { + getCurrentMedia()[Math.min(mPos, getCurrentMedia().size - 1)] + } + } + + private fun getCurrentMedia() = if (mAreSlideShowMediaVisible) mSlideshowMedia else mMediaFiles + + private fun getCurrentPath() = getCurrentMedium()?.path ?: "" + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} + + override fun onPageSelected(position: Int) { + if (mPos != position) { + mPos = position + updateActionbarTitle() + invalidateOptionsMenu() + scheduleSwipe() + } + } + + override fun onPageScrollStateChanged(state: Int) { + if (state == ViewPager.SCROLL_STATE_IDLE && getCurrentMedium() != null) { + checkOrientation() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/WidgetConfigureActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/WidgetConfigureActivity.kt new file mode 100644 index 000000000..4521e64a1 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/WidgetConfigureActivity.kt @@ -0,0 +1,183 @@ +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.bumptech.glide.signature.ObjectKey +import com.simplemobiletools.commons.dialogs.ColorPickerDialog +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +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.helpers.ROUNDED_CORNERS_NONE +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() + + 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 + 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) + ensureBackgroundThread { + widgetsDB.insertOrUpdate(widget) + } + + 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, true) { + updateFolderImage(it) + } + } + + private fun updateFolderImage(folderPath: String) { + mFolderPath = folderPath + runOnUiThread { + folder_picker_value.text = getFolderNameFromPath(folderPath) + config_folder_name.text = getFolderNameFromPath(folderPath) + } + + ensureBackgroundThread { + val path = directoryDao.getDirectoryThumbnail(folderPath) + if (path != null) { + runOnUiThread { + val signature = ObjectKey(System.currentTimeMillis().toString()) + loadJpg(path, config_image, config.cropThumbnails, ROUNDED_CORNERS_NONE, signature) + } + } + } + } + + 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() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/DirectoryAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/DirectoryAdapter.kt new file mode 100644 index 000000000..177bb91e8 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/DirectoryAdapter.kt @@ -0,0 +1,760 @@ +package com.simplemobiletools.gallery.pro.adapters + +import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Icon +import android.text.TextUtils +import android.view.Menu +import android.view.View +import android.view.ViewGroup +import android.widget.RelativeLayout +import com.bumptech.glide.Glide +import com.google.gson.Gson +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter +import com.simplemobiletools.commons.dialogs.* +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.commons.models.FileDirItem +import com.simplemobiletools.commons.views.FastScroller +import com.simplemobiletools.commons.views.MyRecyclerView +import com.simplemobiletools.gallery.pro.R +import com.simplemobiletools.gallery.pro.activities.MediaActivity +import com.simplemobiletools.gallery.pro.dialogs.ConfirmDeleteFolderDialog +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_grid_square.view.dir_check +import kotlinx.android.synthetic.main.directory_item_grid_square.view.dir_location +import kotlinx.android.synthetic.main.directory_item_grid_square.view.dir_lock +import kotlinx.android.synthetic.main.directory_item_grid_square.view.dir_name +import kotlinx.android.synthetic.main.directory_item_grid_square.view.dir_pin +import kotlinx.android.synthetic.main.directory_item_grid_square.view.dir_thumbnail +import kotlinx.android.synthetic.main.directory_item_list.view.* +import java.io.File + +class DirectoryAdapter(activity: BaseSimpleActivity, var dirs: ArrayList, val listener: DirectoryOperationsListener?, recyclerView: MyRecyclerView, + val isPickIntent: Boolean, fastScroller: FastScroller? = null, itemClick: (Any) -> Unit) : + MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { + + private val config = activity.config + private val isListViewType = config.viewTypeFolders == VIEW_TYPE_LIST + private var pinnedFolders = config.pinnedFolders + private var scrollHorizontally = config.scrollHorizontally + private var animateGifs = config.animateGifs + private var cropThumbnails = config.cropThumbnails + private var groupDirectSubfolders = config.groupDirectSubfolders + private var currentDirectoriesHash = dirs.hashCode() + private var lockedFolderPaths = ArrayList() + + private var showMediaCount = config.showFolderMediaCount + private var folderStyle = config.folderStyle + private var limitFolderTitle = config.limitFolderTitle + + init { + setupDragListener(true) + fillLockedFolders() + } + + override fun getActionMenuId() = R.menu.cab_directories + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val layoutType = when { + isListViewType -> R.layout.directory_item_list + folderStyle == FOLDER_STYLE_SQUARE -> R.layout.directory_item_grid_square + else -> R.layout.directory_item_grid_rounded_corners + } + + return createViewHolder(layoutType, parent) + } + + override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) { + val dir = dirs.getOrNull(position) ?: return + holder.bindView(dir, true, !isPickIntent) { itemView, adapterPosition -> + setupView(itemView, dir) + } + bindViewHolder(holder) + } + + override fun getItemCount() = dirs.size + + override fun prepareActionMode(menu: Menu) { + val selectedPaths = getSelectedPaths() + if (selectedPaths.isEmpty()) { + return + } + + val isOneItemSelected = isOneItemSelected() + menu.apply { + 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_lock).isVisible = selectedPaths.any { !config.isFolderProtected(it) } + findItem(R.id.cab_unlock).isVisible = selectedPaths.any { config.isFolderProtected(it) } + + 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_create_shortcut).isVisible = isOreoPlus() && isOneItemSelected + + checkHideBtnVisibility(this, selectedPaths) + checkPinBtnVisibility(this, selectedPaths) + } + } + + override fun actionItemPressed(id: Int) { + if (selectedKeys.isEmpty()) { + return + } + + when (id) { + R.id.cab_properties -> showProperties() + R.id.cab_rename -> renameDir() + R.id.cab_pin -> pinFolders(true) + R.id.cab_unpin -> pinFolders(false) + R.id.cab_empty_recycle_bin -> tryEmptyRecycleBin(true) + R.id.cab_empty_disable_recycle_bin -> emptyAndDisableRecycleBin() + R.id.cab_hide -> toggleFoldersVisibility(true) + R.id.cab_unhide -> toggleFoldersVisibility(false) + R.id.cab_exclude -> tryExcludeFolder() + R.id.cab_lock -> tryLockFolder() + R.id.cab_unlock -> unlockFolder() + R.id.cab_copy_to -> copyMoveTo(true) + R.id.cab_move_to -> moveFilesTo() + R.id.cab_select_all -> selectAll() + R.id.cab_create_shortcut -> tryCreateShortcut() + R.id.cab_delete -> askConfirmDelete() + R.id.cab_select_photo -> tryChangeAlbumCover(false) + R.id.cab_use_default -> tryChangeAlbumCover(true) + } + } + + override fun getSelectableItemCount() = dirs.size + + 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 onActionModeCreated() {} + + override fun onActionModeDestroyed() {} + + override fun onViewRecycled(holder: ViewHolder) { + super.onViewRecycled(holder) + if (!activity.isDestroyed) { + Glide.with(activity).clear(holder.itemView.dir_thumbnail!!) + } + } + + private fun checkHideBtnVisibility(menu: Menu, selectedPaths: ArrayList) { + menu.findItem(R.id.cab_hide).isVisible = selectedPaths.any { !it.doesThisOrParentHaveNoMedia(HashMap(), null) } + menu.findItem(R.id.cab_unhide).isVisible = selectedPaths.any { it.doesThisOrParentHaveNoMedia(HashMap(), null) } + } + + private fun checkPinBtnVisibility(menu: Menu, selectedPaths: ArrayList) { + val pinnedFolders = config.pinnedFolders + 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 (selectedKeys.size <= 1) { + val path = getFirstSelectedItemPath() ?: return + if (path != FAVORITES && path != RECYCLE_BIN) { + activity.handleLockedFolderOpening(path) { success -> + if (success) { + PropertiesDialog(activity, path, config.shouldShowHidden) + } + } + } + } else { + PropertiesDialog(activity, getSelectedPaths().filter { + it != FAVORITES && it != RECYCLE_BIN && !config.isFolderProtected(it) + }.toMutableList(), config.shouldShowHidden) + } + } + + private fun renameDir() { + 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 + } + + activity.handleLockedFolderOpening(sourcePath) { success -> + if (success) { + RenameItemDialog(activity, dir.absolutePath) { + activity.runOnUiThread { + firstDir.apply { + path = it + name = it.getFilenameFromPath() + tmb = File(it, tmb.getFilenameFromPath()).absolutePath + } + updateDirs(dirs) + ensureBackgroundThread { + try { + activity.directoryDao.updateDirectoryAfterRename(firstDir.tmb, firstDir.name, firstDir.path, sourcePath) + listener?.refreshItems() + } catch (e: Exception) { + activity.showErrorToast(e) + } + } + } + } + } + } + } else { + val paths = getSelectedPaths().filter { !activity.isAStorageRootFolder(it) && !config.isFolderProtected(it) } as ArrayList + RenameItemsDialog(activity, paths) { + listener?.refreshItems() + } + } + } + + private fun toggleFoldersVisibility(hide: Boolean) { + val selectedPaths = getSelectedPaths() + if (hide && selectedPaths.contains(RECYCLE_BIN)) { + config.showRecycleBinAtFolders = false + if (selectedPaths.size == 1) { + listener?.refreshItems() + finishActMode() + } + } + + if (hide) { + if (config.wasHideFolderTooltipShown) { + hideFolders(selectedPaths) + } else { + config.wasHideFolderTooltipShown = true + ConfirmationDialog(activity, activity.getString(R.string.hide_folder_description)) { + hideFolders(selectedPaths) + } + } + } else { + selectedPaths.filter { it != FAVORITES && it != RECYCLE_BIN && (selectedPaths.size == 1 || !config.isFolderProtected(it)) }.forEach { + val path = it + activity.handleLockedFolderOpening(path) { success -> + if (success) { + if (path.containsNoMedia()) { + activity.removeNoMedia(path) { + if (config.shouldShowHidden) { + updateFolderNames() + } else { + activity.runOnUiThread { + listener?.refreshItems() + finishActMode() + } + } + } + } + } + } + } + } + } + + private fun hideFolders(paths: ArrayList) { + for (path in paths) { + activity.handleLockedFolderOpening(path) { success -> + if (success) { + hideFolder(path) + } + } + } + } + + private fun tryEmptyRecycleBin(askConfirmation: Boolean) { + if (askConfirmation) { + activity.showRecycleBinEmptyingDialog { + emptyRecycleBin() + } + } else { + emptyRecycleBin() + } + } + + private fun emptyRecycleBin() { + activity.handleLockedFolderOpening(RECYCLE_BIN) { success -> + if (success) { + activity.emptyTheRecycleBin { + listener?.refreshItems() + } + } + } + } + + private fun emptyAndDisableRecycleBin() { + activity.handleLockedFolderOpening(RECYCLE_BIN) { success -> + if (success) { + activity.showRecycleBinEmptyingDialog { + activity.emptyAndDisableTheRecycleBin { + listener?.refreshItems() + } + } + } + } + } + + private fun updateFolderNames() { + val includedFolders = config.includedFolders + val hidden = activity.getString(R.string.hidden) + dirs.forEach { + it.name = activity.checkAppendingHidden(it.path, hidden, includedFolders, ArrayList()) + } + listener?.updateDirectories(dirs.toMutableList() as ArrayList) + activity.runOnUiThread { + updateDirs(dirs) + } + } + + private fun hideFolder(path: String) { + activity.addNoMedia(path) { + if (config.shouldShowHidden) { + updateFolderNames() + } else { + val affectedPositions = ArrayList() + val includedFolders = config.includedFolders + val newDirs = dirs.filterIndexed { index, directory -> + val removeDir = directory.path.doesThisOrParentHaveNoMedia(HashMap(), null) && !includedFolders.contains(directory.path) + if (removeDir) { + affectedPositions.add(index) + } + !removeDir + } as ArrayList + + activity.runOnUiThread { + affectedPositions.sortedDescending().forEach { + notifyItemRemoved(it) + } + + currentDirectoriesHash = newDirs.hashCode() + dirs = newDirs + + finishActMode() + listener?.updateDirectories(newDirs) + } + } + } + } + + private fun tryExcludeFolder() { + val selectedPaths = getSelectedPaths() + val paths = selectedPaths.filter { it != PATH && it != RECYCLE_BIN && it != FAVORITES }.toSet() + if (selectedPaths.contains(RECYCLE_BIN)) { + config.showRecycleBinAtFolders = false + if (selectedPaths.size == 1) { + listener?.refreshItems() + finishActMode() + } + } + + if (paths.size == 1) { + ExcludeFolderDialog(activity, paths.toMutableList()) { + listener?.refreshItems() + finishActMode() + } + } else if (paths.size > 1) { + config.addExcludedFolders(paths) + listener?.refreshItems() + finishActMode() + } + } + + private fun tryLockFolder() { + if (config.wasFolderLockingNoticeShown) { + lockFolder() + } else { + FolderLockingNoticeDialog(activity) { + lockFolder() + } + } + } + + private fun lockFolder() { + SecurityDialog(activity, "", SHOW_ALL_TABS) { hash, type, success -> + if (success) { + getSelectedPaths().filter { !config.isFolderProtected(it) }.forEach { + config.addFolderProtection(it, hash, type) + lockedFolderPaths.add(it) + } + + listener?.refreshItems() + finishActMode() + } + } + } + + private fun unlockFolder() { + val paths = getSelectedPaths() + val firstPath = paths.first() + val tabToShow = config.getFolderProtectionType(firstPath) + val hashToCheck = config.getFolderProtectionHash(firstPath) + SecurityDialog(activity, hashToCheck, tabToShow) { hash, type, success -> + if (success) { + paths.filter { config.isFolderProtected(it) && config.getFolderProtectionType(it) == tabToShow && config.getFolderProtectionHash(it) == hashToCheck }.forEach { + config.removeFolderProtection(it) + lockedFolderPaths.remove(it) + } + + listener?.refreshItems() + finishActMode() + } + } + } + + private fun pinFolders(pin: Boolean) { + if (pin) { + config.addPinnedFolders(getSelectedPaths().toHashSet()) + } else { + config.removePinnedFolders(getSelectedPaths().toHashSet()) + } + + currentDirectoriesHash = 0 + pinnedFolders = config.pinnedFolders + listener?.recheckPinnedFolders() + } + + private fun moveFilesTo() { + activity.handleDeletePasswordProtection { + copyMoveTo(false) + } + } + + private fun copyMoveTo(isCopyOperation: Boolean) { + val paths = ArrayList() + val showHidden = config.shouldShowHidden + getSelectedPaths().forEach { + val filter = config.filterMedia + File(it).listFiles()?.filter { + !File(it.absolutePath).isDirectory && + 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 } + } + + val fileDirItems = paths.map { FileDirItem(it, it.getFilenameFromPath()) } as ArrayList + activity.tryCopyMoveFilesTo(fileDirItems, isCopyOperation) { + val destinationPath = it + val newPaths = fileDirItems.map { "$destinationPath/${it.name}" }.toMutableList() as java.util.ArrayList + activity.rescanPaths(newPaths) { + activity.fixDateTaken(newPaths, false) + } + + config.tempFolderPath = "" + listener?.refreshItems() + finishActMode() + } + } + + private fun tryCreateShortcut() { + activity.handleLockedFolderOpening(getFirstSelectedItemPath() ?: "") { success -> + if (success) { + createShortcut() + } + } + } + + @SuppressLint("NewApi") + private fun createShortcut() { + val manager = activity.getSystemService(ShortcutManager::class.java) + if (manager.isRequestPinShortcutSupported) { + val dir = getFirstSelectedItem() ?: return + val path = dir.path + val drawable = resources.getDrawable(R.drawable.shortcut_image).mutate() + val coverThumbnail = config.parseAlbumCovers().firstOrNull { it.tmb == dir.path }?.tmb ?: dir.tmb + activity.getShortcutImage(coverThumbnail, drawable) { + val intent = Intent(activity, MediaActivity::class.java) + intent.action = Intent.ACTION_VIEW + intent.flags = intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + intent.putExtra(DIRECTORY, path) + + val shortcut = ShortcutInfo.Builder(activity, path) + .setShortLabel(dir.name) + .setIcon(Icon.createWithBitmap(drawable.convertToBitmap())) + .setIntent(intent) + .build() + + manager.requestPinShortcut(shortcut, null) + } + } + } + + private fun askConfirmDelete() { + when { + config.isDeletePasswordProtectionOn -> activity.handleDeletePasswordProtection { + deleteFolders() + } + config.skipDeleteConfirmation -> deleteFolders() + else -> { + val itemsCnt = selectedKeys.size + val items = if (itemsCnt == 1) { + var folder = getSelectedPaths().first().getFilenameFromPath() + if (folder == RECYCLE_BIN) { + folder = activity.getString(R.string.recycle_bin) + } + "\"$folder\"" + } 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 + } + + val question = String.format(resources.getString(baseString), items) + val warning = resources.getQuantityString(R.plurals.delete_warning, itemsCnt, itemsCnt) + ConfirmDeleteFolderDialog(activity, question, warning) { + deleteFolders() + } + } + } + } + + private fun deleteFolders() { + if (selectedKeys.isEmpty()) { + return + } + + var SAFPath = "" + val selectedDirs = getSelectedItems() + selectedDirs.forEach { + val path = it.path + if (activity.needsStupidWritePermissions(path) && config.treeUri.isEmpty()) { + SAFPath = path + } + } + + activity.handleSAFDialog(SAFPath) { + if (!it) { + return@handleSAFDialog + } + + var foldersToDelete = ArrayList(selectedKeys.size) + selectedDirs.forEach { + if (it.areFavorites() || it.isRecycleBin()) { + if (it.isRecycleBin()) { + tryEmptyRecycleBin(false) + } else { + ensureBackgroundThread { + activity.mediaDB.clearFavorites() + activity.favoritesDB.clearFavorites() + listener?.refreshItems() + } + } + + if (selectedKeys.size == 1) { + finishActMode() + } + } else { + foldersToDelete.add(File(it.path)) + } + } + + if (foldersToDelete.size == 1) { + activity.handleLockedFolderOpening(foldersToDelete.first().absolutePath) { success -> + if (success) { + listener?.deleteFolders(foldersToDelete) + } + } + } else { + foldersToDelete = foldersToDelete.filter { !config.isFolderProtected(it.absolutePath) }.toMutableList() as ArrayList + listener?.deleteFolders(foldersToDelete) + } + } + } + + private fun tryChangeAlbumCover(useDefault: Boolean) { + activity.handleLockedFolderOpening(getFirstSelectedItemPath() ?: "") { success -> + if (success) { + changeAlbumCover(useDefault) + } + } + } + + private fun changeAlbumCover(useDefault: Boolean) { + if (selectedKeys.size != 1) + return + + val path = getFirstSelectedItemPath() ?: return + + if (useDefault) { + val albumCovers = getAlbumCoversWithout(path) + storeCovers(albumCovers) + } else { + pickMediumFrom(path, path) + } + } + + private fun pickMediumFrom(targetFolder: String, path: String) { + PickMediumDialog(activity, path) { + if (File(it).isDirectory) { + pickMediumFrom(targetFolder, it) + } else { + val albumCovers = getAlbumCoversWithout(path) + val cover = AlbumCover(targetFolder, it) + albumCovers.add(cover) + storeCovers(albumCovers) + } + } + } + + private fun getAlbumCoversWithout(path: String) = config.parseAlbumCovers().filterNot { it.path == path } as ArrayList + + private fun storeCovers(albumCovers: ArrayList) { + config.albumCovers = Gson().toJson(albumCovers) + finishActMode() + listener?.refreshItems() + } + + private fun getSelectedItems() = selectedKeys.mapNotNull { getItemWithKey(it) } as ArrayList + + private fun getSelectedPaths() = getSelectedItems().map { it.path } as ArrayList + + private fun getFirstSelectedItem() = getItemWithKey(selectedKeys.first()) + + private fun getFirstSelectedItemPath() = getFirstSelectedItem()?.path + + private fun getItemWithKey(key: Int): Directory? = dirs.firstOrNull { it.path.hashCode() == key } + + private fun fillLockedFolders() { + lockedFolderPaths.clear() + dirs.map { it.path }.filter { config.isFolderProtected(it) }.forEach { + lockedFolderPaths.add(it) + } + } + + fun updateDirs(newDirs: ArrayList) { + val directories = newDirs.clone() as ArrayList + if (directories.hashCode() != currentDirectoriesHash) { + currentDirectoriesHash = directories.hashCode() + dirs = directories + fillLockedFolders() + notifyDataSetChanged() + finishActMode() + } + } + + fun updateAnimateGifs(animateGifs: Boolean) { + this.animateGifs = animateGifs + notifyDataSetChanged() + } + + fun updateCropThumbnails(cropThumbnails: Boolean) { + this.cropThumbnails = cropThumbnails + notifyDataSetChanged() + } + + private fun setupView(view: View, directory: Directory) { + val isSelected = selectedKeys.contains(directory.path.hashCode()) + view.apply { + dir_path?.text = "${directory.path.substringBeforeLast("/")}/" + val thumbnailType = when { + directory.tmb.isVideoFast() -> TYPE_VIDEOS + directory.tmb.isGif() -> TYPE_GIFS + directory.tmb.isRawFast() -> TYPE_RAWS + directory.tmb.isSvg() -> TYPE_SVGS + else -> TYPE_IMAGES + } + + dir_check?.beVisibleIf(isSelected) + if (isSelected) { + dir_check.background?.applyColorFilter(primaryColor) + } + + if (isListViewType) { + dir_holder.isSelected = isSelected + } + + if (scrollHorizontally && !isListViewType && folderStyle == FOLDER_STYLE_ROUNDED_CORNERS) { + (dir_thumbnail.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ABOVE, dir_name.id) + + val photoCntParams = (photo_cnt.layoutParams as RelativeLayout.LayoutParams) + val nameParams = (dir_name.layoutParams as RelativeLayout.LayoutParams) + nameParams.removeRule(RelativeLayout.BELOW) + + if (config.showFolderMediaCount == FOLDER_MEDIA_CNT_LINE) { + nameParams.addRule(RelativeLayout.ABOVE, photo_cnt.id) + nameParams.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM) + + photoCntParams.removeRule(RelativeLayout.BELOW) + photoCntParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) + } else { + nameParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) + } + } + + if (lockedFolderPaths.contains(directory.path)) { + dir_lock.beVisible() + dir_lock.background = ColorDrawable(config.backgroundColor) + dir_lock.applyColorFilter(config.backgroundColor.getContrastColor()) + } else { + dir_lock.beGone() + val roundedCorners = when { + isListViewType -> ROUNDED_CORNERS_SMALL + folderStyle == FOLDER_STYLE_SQUARE -> ROUNDED_CORNERS_NONE + else -> ROUNDED_CORNERS_BIG + } + + activity.loadImage(thumbnailType, directory.tmb, dir_thumbnail, scrollHorizontally, animateGifs, cropThumbnails, roundedCorners, directory.getKey()) + } + + dir_pin.beVisibleIf(pinnedFolders.contains(directory.path)) + dir_location.beVisibleIf(directory.location != LOCATION_INTERNAL) + if (dir_location.isVisible()) { + dir_location.setImageResource(if (directory.location == LOCATION_SD) R.drawable.ic_sd_card_vector else R.drawable.ic_usb_vector) + } + + photo_cnt.text = directory.subfoldersMediaCount.toString() + photo_cnt.beVisibleIf(showMediaCount == FOLDER_MEDIA_CNT_LINE) + + if (limitFolderTitle) { + dir_name.setSingleLine() + dir_name.ellipsize = TextUtils.TruncateAt.MIDDLE + } + + var nameCount = directory.name + if (showMediaCount == FOLDER_MEDIA_CNT_BRACKETS) { + nameCount += " (${directory.subfoldersMediaCount})" + } + + if (groupDirectSubfolders) { + if (directory.subfoldersCount > 1) { + nameCount += " [${directory.subfoldersCount}]" + } + } + + dir_name.text = nameCount + + if (isListViewType || folderStyle == FOLDER_STYLE_ROUNDED_CORNERS) { + photo_cnt.setTextColor(textColor) + dir_name.setTextColor(textColor) + dir_location.applyColorFilter(textColor) + } + + if (isListViewType) { + dir_path.setTextColor(textColor) + dir_pin.applyColorFilter(textColor) + dir_location.applyColorFilter(textColor) + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/FiltersAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/FiltersAdapter.kt new file mode 100644 index 000000000..b614787b8 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/FiltersAdapter.kt @@ -0,0 +1,58 @@ +package com.simplemobiletools.gallery.pro.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +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, val itemClick: (Int) -> Unit) : RecyclerView.Adapter() { + + 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]) + } + + 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) + } + + override fun getItemCount() = filterItems.size + + fun getCurrentFilter() = currentSelection + + private fun setCurrentFilter(position: Int) { + val filterItem = filterItems.getOrNull(position) ?: return + if (currentSelection != filterItem) { + currentSelection = filterItem + notifyDataSetChanged() + itemClick.invoke(position) + } + } + + 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 (getCurrentFilter() == filterItem) { + strokeBackground + } else { + null + } + + setOnClickListener { + setCurrentFilter(adapterPosition) + } + } + return itemView + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/ManageFoldersAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/ManageFoldersAdapter.kt new file mode 100644 index 000000000..670174413 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/ManageFoldersAdapter.kt @@ -0,0 +1,89 @@ +package com.simplemobiletools.gallery.pro.adapters + +import android.view.Menu +import android.view.View +import android.view.ViewGroup +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.pro.R +import com.simplemobiletools.gallery.pro.extensions.config +import kotlinx.android.synthetic.main.item_manage_folder.view.* +import java.util.* + +class ManageFoldersAdapter(activity: BaseSimpleActivity, var folders: ArrayList, val isShowingExcludedFolders: Boolean, val listener: RefreshRecyclerViewListener?, + recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, null, itemClick) { + + private val config = activity.config + + init { + setupDragListener(true) + } + + override fun getActionMenuId() = R.menu.cab_remove_only + + override fun prepareActionMode(menu: Menu) {} + + override fun actionItemPressed(id: Int) { + when (id) { + R.id.cab_remove -> removeSelection() + } + } + + override fun getSelectableItemCount() = folders.size + + 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 onActionModeCreated() {} + + override fun onActionModeDestroyed() {} + + 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] + holder.bindView(folder, true, true) { itemView, adapterPosition -> + setupView(itemView, folder) + } + bindViewHolder(holder) + } + + override fun getItemCount() = folders.size + + private fun getSelectedItems() = folders.filter { selectedKeys.contains(it.hashCode()) } as ArrayList + + 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) + } + } + } + + private fun removeSelection() { + val removeFolders = ArrayList(selectedKeys.size) + val positions = getSelectedItemPositions() + + getSelectedItems().forEach { + removeFolders.add(it) + if (isShowingExcludedFolders) { + config.removeExcludedFolder(it) + } else { + config.removeIncludedFolder(it) + } + } + + folders.removeAll(removeFolders) + removeSelectedItems(positions) + if (folders.isEmpty()) { + listener?.refreshItems() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/ManageHiddenFoldersAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/ManageHiddenFoldersAdapter.kt new file mode 100644 index 000000000..972d3e7a1 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/ManageHiddenFoldersAdapter.kt @@ -0,0 +1,106 @@ +package com.simplemobiletools.gallery.pro.adapters + +import android.view.Menu +import android.view.View +import android.view.ViewGroup +import com.simplemobiletools.commons.activities.BaseSimpleActivity +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.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.* + +class ManageHiddenFoldersAdapter(activity: BaseSimpleActivity, var folders: ArrayList, val listener: RefreshRecyclerViewListener?, + recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, null, itemClick) { + + private val config = activity.config + + init { + setupDragListener(true) + } + + override fun getActionMenuId() = R.menu.cab_hidden_folders + + override fun prepareActionMode(menu: Menu) {} + + override fun actionItemPressed(id: Int) { + when (id) { + R.id.cab_unhide -> tryUnhideFolders() + } + } + + override fun getSelectableItemCount() = folders.size + + 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 onActionModeCreated() {} + + override fun onActionModeDestroyed() {} + + 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] + holder.bindView(folder, true, true) { itemView, adapterPosition -> + setupView(itemView, folder) + } + bindViewHolder(holder) + } + + override fun getItemCount() = folders.size + + private fun getSelectedItems() = folders.filter { selectedKeys.contains(it.hashCode()) } as ArrayList + + 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) + } + } + } + + private fun tryUnhideFolders() { + val removeFolders = ArrayList(selectedKeys.size) + + val sdCardPaths = ArrayList() + getSelectedItems().forEach { + if (activity.isPathOnSD(it)) { + sdCardPaths.add(it) + } + } + + if (sdCardPaths.isNotEmpty()) { + activity.handleSAFDialog(sdCardPaths.first()) { + if (it) { + unhideFolders(removeFolders) + } + } + } else { + unhideFolders(removeFolders) + } + } + + private fun unhideFolders(removeFolders: ArrayList) { + val position = getSelectedItemPositions() + getSelectedItems().forEach { + removeFolders.add(it) + activity.removeNoMedia(it) + } + + folders.removeAll(removeFolders) + removeSelectedItems(position) + if (folders.isEmpty()) { + listener?.refreshItems() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/MediaAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/MediaAdapter.kt new file mode 100644 index 000000000..9139e10f9 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/MediaAdapter.kt @@ -0,0 +1,588 @@ +package com.simplemobiletools.gallery.pro.adapters + +import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager +import android.graphics.drawable.Icon +import android.os.Handler +import android.os.Looper +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.RenameDialog +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.commons.views.FastScroller +import com.simplemobiletools.commons.views.MyRecyclerView +import com.simplemobiletools.gallery.pro.R +import com.simplemobiletools.gallery.pro.activities.ViewPagerActivity +import com.simplemobiletools.gallery.pro.dialogs.DeleteWithRememberDialog +import com.simplemobiletools.gallery.pro.extensions.* +import com.simplemobiletools.gallery.pro.helpers.* +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.util.* + +class MediaAdapter(activity: BaseSimpleActivity, var media: ArrayList, val listener: MediaOperationsListener?, val isAGetIntent: Boolean, + 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 ITEM_SECTION = 0 + private val ITEM_MEDIUM = 1 + + private val config = activity.config + private val viewType = config.getFolderViewType(if (config.showAll) SHOW_ALL else path) + private val isListViewType = viewType == VIEW_TYPE_LIST + private var visibleItemPaths = ArrayList() + private var rotatedImagePaths = ArrayList() + private var loadImageInstantly = false + private var delayHandler = Handler(Looper.getMainLooper()) + private var currentMediaHash = media.hashCode() + private val hasOTGConnected = activity.hasOTGConnected() + + private var scrollHorizontally = config.scrollHorizontally + private var animateGifs = config.animateGifs + private var cropThumbnails = config.cropThumbnails + private var displayFilenames = config.displayFileNames + private var showFileTypes = config.showThumbnailFileTypes + + init { + setupDragListener(true) + enableInstantLoad() + } + + override fun getActionMenuId() = R.menu.cab_media + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val layoutType = if (viewType == ITEM_SECTION) { + R.layout.thumbnail_section + } else { + if (isListViewType) { + R.layout.photo_video_item_list + } else { + R.layout.photo_video_item_grid + } + } + return createViewHolder(layoutType, parent) + } + + override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) { + val tmbItem = media.getOrNull(position) ?: return + if (tmbItem is Medium) { + visibleItemPaths.add(tmbItem.path) + } + + 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) + } + + override fun getItemCount() = media.size + + override fun getItemViewType(position: Int): Int { + val tmbItem = media[position] + return if (tmbItem is ThumbnailSection) { + ITEM_SECTION + } else { + ITEM_MEDIUM + } + } + + override fun prepareActionMode(menu: Menu) { + val selectedItems = getSelectedItems() + if (selectedItems.isEmpty()) { + return + } + + val isOneItemSelected = isOneItemSelected() + val selectedPaths = selectedItems.map { it.path } as ArrayList + val isInRecycleBin = selectedItems.firstOrNull()?.getIsInRecycleBin() == true + menu.apply { + findItem(R.id.cab_rename).isVisible = !isInRecycleBin + findItem(R.id.cab_add_to_favorites).isVisible = !isInRecycleBin + findItem(R.id.cab_fix_date_taken).isVisible = !isInRecycleBin + findItem(R.id.cab_move_to).isVisible = !isInRecycleBin + 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) } + findItem(R.id.cab_create_shortcut).isVisible = isOreoPlus() && isOneItemSelected + + checkHideBtnVisibility(this, selectedItems) + checkFavoriteBtnVisibility(this, selectedItems) + } + } + + override fun actionItemPressed(id: Int) { + if (selectedKeys.isEmpty()) { + return + } + + when (id) { + R.id.cab_confirm_selection -> confirmSelection() + R.id.cab_properties -> showProperties() + R.id.cab_rename -> renameFile() + R.id.cab_edit -> editFile() + R.id.cab_hide -> toggleFileVisibility(true) + R.id.cab_unhide -> toggleFileVisibility(false) + R.id.cab_add_to_favorites -> toggleFavorites(true) + 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 -> moveFilesTo() + R.id.cab_create_shortcut -> createShortcut() + R.id.cab_select_all -> selectAll() + R.id.cab_open_with -> openPath() + R.id.cab_fix_date_taken -> fixDateTaken() + R.id.cab_set_as -> setAs() + R.id.cab_delete -> checkDeleteConfirmation() + } + } + + override fun getSelectableItemCount() = media.filter { it is Medium }.size + + 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 onActionModeCreated() {} + + override fun onActionModeDestroyed() {} + + override fun onViewRecycled(holder: ViewHolder) { + super.onViewRecycled(holder) + if (!activity.isDestroyed) { + val itemView = holder.itemView + visibleItemPaths.remove(itemView.medium_name?.tag) + val tmb = itemView.medium_thumbnail + if (tmb != null) { + Glide.with(activity).clear(tmb) + } + } + } + + fun isASectionTitle(position: Int) = media.getOrNull(position) is ThumbnailSection + + private fun checkHideBtnVisibility(menu: Menu, selectedItems: ArrayList) { + 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, selectedItems: ArrayList) { + menu.findItem(R.id.cab_add_to_favorites).isVisible = selectedItems.none { it.getIsInRecycleBin() } && selectedItems.any { !it.isFavorite } + menu.findItem(R.id.cab_remove_from_favorites).isVisible = selectedItems.none { it.getIsInRecycleBin() } && selectedItems.any { it.isFavorite } + } + + private fun confirmSelection() { + listener?.selectedPaths(getSelectedPaths()) + } + + private fun showProperties() { + if (selectedKeys.size <= 1) { + val path = getFirstSelectedItemPath() ?: return + PropertiesDialog(activity, path, config.shouldShowHidden) + } else { + val paths = getSelectedPaths() + PropertiesDialog(activity, paths, config.shouldShowHidden) + } + } + + private fun renameFile() { + if (selectedKeys.size == 1) { + val oldPath = getFirstSelectedItemPath() ?: return + RenameItemDialog(activity, oldPath) { + ensureBackgroundThread { + activity.updateDBMediaPath(oldPath, it) + + activity.runOnUiThread { + enableInstantLoad() + listener?.refreshItems() + finishActMode() + } + } + } + } else { + RenameDialog(activity, getSelectedPaths(), true) { + enableInstantLoad() + listener?.refreshItems() + finishActMode() + } + } + } + + private fun editFile() { + 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) { + ensureBackgroundThread { + getSelectedItems().forEach { + activity.toggleFileVisibility(it.path, hide) + } + activity.runOnUiThread { + listener?.refreshItems() + finishActMode() + } + } + } + + private fun toggleFavorites(add: Boolean) { + ensureBackgroundThread { + getSelectedItems().forEach { + it.isFavorite = add + activity.updateFavorite(it.path, add) + } + activity.runOnUiThread { + listener?.refreshItems() + finishActMode() + } + } + } + + private fun restoreFiles() { + activity.restoreRecycleBinPaths(getSelectedPaths()) { + listener?.refreshItems() + finishActMode() + } + } + + private fun shareMedia() { + 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) + ensureBackgroundThread { + 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() + } + } + } + } + } + } + + private fun moveFilesTo() { + activity.handleDeletePasswordProtection { + copyMoveTo(false) + } + } + + private fun copyMoveTo(isCopyOperation: Boolean) { + val paths = getSelectedPaths() + + val recycleBinPath = activity.recycleBinPath + val fileDirItems = paths.asSequence().filter { isCopyOperation || !it.startsWith(recycleBinPath) }.map { + FileDirItem(it, it.getFilenameFromPath()) + }.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) { + val destinationPath = it + config.tempFolderPath = "" + activity.applicationContext.rescanFolderMedia(destinationPath) + activity.applicationContext.rescanFolderMedia(fileDirItems.first().getParentPath()) + + val newPaths = fileDirItems.map { "$destinationPath/${it.name}" }.toMutableList() as ArrayList + activity.rescanPaths(newPaths) { + activity.fixDateTaken(newPaths, false) + } + if (!isCopyOperation) { + listener?.refreshItems() + activity.updateFavoritePaths(fileDirItems, destinationPath) + } + } + } + + @SuppressLint("NewApi") + private fun createShortcut() { + val manager = activity.getSystemService(ShortcutManager::class.java) + if (manager.isRequestPinShortcutSupported) { + val path = getSelectedPaths().first() + val drawable = resources.getDrawable(R.drawable.shortcut_image).mutate() + activity.getShortcutImage(path, drawable) { + val intent = Intent(activity, ViewPagerActivity::class.java).apply { + putExtra(PATH, path) + putExtra(SHOW_ALL, config.showAll) + putExtra(SHOW_FAVORITES, path == FAVORITES) + putExtra(SHOW_RECYCLE_BIN, path == RECYCLE_BIN) + action = Intent.ACTION_VIEW + flags = flags or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + + val shortcut = ShortcutInfo.Builder(activity, path) + .setShortLabel(path.getFilenameFromPath()) + .setIcon(Icon.createWithBitmap(drawable.convertToBitmap())) + .setIntent(intent) + .build() + + manager.requestPinShortcut(shortcut, null) + } + } + } + + private fun fixDateTaken() { + ensureBackgroundThread { + activity.fixDateTaken(getSelectedPaths(), true) { + listener?.refreshItems() + finishActMode() + } + } + } + + private fun checkDeleteConfirmation() { + if (config.isDeletePasswordProtectionOn) { + activity.handleDeletePasswordProtection { + deleteFiles() + } + } else if (config.tempSkipDeleteConfirmation || config.skipDeleteConfirmation) { + deleteFiles() + } else { + askConfirmDelete() + } + } + + private fun askConfirmDelete() { + 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) { + config.tempSkipDeleteConfirmation = it + deleteFiles() + } + } + + private fun deleteFiles() { + if (selectedKeys.isEmpty()) { + return + } + + val SAFPath = getSelectedPaths().firstOrNull { activity.needsStupidWritePermissions(it) } ?: getFirstSelectedItemPath() ?: return + activity.handleSAFDialog(SAFPath) { + if (!it) { + return@handleSAFDialog + } + + val fileDirItems = ArrayList(selectedKeys.size) + val removeMedia = ArrayList(selectedKeys.size) + val positions = getSelectedItemPositions() + + getSelectedItems().forEach { + fileDirItems.add(FileDirItem(it.path, it.name)) + removeMedia.add(it) + } + + media.removeAll(removeMedia) + listener?.tryDeleteFiles(fileDirItems) + listener?.updateMediaGridDecoration(media) + removeSelectedItems(positions) + currentMediaHash = media.hashCode() + } + } + + private fun getSelectedItems() = selectedKeys.mapNotNull { getItemWithKey(it) } as ArrayList + + private fun getSelectedPaths() = getSelectedItems().map { it.path } as ArrayList + + 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) { + val thumbnailItems = newMedia.clone() as ArrayList + if (thumbnailItems.hashCode() != currentMediaHash) { + currentMediaHash = thumbnailItems.hashCode() + media = thumbnailItems + enableInstantLoad() + notifyDataSetChanged() + finishActMode() + } + } + + fun updateDisplayFilenames(displayFilenames: Boolean) { + this.displayFilenames = displayFilenames + enableInstantLoad() + notifyDataSetChanged() + } + + fun updateAnimateGifs(animateGifs: Boolean) { + this.animateGifs = animateGifs + notifyDataSetChanged() + } + + fun updateCropThumbnails(cropThumbnails: Boolean) { + this.cropThumbnails = cropThumbnails + notifyDataSetChanged() + } + + fun updateShowFileTypes(showFileTypes: Boolean) { + this.showFileTypes = showFileTypes + notifyDataSetChanged() + } + + private fun enableInstantLoad() { + loadImageInstantly = true + delayHandler.postDelayed({ + loadImageInstantly = false + }, INSTANT_LOAD_DURATION) + } + + fun getItemBubbleText(position: Int, sorting: Int, dateFormat: String, timeFormat: String): String { + return (media[position] as? Medium)?.getBubbleText(sorting, activity, dateFormat, timeFormat) ?: "" + } + + private fun setupThumbnail(view: View, medium: Medium) { + val isSelected = selectedKeys.contains(medium.path.hashCode()) + view.apply { + val padding = if (config.thumbnailSpacing <= 1) { + config.thumbnailSpacing + } else { + 0 + } + + media_item_holder.setPadding(padding, padding, padding, padding) + + play_outline.beVisibleIf(medium.isVideo() || medium.isPortrait()) + if (medium.isVideo()) { + play_outline.setImageResource(R.drawable.ic_play_outline_vector) + play_outline.beVisible() + } else if (medium.isPortrait()) { + play_outline.setImageResource(R.drawable.ic_portrait_photo_vector) + play_outline.beVisibleIf(showFileTypes) + } + + if (showFileTypes && (medium.isGIF() || medium.isRaw() || medium.isSVG())) { + file_type.setText(when (medium.type) { + TYPE_GIFS -> R.string.gif + TYPE_RAWS -> R.string.raw + else -> R.string.svg + }) + file_type.beVisible() + } else { + file_type.beGone() + } + + 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.beVisibleIf(showVideoDuration) + + medium_check?.beVisibleIf(isSelected) + if (isSelected) { + medium_check?.background?.applyColorFilter(primaryColor) + } + + if (isListViewType) { + media_item_holder.isSelected = isSelected + } + + var path = medium.path + if (hasOTGConnected && context.isPathOnOTG(path)) { + path = path.getOTGPublicPath(context) + } + + val roundedCorners = when { + isListViewType -> ROUNDED_CORNERS_SMALL + config.fileRoundedCorners -> ROUNDED_CORNERS_BIG + else -> ROUNDED_CORNERS_NONE + } + + if (loadImageInstantly) { + activity.loadImage(medium.type, path, medium_thumbnail, scrollHorizontally, animateGifs, cropThumbnails, roundedCorners, medium.getKey(), + 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, roundedCorners, + medium.getKey(), rotatedImagePaths) + } + }, IMAGE_LOAD_DELAY) + } + + if (isListViewType) { + medium_name.setTextColor(textColor) + play_outline.applyColorFilter(textColor) + } + } + } + + private fun setupSection(view: View, section: ThumbnailSection) { + view.apply { + thumbnail_section.text = section.title + thumbnail_section.setTextColor(textColor) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/MyPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/MyPagerAdapter.kt new file mode 100644 index 000000000..02be99de0 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/MyPagerAdapter.kt @@ -0,0 +1,67 @@ +package com.simplemobiletools.gallery.pro.adapters + +import android.os.Bundle +import android.os.Parcelable +import android.view.ViewGroup +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.helpers.SHOULD_INIT_FRAGMENT +import com.simplemobiletools.gallery.pro.models.Medium + +class MyPagerAdapter(val activity: ViewPagerActivity, fm: FragmentManager, val media: MutableList) : FragmentStatePagerAdapter(fm) { + private val fragments = HashMap() + var shouldInitFragment = true + + override fun getCount() = media.size + + override fun getItem(position: Int): Fragment { + val medium = media[position] + val bundle = Bundle() + bundle.putSerializable(MEDIUM, medium) + bundle.putBoolean(SHOULD_INIT_FRAGMENT, shouldInitFragment) + val fragment = if (medium.isVideo()) { + VideoFragment() + } else { + PhotoFragment() + } + + fragment.arguments = bundle + fragment.listener = activity + return fragment + } + + override fun getItemPosition(item: Any) = PagerAdapter.POSITION_NONE + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val fragment = super.instantiateItem(container, position) as ViewPagerFragment + fragments[position] = fragment + return fragment + } + + override fun destroyItem(container: ViewGroup, position: Int, any: Any) { + fragments.remove(position) + super.destroyItem(container, position, any) + } + + fun getCurrentFragment(position: Int) = fragments[position] + + fun toggleFullscreen(isFullscreen: Boolean) { + for ((pos, fragment) in fragments) { + fragment.fullscreenToggled(isFullscreen) + } + } + + // try fixing TransactionTooLargeException crash on Android Nougat, tip from https://stackoverflow.com/a/43193425/1967672 + override fun saveState(): Parcelable? { + val bundle = super.saveState() as Bundle? + bundle?.putParcelableArray("states", null) + return bundle + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/PortraitPhotosAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/PortraitPhotosAdapter.kt new file mode 100644 index 000000000..1752db8ca --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/PortraitPhotosAdapter.kt @@ -0,0 +1,88 @@ +package com.simplemobiletools.gallery.pro.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.signature.ObjectKey +import com.simplemobiletools.commons.extensions.getFileKey +import com.simplemobiletools.gallery.pro.R +import kotlinx.android.synthetic.main.portrait_photo_item.view.* +import java.util.* + +class PortraitPhotosAdapter(val context: Context, val photos: ArrayList, val sideElementWidth: Int, val itemClick: (Int, Int) -> Unit) : + RecyclerView.Adapter() { + + var currentSelectionIndex = -1 + var views = HashMap() + private var strokeBackground = context.resources.getDrawable(R.drawable.stroke_background) + private val itemWidth = context.resources.getDimension(R.dimen.portrait_photos_stripe_height).toInt() + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bindView(photos[position], position) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.portrait_photo_item, parent, false) + return ViewHolder(view) + } + + override fun getItemCount() = photos.size + + fun setCurrentPhoto(position: Int) { + if (currentSelectionIndex != position) { + currentSelectionIndex = position + notifyDataSetChanged() + } + } + + fun performClickOn(position: Int) { + views[position]?.performClick() + } + + inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + fun bindView(photo: String, position: Int): View { + itemView.apply { + portrait_photo_item_thumbnail.layoutParams.width = if (position == 0 || position == photos.size - 1) { + sideElementWidth + } else { + itemWidth + } + + portrait_photo_item_thumbnail.background = if (photo.isEmpty() || position != currentSelectionIndex) { + null + } else { + strokeBackground + } + + val options = RequestOptions() + .signature(ObjectKey(photo.getFileKey())) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .centerCrop() + + Glide.with(context) + .load(photo) + .transition(DrawableTransitionOptions.withCrossFade()) + .apply(options) + .into(portrait_photo_item_thumbnail) + + if (photo.isNotEmpty()) { + isClickable = true + views[position] = this + setOnClickListener { + itemClick(position, x.toInt()) + setCurrentPhoto(position) + } + } else { + isClickable = false + } + } + return itemView + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/asynctasks/GetMediaAsynctask.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/asynctasks/GetMediaAsynctask.kt new file mode 100644 index 000000000..07cfd49f1 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/asynctasks/GetMediaAsynctask.kt @@ -0,0 +1,64 @@ +package com.simplemobiletools.gallery.pro.asynctasks + +import android.content.Context +import android.os.AsyncTask +import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.gallery.pro.extensions.config +import com.simplemobiletools.gallery.pro.extensions.getFavoritePaths +import com.simplemobiletools.gallery.pro.helpers.* +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, + val showAll: Boolean, val callback: (media: ArrayList) -> Unit) : + AsyncTask>() { + private val mediaFetcher = MediaFetcher(context) + + override fun doInBackground(vararg params: Void): ArrayList { + val pathToUse = if (showAll) SHOW_ALL else mPath + val folderGrouping = context.config.getFolderGrouping(pathToUse) + val fileSorting = context.config.getFolderSorting(pathToUse) + val getProperDateTaken = fileSorting and SORT_BY_DATE_TAKEN != 0 || + folderGrouping and GROUP_BY_DATE_TAKEN_DAILY != 0 || + folderGrouping and GROUP_BY_DATE_TAKEN_MONTHLY != 0 + + val getProperLastModified = fileSorting and SORT_BY_DATE_MODIFIED != 0 || + folderGrouping and GROUP_BY_LAST_MODIFIED_DAILY != 0 || + folderGrouping and GROUP_BY_LAST_MODIFIED_MONTHLY != 0 + + val getProperFileSize = fileSorting and SORT_BY_SIZE != 0 + val favoritePaths = context.getFavoritePaths() + val getVideoDurations = context.config.showThumbnailVideoDuration + val lastModifieds = if (getProperLastModified) mediaFetcher.getLastModifieds() else HashMap() + val dateTakens = if (getProperDateTaken) mediaFetcher.getDateTakens() else HashMap() + + val media = if (showAll) { + val foldersToScan = mediaFetcher.getFoldersToScan().filter { it != RECYCLE_BIN && it != FAVORITES && !context.config.isFolderProtected(it) } + val media = ArrayList() + foldersToScan.forEach { + val newMedia = mediaFetcher.getFilesFrom(it, isPickImage, isPickVideo, getProperDateTaken, getProperLastModified, getProperFileSize, + favoritePaths, getVideoDurations, lastModifieds, dateTakens) + media.addAll(newMedia) + } + + mediaFetcher.sortMedia(media, context.config.getFolderSorting(SHOW_ALL)) + media + } else { + mediaFetcher.getFilesFrom(mPath, isPickImage, isPickVideo, getProperDateTaken, getProperLastModified, getProperFileSize, favoritePaths, + getVideoDurations, lastModifieds, dateTakens) + } + + return mediaFetcher.groupMedia(media, pathToUse) + } + + override fun onPostExecute(media: ArrayList) { + super.onPostExecute(media) + callback(media) + } + + fun stopFetching() { + mediaFetcher.shouldStop = true + cancel(true) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/databases/GalleryDatabase.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/databases/GalleryDatabase.kt new file mode 100644 index 000000000..0b41a17d6 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/databases/GalleryDatabase.kt @@ -0,0 +1,85 @@ +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.* +import com.simplemobiletools.gallery.pro.models.* + +@Database(entities = [Directory::class, Medium::class, Widget::class, DateTaken::class, Favorite::class], version = 9) +abstract class GalleryDatabase : RoomDatabase() { + + abstract fun DirectoryDao(): DirectoryDao + + abstract fun MediumDao(): MediumDao + + abstract fun WidgetsDao(): WidgetsDao + + abstract fun DateTakensDao(): DateTakensDao + + abstract fun FavoritesDao(): FavoritesDao + + 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) + .addMigrations(MIGRATION_6_7) + .addMigrations(MIGRATION_7_8) + .addMigrations(MIGRATION_8_9) + .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`)") + } + } + + private val MIGRATION_6_7 = object : Migration(6, 7) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `date_takens` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `full_path` TEXT NOT NULL, `filename` TEXT NOT NULL, `parent_path` TEXT NOT NULL, `date_taken` INTEGER NOT NULL, `last_fixed` INTEGER NOT NULL)") + database.execSQL("CREATE TABLE IF NOT EXISTS `favorites` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `full_path` TEXT NOT NULL, `filename` TEXT NOT NULL, `parent_path` TEXT NOT NULL)") + + database.execSQL("CREATE UNIQUE INDEX `index_date_takens_full_path` ON `date_takens` (`full_path`)") + database.execSQL("CREATE UNIQUE INDEX `index_favorites_full_path` ON `favorites` (`full_path`)") + } + } + + private val MIGRATION_7_8 = object : Migration(7, 8) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE directories ADD COLUMN sort_value TEXT default '' NOT NULL") + } + } + + private val MIGRATION_8_9 = object : Migration(8, 9) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE date_takens ADD COLUMN last_modified INTEGER default 0 NOT NULL") + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFileThumbnailStyleDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFileThumbnailStyleDialog.kt new file mode 100644 index 000000000..5498c66b8 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFileThumbnailStyleDialog.kt @@ -0,0 +1,70 @@ +package com.simplemobiletools.gallery.pro.dialogs + +import android.content.DialogInterface +import android.view.View +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.dialogs.RadioGroupDialog +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.models.RadioItem +import com.simplemobiletools.gallery.pro.R +import com.simplemobiletools.gallery.pro.extensions.config +import kotlinx.android.synthetic.main.dialog_change_file_thumbnail_style.view.* + +class ChangeFileThumbnailStyleDialog(val activity: BaseSimpleActivity) : DialogInterface.OnClickListener { + private var config = activity.config + private var view: View + private var thumbnailSpacing = config.thumbnailSpacing + + init { + view = activity.layoutInflater.inflate(R.layout.dialog_change_file_thumbnail_style, null).apply { + dialog_file_style_rounded_corners.isChecked = config.fileRoundedCorners + dialog_file_style_animate_gifs.isChecked = config.animateGifs + dialog_file_style_show_thumbnail_video_duration.isChecked = config.showThumbnailVideoDuration + dialog_file_style_show_thumbnail_file_types.isChecked = config.showThumbnailFileTypes + + dialog_file_style_rounded_corners_holder.setOnClickListener { dialog_file_style_rounded_corners.toggle() } + dialog_file_style_animate_gifs_holder.setOnClickListener { dialog_file_style_animate_gifs.toggle() } + dialog_file_style_show_thumbnail_video_duration_holder.setOnClickListener { dialog_file_style_show_thumbnail_video_duration.toggle() } + dialog_file_style_show_thumbnail_file_types_holder.setOnClickListener { dialog_file_style_show_thumbnail_file_types.toggle() } + + dialog_file_style_spacing_holder.setOnClickListener { + val items = arrayListOf( + RadioItem(0, "0x"), + RadioItem(1, "1x"), + RadioItem(2, "2x"), + RadioItem(4, "4x"), + RadioItem(8, "8x"), + RadioItem(16, "16x"), + RadioItem(32, "32x"), + RadioItem(64, "64x")) + + RadioGroupDialog(activity, items, thumbnailSpacing) { + thumbnailSpacing = it as Int + updateThumbnailSpacingText() + } + } + } + + updateThumbnailSpacingText() + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok, this) + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this) + } + } + + override fun onClick(dialog: DialogInterface, which: Int) { + config.fileRoundedCorners = view.dialog_file_style_rounded_corners.isChecked + config.animateGifs = view.dialog_file_style_animate_gifs.isChecked + config.showThumbnailVideoDuration = view.dialog_file_style_show_thumbnail_video_duration.isChecked + config.showThumbnailFileTypes = view.dialog_file_style_show_thumbnail_file_types.isChecked + config.thumbnailSpacing = thumbnailSpacing + } + + private fun updateThumbnailSpacingText() { + view.dialog_file_style_spacing.text = "${thumbnailSpacing}x" + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFolderThumbnailStyleDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFolderThumbnailStyleDialog.kt new file mode 100644 index 000000000..b41b4b23e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFolderThumbnailStyleDialog.kt @@ -0,0 +1,134 @@ +package com.simplemobiletools.gallery.pro.dialogs + +import android.content.DialogInterface +import android.view.View +import android.widget.RelativeLayout +import androidx.appcompat.app.AlertDialog +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.extensions.beGone +import com.simplemobiletools.commons.extensions.beVisible +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.* +import kotlinx.android.synthetic.main.dialog_change_folder_thumbnail_style.view.* +import kotlinx.android.synthetic.main.directory_item_grid_square.view.* + +class ChangeFolderThumbnailStyleDialog(val activity: BaseSimpleActivity, val callback: () -> Unit) : DialogInterface.OnClickListener { + private var config = activity.config + private var view: View + + init { + view = activity.layoutInflater.inflate(R.layout.dialog_change_folder_thumbnail_style, null).apply { + dialog_folder_limit_title.isChecked = config.limitFolderTitle + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok, this) + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this) { + setupStyle() + setupMediaCount() + updateSample() + } + } + } + + private fun setupStyle() { + val styleRadio = view.dialog_radio_folder_style + styleRadio.setOnCheckedChangeListener { group, checkedId -> + updateSample() + } + + val styleBtn = when (config.folderStyle) { + FOLDER_STYLE_SQUARE -> styleRadio.dialog_radio_folder_square + else -> styleRadio.dialog_radio_folder_rounded_corners + } + + styleBtn.isChecked = true + } + + private fun setupMediaCount() { + val countRadio = view.dialog_radio_folder_count_holder + countRadio.setOnCheckedChangeListener { group, checkedId -> + updateSample() + } + + val countBtn = when (config.showFolderMediaCount) { + FOLDER_MEDIA_CNT_LINE -> countRadio.dialog_radio_folder_count_line + FOLDER_MEDIA_CNT_BRACKETS -> countRadio.dialog_radio_folder_count_brackets + else -> countRadio.dialog_radio_folder_count_none + } + + countBtn.isChecked = true + } + + private fun updateSample() { + val photoCount = 36 + val folderName = "Camera" + view.apply { + val useRoundedCornersLayout = dialog_radio_folder_style.checkedRadioButtonId == R.id.dialog_radio_folder_rounded_corners + dialog_folder_sample_holder.removeAllViews() + + val layout = if (useRoundedCornersLayout) R.layout.directory_item_grid_rounded_corners else R.layout.directory_item_grid_square + val sampleView = activity.layoutInflater.inflate(layout, null) + dialog_folder_sample_holder.addView(sampleView) + + sampleView.layoutParams.width = activity.resources.getDimension(R.dimen.sample_thumbnail_size).toInt() + (sampleView.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.CENTER_HORIZONTAL) + + when (dialog_radio_folder_count_holder.checkedRadioButtonId) { + R.id.dialog_radio_folder_count_line -> { + dir_name.text = folderName + photo_cnt.text = photoCount.toString() + photo_cnt.beVisible() + } + R.id.dialog_radio_folder_count_brackets -> { + photo_cnt.beGone() + dir_name.text = "$folderName ($photoCount)" + } + else -> { + dir_name.text = folderName + photo_cnt?.beGone() + } + } + + val options = RequestOptions().centerCrop() + var builder = Glide.with(activity) + .load(R.drawable.sample_logo) + .apply(options) + + if (useRoundedCornersLayout) { + val cornerRadius = resources.getDimension(R.dimen.rounded_corner_radius_big).toInt() + builder = builder.transform(CenterCrop(), RoundedCorners(cornerRadius)) + dir_name.setTextColor(activity.config.textColor) + photo_cnt.setTextColor(activity.config.textColor) + } + + builder.into(dir_thumbnail) + } + } + + override fun onClick(dialog: DialogInterface, which: Int) { + val style = when (view.dialog_radio_folder_style.checkedRadioButtonId) { + R.id.dialog_radio_folder_square -> FOLDER_STYLE_SQUARE + else -> FOLDER_STYLE_ROUNDED_CORNERS + } + + val count = when (view.dialog_radio_folder_count_holder.checkedRadioButtonId) { + R.id.dialog_radio_folder_count_line -> FOLDER_MEDIA_CNT_LINE + R.id.dialog_radio_folder_count_brackets -> FOLDER_MEDIA_CNT_BRACKETS + else -> FOLDER_MEDIA_CNT_NONE + } + + config.folderStyle = style + config.showFolderMediaCount = count + config.limitFolderTitle = view.dialog_folder_limit_title.isChecked + callback() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeGroupingDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeGroupingDialog.kt new file mode 100644 index 000000000..71da2f57d --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeGroupingDialog.kt @@ -0,0 +1,90 @@ +package com.simplemobiletools.gallery.pro.dialogs + +import android.content.DialogInterface +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.* +import kotlinx.android.synthetic.main.dialog_change_grouping.view.* + +class ChangeGroupingDialog(val activity: BaseSimpleActivity, val path: String = "", val callback: () -> Unit) : + DialogInterface.OnClickListener { + private var currGrouping = 0 + private var config = activity.config + private val pathToUse = if (path.isEmpty()) SHOW_ALL else path + private var view: View + + init { + view = activity.layoutInflater.inflate(R.layout.dialog_change_grouping, null).apply { + grouping_dialog_use_for_this_folder.isChecked = config.hasCustomGrouping(pathToUse) + grouping_dialog_radio_folder.beVisibleIf(path.isEmpty()) + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok, this) + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this, R.string.group_by) + } + + currGrouping = config.getFolderGrouping(pathToUse) + setupGroupRadio() + setupOrderRadio() + } + + private fun setupGroupRadio() { + val groupingRadio = view.grouping_dialog_radio_grouping + + val groupBtn = when { + currGrouping and GROUP_BY_NONE != 0 -> groupingRadio.grouping_dialog_radio_none + currGrouping and GROUP_BY_LAST_MODIFIED_DAILY != 0 -> groupingRadio.grouping_dialog_radio_last_modified_daily + currGrouping and GROUP_BY_LAST_MODIFIED_MONTHLY != 0 -> groupingRadio.grouping_dialog_radio_last_modified_monthly + currGrouping and GROUP_BY_DATE_TAKEN_DAILY != 0 -> groupingRadio.grouping_dialog_radio_date_taken_daily + currGrouping and GROUP_BY_DATE_TAKEN_MONTHLY != 0 -> groupingRadio.grouping_dialog_radio_date_taken_monthly + currGrouping and GROUP_BY_FILE_TYPE != 0 -> groupingRadio.grouping_dialog_radio_file_type + currGrouping and GROUP_BY_EXTENSION != 0 -> groupingRadio.grouping_dialog_radio_extension + else -> groupingRadio.grouping_dialog_radio_folder + } + groupBtn.isChecked = true + } + + private fun setupOrderRadio() { + val orderRadio = view.grouping_dialog_radio_order + var orderBtn = orderRadio.grouping_dialog_radio_ascending + + if (currGrouping and GROUP_DESCENDING != 0) { + orderBtn = orderRadio.grouping_dialog_radio_descending + } + orderBtn.isChecked = true + } + + override fun onClick(dialog: DialogInterface, which: Int) { + val groupingRadio = view.grouping_dialog_radio_grouping + var grouping = when (groupingRadio.checkedRadioButtonId) { + R.id.grouping_dialog_radio_none -> GROUP_BY_NONE + R.id.grouping_dialog_radio_last_modified_daily -> GROUP_BY_LAST_MODIFIED_DAILY + R.id.grouping_dialog_radio_last_modified_monthly -> GROUP_BY_LAST_MODIFIED_MONTHLY + R.id.grouping_dialog_radio_date_taken_daily -> GROUP_BY_DATE_TAKEN_DAILY + R.id.grouping_dialog_radio_date_taken_monthly -> GROUP_BY_DATE_TAKEN_MONTHLY + R.id.grouping_dialog_radio_file_type -> GROUP_BY_FILE_TYPE + R.id.grouping_dialog_radio_extension -> GROUP_BY_EXTENSION + else -> GROUP_BY_FOLDER + } + + if (view.grouping_dialog_radio_order.checkedRadioButtonId == R.id.grouping_dialog_radio_descending) { + grouping = grouping or GROUP_DESCENDING + } + + if (view.grouping_dialog_use_for_this_folder.isChecked) { + config.saveFolderGrouping(pathToUse, grouping) + } else { + config.removeFolderGrouping(pathToUse) + config.groupBy = grouping + } + callback() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeSortingDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeSortingDialog.kt new file mode 100644 index 000000000..cffa62738 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeSortingDialog.kt @@ -0,0 +1,108 @@ +package com.simplemobiletools.gallery.pro.dialogs + +import android.content.DialogInterface +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.isVisible +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.helpers.* +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, val showFolderCheckbox: Boolean, + val path: String = "", val callback: () -> Unit) : + DialogInterface.OnClickListener { + private var currSorting = 0 + private var config = activity.config + private var pathToUse = if (!isDirectorySorting && path.isEmpty()) SHOW_ALL else path + private var view: View + + init { + currSorting = if (isDirectorySorting) config.directorySorting else config.getFolderSorting(pathToUse) + view = activity.layoutInflater.inflate(R.layout.dialog_change_sorting, null).apply { + use_for_this_folder_divider.beVisibleIf(showFolderCheckbox || (currSorting and SORT_BY_NAME != 0 || currSorting and SORT_BY_PATH != 0)) + + sorting_dialog_numeric_sorting.beVisibleIf(showFolderCheckbox && (currSorting and SORT_BY_NAME != 0 || currSorting and SORT_BY_PATH != 0)) + sorting_dialog_numeric_sorting.isChecked = currSorting and SORT_USE_NUMERIC_VALUE != 0 + + sorting_dialog_use_for_this_folder.beVisibleIf(showFolderCheckbox) + sorting_dialog_use_for_this_folder.isChecked = config.hasCustomSorting(pathToUse) + sorting_dialog_bottom_note.beVisibleIf(!isDirectorySorting) + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok, this) + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this, R.string.sort_by) + } + + setupSortRadio() + setupOrderRadio() + } + + private fun setupSortRadio() { + val sortingRadio = view.sorting_dialog_radio_sorting + sortingRadio.setOnCheckedChangeListener { group, checkedId -> + val isSortingByNameOrPath = checkedId == sortingRadio.sorting_dialog_radio_name.id || checkedId == sortingRadio.sorting_dialog_radio_path.id + view.sorting_dialog_numeric_sorting.beVisibleIf(isSortingByNameOrPath) + view.use_for_this_folder_divider.beVisibleIf(view.sorting_dialog_numeric_sorting.isVisible() || view.sorting_dialog_use_for_this_folder.isVisible()) + } + + val sortBtn = when { + currSorting and SORT_BY_PATH != 0 -> sortingRadio.sorting_dialog_radio_path + currSorting and SORT_BY_SIZE != 0 -> sortingRadio.sorting_dialog_radio_size + currSorting and SORT_BY_DATE_MODIFIED != 0 -> sortingRadio.sorting_dialog_radio_last_modified + currSorting and SORT_BY_DATE_TAKEN != 0 -> sortingRadio.sorting_dialog_radio_date_taken + currSorting and SORT_BY_RANDOM != 0 -> sortingRadio.sorting_dialog_radio_random + else -> sortingRadio.sorting_dialog_radio_name + } + sortBtn.isChecked = true + } + + private fun setupOrderRadio() { + val orderRadio = view.sorting_dialog_radio_order + var orderBtn = orderRadio.sorting_dialog_radio_ascending + + if (currSorting and SORT_DESCENDING != 0) { + orderBtn = orderRadio.sorting_dialog_radio_descending + } + orderBtn.isChecked = true + } + + override fun onClick(dialog: DialogInterface, which: Int) { + val sortingRadio = view.sorting_dialog_radio_sorting + var sorting = when (sortingRadio.checkedRadioButtonId) { + R.id.sorting_dialog_radio_name -> SORT_BY_NAME + R.id.sorting_dialog_radio_path -> SORT_BY_PATH + R.id.sorting_dialog_radio_size -> SORT_BY_SIZE + R.id.sorting_dialog_radio_last_modified -> SORT_BY_DATE_MODIFIED + R.id.sorting_dialog_radio_random -> SORT_BY_RANDOM + else -> SORT_BY_DATE_TAKEN + } + + if (view.sorting_dialog_radio_order.checkedRadioButtonId == R.id.sorting_dialog_radio_descending) { + sorting = sorting or SORT_DESCENDING + } + + if (view.sorting_dialog_numeric_sorting.isChecked) { + sorting = sorting or SORT_USE_NUMERIC_VALUE + } + + if (isDirectorySorting) { + config.directorySorting = sorting + } else { + if (view.sorting_dialog_use_for_this_folder.isChecked) { + config.saveCustomSorting(pathToUse, sorting) + } else { + config.removeCustomSorting(pathToUse) + config.sorting = sorting + } + } + callback() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeViewTypeDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeViewTypeDialog.kt new file mode 100644 index 000000000..c64fbde04 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeViewTypeDialog.kt @@ -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.commons.helpers.VIEW_TYPE_GRID +import com.simplemobiletools.commons.helpers.VIEW_TYPE_LIST +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_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() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ConfirmDeleteFolderDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ConfirmDeleteFolderDialog.kt new file mode 100644 index 000000000..27f3a59c9 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ConfirmDeleteFolderDialog.kt @@ -0,0 +1,31 @@ +package com.simplemobiletools.gallery.pro.dialogs + +import android.app.Activity +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.gallery.pro.R +import kotlinx.android.synthetic.main.dialog_confirm_delete_folder.view.* + +class ConfirmDeleteFolderDialog(activity: Activity, message: String, warningMessage: String, val callback: () -> Unit) { + var dialog: AlertDialog + + init { + val view = activity.layoutInflater.inflate(R.layout.dialog_confirm_delete_folder, null) + view.message.text = message + view.message_warning.text = warningMessage + + val builder = AlertDialog.Builder(activity) + .setPositiveButton(R.string.yes) { dialog, which -> dialogConfirmed() } + + builder.setNegativeButton(R.string.no, null) + + dialog = builder.create().apply { + activity.setupDialogStuff(view, this) + } + } + + private fun dialogConfirmed() { + dialog.dismiss() + callback() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/CustomAspectRatioDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/CustomAspectRatioDialog.kt new file mode 100644 index 000000000..47df4b89e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/CustomAspectRatioDialog.kt @@ -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?, val callback: (aspectRatio: Pair) -> Unit) { + init { + val view = activity.layoutInflater.inflate(R.layout.dialog_custom_aspect_ratio, null).apply { + aspect_ratio_width.setText(defaultCustomAspectRatio?.first?.toInt()?.toString() ?: "") + aspect_ratio_height.setText(defaultCustomAspectRatio?.second?.toInt()?.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): Float { + val textValue = view.value + return if (textValue.isEmpty()) 0f else textValue.toFloat() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/DeleteWithRememberDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/DeleteWithRememberDialog.kt new file mode 100644 index 000000000..c570820b8 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/DeleteWithRememberDialog.kt @@ -0,0 +1,28 @@ +package com.simplemobiletools.gallery.pro.dialogs + +import android.app.Activity +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.commons.extensions.setupDialogStuff +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) { + private var dialog: AlertDialog + val view = activity.layoutInflater.inflate(R.layout.dialog_delete_with_remember, null)!! + + init { + view.delete_remember_title.text = message + val builder = AlertDialog.Builder(activity) + .setPositiveButton(R.string.yes) { dialog, which -> dialogConfirmed() } + .setNegativeButton(R.string.no, null) + + dialog = builder.create().apply { + activity.setupDialogStuff(view, this) + } + } + + private fun dialogConfirmed() { + dialog.dismiss() + callback(view.delete_remember_checkbox.isChecked) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ExcludeFolderDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ExcludeFolderDialog.kt similarity index 75% rename from app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ExcludeFolderDialog.kt rename to app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ExcludeFolderDialog.kt index 936433cb3..8575f75a4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/dialogs/ExcludeFolderDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ExcludeFolderDialog.kt @@ -1,25 +1,23 @@ -package com.simplemobiletools.gallery.dialogs +package com.simplemobiletools.gallery.pro.dialogs -import android.support.v7.app.AlertDialog -import android.view.LayoutInflater 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.activities.SimpleActivity -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: SimpleActivity, val selectedPaths: List, val callback: () -> Unit) { - var dialog: AlertDialog? = null +class ExcludeFolderDialog(val activity: BaseSimpleActivity, val selectedPaths: List, val callback: () -> Unit) { val alternativePaths = getAlternativePathsList() var radioGroup: RadioGroup? = null init { - val view = LayoutInflater.from(activity).inflate(R.layout.dialog_exclude_folder, null).apply { + val view = activity.layoutInflater.inflate(R.layout.dialog_exclude_folder, null).apply { exclude_folder_parent.beVisibleIf(alternativePaths.size > 1) radioGroup = exclude_folder_radio_group @@ -36,17 +34,17 @@ class ExcludeFolderDialog(val activity: SimpleActivity, val selectedPaths: List< } 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() { val path = if (alternativePaths.isEmpty()) selectedPaths[0] else alternativePaths[radioGroup!!.checkedRadioButtonId] activity.config.addExcludedFolder(path) - callback.invoke() + callback() } private fun getAlternativePathsList(): List { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/FilterMediaDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/FilterMediaDialog.kt new file mode 100644 index 000000000..5bcd1d09c --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/FilterMediaDialog.kt @@ -0,0 +1,55 @@ +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 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) { + private var view = activity.layoutInflater.inflate(R.layout.dialog_filter_media, null) + + init { + val filterMedia = activity.config.filterMedia + view.apply { + filter_media_images.isChecked = filterMedia and TYPE_IMAGES != 0 + filter_media_videos.isChecked = filterMedia and TYPE_VIDEOS != 0 + filter_media_gifs.isChecked = filterMedia and TYPE_GIFS != 0 + filter_media_raws.isChecked = filterMedia and TYPE_RAWS != 0 + filter_media_svgs.isChecked = filterMedia and TYPE_SVGS != 0 + filter_media_portraits.isChecked = filterMedia and TYPE_PORTRAITS != 0 + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() } + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this, R.string.filter_media) + } + } + + private fun dialogConfirmed() { + var result = 0 + if (view.filter_media_images.isChecked) + result += TYPE_IMAGES + if (view.filter_media_videos.isChecked) + result += TYPE_VIDEOS + if (view.filter_media_gifs.isChecked) + result += TYPE_GIFS + if (view.filter_media_raws.isChecked) + result += TYPE_RAWS + if (view.filter_media_svgs.isChecked) + result += TYPE_SVGS + if (view.filter_media_portraits.isChecked) + result += TYPE_PORTRAITS + + if (result == 0) { + result = getDefaultFileFilter() + } + + activity.config.filterMedia = result + callback(result) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ManageBottomActionsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ManageBottomActionsDialog.kt new file mode 100644 index 000000000..cfbe131f8 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ManageBottomActionsDialog.kt @@ -0,0 +1,80 @@ +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 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) { + private var view = activity.layoutInflater.inflate(R.layout.dialog_manage_bottom_actions, null) + + init { + val actions = activity.config.visibleBottomActions + view.apply { + manage_bottom_actions_toggle_favorite.isChecked = actions and BOTTOM_ACTION_TOGGLE_FAVORITE != 0 + manage_bottom_actions_edit.isChecked = actions and BOTTOM_ACTION_EDIT != 0 + manage_bottom_actions_share.isChecked = actions and BOTTOM_ACTION_SHARE != 0 + manage_bottom_actions_delete.isChecked = actions and BOTTOM_ACTION_DELETE != 0 + manage_bottom_actions_rotate.isChecked = actions and BOTTOM_ACTION_ROTATE != 0 + manage_bottom_actions_properties.isChecked = actions and BOTTOM_ACTION_PROPERTIES != 0 + manage_bottom_actions_change_orientation.isChecked = actions and BOTTOM_ACTION_CHANGE_ORIENTATION != 0 + manage_bottom_actions_slideshow.isChecked = actions and BOTTOM_ACTION_SLIDESHOW != 0 + manage_bottom_actions_show_on_map.isChecked = actions and BOTTOM_ACTION_SHOW_ON_MAP != 0 + manage_bottom_actions_toggle_visibility.isChecked = actions and BOTTOM_ACTION_TOGGLE_VISIBILITY != 0 + manage_bottom_actions_rename.isChecked = actions and BOTTOM_ACTION_RENAME != 0 + manage_bottom_actions_set_as.isChecked = actions and BOTTOM_ACTION_SET_AS != 0 + manage_bottom_actions_copy.isChecked = actions and BOTTOM_ACTION_COPY != 0 + manage_bottom_actions_move.isChecked = actions and BOTTOM_ACTION_MOVE != 0 + manage_bottom_actions_resize.isChecked = actions and BOTTOM_ACTION_RESIZE != 0 + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() } + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this) + } + } + + private fun dialogConfirmed() { + var result = 0 + view.apply { + if (manage_bottom_actions_toggle_favorite.isChecked) + result += BOTTOM_ACTION_TOGGLE_FAVORITE + if (manage_bottom_actions_edit.isChecked) + result += BOTTOM_ACTION_EDIT + if (manage_bottom_actions_share.isChecked) + result += BOTTOM_ACTION_SHARE + if (manage_bottom_actions_delete.isChecked) + result += BOTTOM_ACTION_DELETE + if (manage_bottom_actions_rotate.isChecked) + result += BOTTOM_ACTION_ROTATE + if (manage_bottom_actions_properties.isChecked) + result += BOTTOM_ACTION_PROPERTIES + if (manage_bottom_actions_change_orientation.isChecked) + result += BOTTOM_ACTION_CHANGE_ORIENTATION + if (manage_bottom_actions_slideshow.isChecked) + result += BOTTOM_ACTION_SLIDESHOW + if (manage_bottom_actions_show_on_map.isChecked) + result += BOTTOM_ACTION_SHOW_ON_MAP + if (manage_bottom_actions_toggle_visibility.isChecked) + result += BOTTOM_ACTION_TOGGLE_VISIBILITY + if (manage_bottom_actions_rename.isChecked) + 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 + if (manage_bottom_actions_move.isChecked) + result += BOTTOM_ACTION_MOVE + if (manage_bottom_actions_resize.isChecked) + result += BOTTOM_ACTION_RESIZE + } + + activity.config.visibleBottomActions = result + callback(result) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ManageExtendedDetailsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ManageExtendedDetailsDialog.kt new file mode 100644 index 000000000..6c52c3408 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ManageExtendedDetailsDialog.kt @@ -0,0 +1,62 @@ +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 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) { + private var view = activity.layoutInflater.inflate(R.layout.dialog_manage_extended_details, null) + + init { + val details = activity.config.extendedDetails + view.apply { + manage_extended_details_name.isChecked = details and EXT_NAME != 0 + manage_extended_details_path.isChecked = details and EXT_PATH != 0 + manage_extended_details_size.isChecked = details and EXT_SIZE != 0 + manage_extended_details_resolution.isChecked = details and EXT_RESOLUTION != 0 + manage_extended_details_last_modified.isChecked = details and EXT_LAST_MODIFIED != 0 + manage_extended_details_date_taken.isChecked = details and EXT_DATE_TAKEN != 0 + manage_extended_details_camera.isChecked = details and EXT_CAMERA_MODEL != 0 + manage_extended_details_exif.isChecked = details and EXT_EXIF_PROPERTIES != 0 + manage_extended_details_gps_coordinates.isChecked = details and EXT_GPS != 0 + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() } + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this) + } + } + + private fun dialogConfirmed() { + var result = 0 + view.apply { + if (manage_extended_details_name.isChecked) + result += EXT_NAME + if (manage_extended_details_path.isChecked) + result += EXT_PATH + if (manage_extended_details_size.isChecked) + result += EXT_SIZE + if (manage_extended_details_resolution.isChecked) + result += EXT_RESOLUTION + if (manage_extended_details_last_modified.isChecked) + result += EXT_LAST_MODIFIED + if (manage_extended_details_date_taken.isChecked) + result += EXT_DATE_TAKEN + if (manage_extended_details_camera.isChecked) + result += EXT_CAMERA_MODEL + if (manage_extended_details_exif.isChecked) + result += EXT_EXIF_PROPERTIES + if (manage_extended_details_gps_coordinates.isChecked) + result += EXT_GPS + } + + activity.config.extendedDetails = result + callback(result) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/OtherAspectRatioDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/OtherAspectRatioDialog.kt new file mode 100644 index 000000000..76f7e7e78 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/OtherAspectRatioDialog.kt @@ -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?, val callback: (aspectRatio: Pair) -> 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(2f, 1f)) } + other_aspect_ratio_3_2.setOnClickListener { ratioPicked(Pair(3f, 2f)) } + other_aspect_ratio_4_3.setOnClickListener { ratioPicked(Pair(4f, 3f)) } + other_aspect_ratio_5_3.setOnClickListener { ratioPicked(Pair(5f, 3f)) } + other_aspect_ratio_16_9.setOnClickListener { ratioPicked(Pair(16f, 9f)) } + other_aspect_ratio_19_9.setOnClickListener { ratioPicked(Pair(19f, 9f)) } + other_aspect_ratio_custom.setOnClickListener { customRatioPicked() } + + other_aspect_ratio_1_2.setOnClickListener { ratioPicked(Pair(1f, 2f)) } + other_aspect_ratio_2_3.setOnClickListener { ratioPicked(Pair(2f, 3f)) } + other_aspect_ratio_3_4.setOnClickListener { ratioPicked(Pair(3f, 4f)) } + other_aspect_ratio_3_5.setOnClickListener { ratioPicked(Pair(3f, 5f)) } + other_aspect_ratio_9_16.setOnClickListener { ratioPicked(Pair(9f, 16f)) } + other_aspect_ratio_9_19.setOnClickListener { ratioPicked(Pair(9f, 19f)) } + + val radio1SelectedItemId = when (lastOtherAspectRatio) { + Pair(2f, 1f) -> other_aspect_ratio_2_1.id + Pair(3f, 2f) -> other_aspect_ratio_3_2.id + Pair(4f, 3f) -> other_aspect_ratio_4_3.id + Pair(5f, 3f) -> other_aspect_ratio_5_3.id + Pair(16f, 9f) -> other_aspect_ratio_16_9.id + Pair(19f, 9f) -> other_aspect_ratio_19_9.id + else -> 0 + } + other_aspect_ratio_dialog_radio_1.check(radio1SelectedItemId) + + val radio2SelectedItemId = when (lastOtherAspectRatio) { + Pair(1f, 2f) -> other_aspect_ratio_1_2.id + Pair(2f, 3f) -> other_aspect_ratio_2_3.id + Pair(3f, 4f) -> other_aspect_ratio_3_4.id + Pair(3f, 5f) -> other_aspect_ratio_3_5.id + Pair(9f, 16f) -> other_aspect_ratio_9_16.id + Pair(9f, 19f) -> 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) { + callback(pair) + dialog.dismiss() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/PickDirectoryDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/PickDirectoryDialog.kt new file mode 100644 index 000000000..53ee995c2 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/PickDirectoryDialog.kt @@ -0,0 +1,161 @@ +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.helpers.VIEW_TYPE_GRID +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.models.Directory +import kotlinx.android.synthetic.main.dialog_directory_picker.view.* + +class PickDirectoryDialog(val activity: BaseSimpleActivity, val sourcePath: String, showOtherFolderButton: Boolean, val showFavoritesBin: Boolean, + val callback: (path: String) -> Unit) { + private var dialog: AlertDialog + private var shownDirectories = ArrayList() + private var allDirectories = ArrayList() + 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) { + activity.handleLockedFolderOpening(it) { success -> + if (success) { + callback(it) + } + } + } + } + + private fun gotDirectories(newDirs: ArrayList) { + if (allDirectories.isEmpty()) { + allDirectories = newDirs.clone() as ArrayList + } + + val distinctDirs = newDirs.filter { showFavoritesBin || (!it.isRecycleBin() && !it.areFavorites()) }.distinctBy { it.path.getDistinctPath() }.toMutableList() as ArrayList + val sortedDirs = activity.getSortedDirectories(distinctDirs) + val dirs = activity.getDirsToShow(sortedDirs, allDirectories, currentPathPrefix).clone() as ArrayList + if (dirs.hashCode() == shownDirectories.hashCode()) { + return + } + + shownDirectories = dirs + val adapter = DirectoryAdapter(activity, dirs.clone() as ArrayList, 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 { + activity.handleLockedFolderOpening(path) { success -> + if (success) { + callback(path) + } + } + dialog.dismiss() + } + } else { + currentPathPrefix = path + openedSubfolders.add(path) + gotDirectories(allDirectories) + } + } + + val scrollHorizontally = activity.config.scrollHorizontally && isGridViewType + val sorting = activity.config.directorySorting + val dateFormat = activity.config.dateFormat + val timeFormat = activity.getTimeFormat() + 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.setViews(directories_grid) { + directories_horizontal_fastscroller.updateBubbleText(dirs[it].getBubbleText(sorting, activity, dateFormat, timeFormat)) + } + } else { + directories_vertical_fastscroller.setViews(directories_grid) { + directories_vertical_fastscroller.updateBubbleText(dirs[it].getBubbleText(sorting, activity, dateFormat, timeFormat)) + } + } + } + } + + 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() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/PickMediumDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/PickMediumDialog.kt new file mode 100644 index 000000000..1e3b2465e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/PickMediumDialog.kt @@ -0,0 +1,102 @@ +package com.simplemobiletools.gallery.pro.dialogs + +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.getTimeFormat +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.helpers.VIEW_TYPE_GRID +import com.simplemobiletools.commons.views.MyGridLayoutManager +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.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() + val view = activity.layoutInflater.inflate(R.layout.dialog_medium_picker, null) + 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) RecyclerView.HORIZONTAL else RecyclerView.VERTICAL + spanCount = if (isGridViewType) activity.config.mediaColumnCnt 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_photo) + } + + activity.getCachedMedia(path) { + val media = it.filter { it is Medium } as ArrayList + if (media.isNotEmpty()) { + activity.runOnUiThread { + gotMedia(media) + } + } + } + + GetMediaAsynctask(activity, path, false, false, false) { + gotMedia(it) + }.execute() + } + + private fun showOtherFolder() { + PickDirectoryDialog(activity, path, true, true) { + callback(it) + dialog.dismiss() + } + } + + private fun gotMedia(media: ArrayList) { + if (media.hashCode() == shownMedia.hashCode()) + return + + shownMedia = media + val adapter = MediaAdapter(activity, shownMedia.clone() as ArrayList, null, true, false, path, view.media_grid, null) { + if (it is Medium) { + callback(it.path) + dialog.dismiss() + } + } + + val scrollHorizontally = activity.config.scrollHorizontally && isGridViewType + val sorting = activity.config.getFolderSorting(if (path.isEmpty()) SHOW_ALL else path) + val dateFormat = activity.config.dateFormat + val timeFormat = activity.getTimeFormat() + view.apply { + media_grid.adapter = adapter + + media_vertical_fastscroller.isHorizontal = false + media_vertical_fastscroller.beGoneIf(scrollHorizontally) + + media_horizontal_fastscroller.isHorizontal = true + media_horizontal_fastscroller.beVisibleIf(scrollHorizontally) + + if (scrollHorizontally) { + media_horizontal_fastscroller.setViews(media_grid) { + val medium = (media[it] as? Medium) + media_horizontal_fastscroller.updateBubbleText(medium?.getBubbleText(sorting, activity, dateFormat, timeFormat) ?: "") + } + } else { + media_vertical_fastscroller.setViews(media_grid) { + val medium = (media[it] as? Medium) + media_vertical_fastscroller.updateBubbleText(medium?.getBubbleText(sorting, activity, dateFormat, timeFormat) ?: "") + } + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ResizeDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ResizeDialog.kt new file mode 100644 index 000000000..96ef3b50b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ResizeDialog.kt @@ -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() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ResizeWithPathDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ResizeWithPathDialog.kt new file mode 100644 index 000000000..b377f6801 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ResizeWithPathDialog.kt @@ -0,0 +1,125 @@ +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.dialogs.ConfirmationDialog +import com.simplemobiletools.commons.dialogs.FilePickerDialog +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.gallery.pro.R +import com.simplemobiletools.gallery.pro.extensions.config +import kotlinx.android.synthetic.main.dialog_resize_image_with_path.view.* + +class ResizeWithPathDialog(val activity: BaseSimpleActivity, val size: Point, val path: String, val callback: (newSize: Point, newPath: String) -> Unit) { + init { + var realPath = path.getParentPath() + val view = activity.layoutInflater.inflate(R.layout.dialog_resize_image_with_path, null).apply { + image_path.text = "${activity.humanizePath(realPath).trimEnd('/')}/" + + val fullName = path.getFilenameFromPath() + val dotAt = fullName.lastIndexOf(".") + var name = fullName + + if (dotAt > 0) { + name = fullName.substring(0, dotAt) + val extension = fullName.substring(dotAt + 1) + image_extension.setText(extension) + } + + image_name.setText(name) + image_path.setOnClickListener { + FilePickerDialog(activity, realPath, false, activity.config.shouldShowHidden, true, true) { + image_path.text = activity.humanizePath(it) + realPath = it + } + } + } + + 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 + } + + 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 + } + + 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) { + 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)) + + val filename = view.image_name.value + val extension = view.image_extension.value + if (filename.isEmpty()) { + activity.toast(R.string.filename_cannot_be_empty) + return@setOnClickListener + } + + if (extension.isEmpty()) { + activity.toast(R.string.extension_cannot_be_empty) + return@setOnClickListener + } + + val newFilename = "$filename.$extension" + val newPath = "${realPath.trimEnd('/')}/$newFilename" + if (!newFilename.isAValidFilename()) { + activity.toast(R.string.filename_invalid_characters) + return@setOnClickListener + } + + if (activity.getDoesFilePathExist(newPath)) { + val title = String.format(activity.getString(R.string.file_already_exists_overwrite), newFilename) + ConfirmationDialog(activity, title) { + callback(newSize, newPath) + dismiss() + } + } else { + callback(newSize, newPath) + dismiss() + } + } + } + } + } + + private fun getViewValue(view: EditText): Int { + val textValue = view.value + return if (textValue.isEmpty()) 0 else textValue.toInt() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/SaveAsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/SaveAsDialog.kt new file mode 100644 index 000000000..4331b3120 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/SaveAsDialog.kt @@ -0,0 +1,86 @@ +package com.simplemobiletools.gallery.pro.dialogs + +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.pro.R +import kotlinx.android.synthetic.main.dialog_save_as.view.* + +class SaveAsDialog(val activity: BaseSimpleActivity, val path: String, val appendFilename: Boolean, val cancelCallback: (() -> Unit)? = null, + val callback: (savePath: String) -> Unit) { + + init { + var realPath = path.getParentPath() + + val view = activity.layoutInflater.inflate(R.layout.dialog_save_as, null).apply { + save_as_path.text = "${activity.humanizePath(realPath).trimEnd('/')}/" + + val fullName = path.getFilenameFromPath() + val dotAt = fullName.lastIndexOf(".") + var name = fullName + + if (dotAt > 0) { + name = fullName.substring(0, dotAt) + val extension = fullName.substring(dotAt + 1) + save_as_extension.setText(extension) + } + + if (appendFilename) { + name += "_1" + } + + save_as_name.setText(name) + save_as_path.setOnClickListener { + activity.hideKeyboard(save_as_path) + FilePickerDialog(activity, realPath, false, false, true, true) { + save_as_path.text = activity.humanizePath(it) + realPath = it + } + } + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel) { dialog, which -> cancelCallback?.invoke() } + .setOnCancelListener { cancelCallback?.invoke() } + .create().apply { + activity.setupDialogStuff(view, this, R.string.save_as) { + showKeyboard(view.save_as_name) + getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val filename = view.save_as_name.value + val extension = view.save_as_extension.value + + if (filename.isEmpty()) { + activity.toast(R.string.filename_cannot_be_empty) + return@setOnClickListener + } + + if (extension.isEmpty()) { + activity.toast(R.string.extension_cannot_be_empty) + return@setOnClickListener + } + + val newFilename = "$filename.$extension" + val newPath = "${realPath.trimEnd('/')}/$newFilename" + if (!newFilename.isAValidFilename()) { + activity.toast(R.string.filename_invalid_characters) + return@setOnClickListener + } + + if (activity.getDoesFilePathExist(newPath)) { + val title = String.format(activity.getString(R.string.file_already_exists_overwrite), newFilename) + ConfirmationDialog(activity, title) { + callback(newPath) + dismiss() + } + } else { + callback(newPath) + dismiss() + } + } + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/SlideshowDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/SlideshowDialog.kt new file mode 100644 index 000000000..66d57fef9 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/SlideshowDialog.kt @@ -0,0 +1,135 @@ +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.dialogs.RadioGroupDialog +import com.simplemobiletools.commons.extensions.hideKeyboard +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.extensions.value +import com.simplemobiletools.commons.models.RadioItem +import com.simplemobiletools.gallery.pro.R +import com.simplemobiletools.gallery.pro.extensions.config +import com.simplemobiletools.gallery.pro.helpers.SLIDESHOW_ANIMATION_FADE +import com.simplemobiletools.gallery.pro.helpers.SLIDESHOW_ANIMATION_NONE +import com.simplemobiletools.gallery.pro.helpers.SLIDESHOW_ANIMATION_SLIDE +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) { + val view: View + + init { + view = activity.layoutInflater.inflate(R.layout.dialog_slideshow, null).apply { + interval_value.setOnClickListener { + val text = interval_value.text + if (text.isNotEmpty()) { + text.replace(0, 1, text.subSequence(0, 1), 0, 1) + interval_value.selectAll() + } + } + + interval_value.setOnFocusChangeListener { v, hasFocus -> + if (!hasFocus) + activity.hideKeyboard(v) + } + + animation_holder.setOnClickListener { + val items = arrayListOf( + RadioItem(SLIDESHOW_ANIMATION_NONE, activity.getString(R.string.no_animation)), + RadioItem(SLIDESHOW_ANIMATION_SLIDE, activity.getString(R.string.slide)), + RadioItem(SLIDESHOW_ANIMATION_FADE, activity.getString(R.string.fade))) + + RadioGroupDialog(activity, items, activity.config.slideshowAnimation) { + activity.config.slideshowAnimation = it as Int + animation_value.text = getAnimationText() + } + } + + include_videos_holder.setOnClickListener { + interval_value.clearFocus() + include_videos.toggle() + } + + include_gifs_holder.setOnClickListener { + interval_value.clearFocus() + include_gifs.toggle() + } + + random_order_holder.setOnClickListener { + interval_value.clearFocus() + random_order.toggle() + } + + move_backwards_holder.setOnClickListener { + interval_value.clearFocus() + move_backwards.toggle() + } + + loop_slideshow_holder.setOnClickListener { + interval_value.clearFocus() + loop_slideshow.toggle() + } + } + setupValues() + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this) { + hideKeyboard() + getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + storeValues() + callback() + dismiss() + } + } + } + } + + private fun setupValues() { + val config = activity.config + view.apply { + interval_value.setText(config.slideshowInterval.toString()) + animation_value.text = getAnimationText() + include_videos.isChecked = config.slideshowIncludeVideos + include_gifs.isChecked = config.slideshowIncludeGIFs + random_order.isChecked = config.slideshowRandomOrder + move_backwards.isChecked = config.slideshowMoveBackwards + loop_slideshow.isChecked = config.loopSlideshow + } + } + + private fun storeValues() { + var interval = view.interval_value.text.toString() + if (interval.trim('0').isEmpty()) + interval = SLIDESHOW_DEFAULT_INTERVAL.toString() + + activity.config.apply { + slideshowAnimation = getAnimationValue(view.animation_value.value) + slideshowInterval = interval.toInt() + slideshowIncludeVideos = view.include_videos.isChecked + slideshowIncludeGIFs = view.include_gifs.isChecked + slideshowRandomOrder = view.random_order.isChecked + slideshowMoveBackwards = view.move_backwards.isChecked + loopSlideshow = view.loop_slideshow.isChecked + } + } + + private fun getAnimationText(): String { + return when (activity.config.slideshowAnimation) { + SLIDESHOW_ANIMATION_SLIDE -> activity.getString(R.string.slide) + SLIDESHOW_ANIMATION_FADE -> activity.getString(R.string.fade) + else -> activity.getString(R.string.no_animation) + } + } + + private fun getAnimationValue(text: String): Int { + return when (text) { + activity.getString(R.string.slide) -> SLIDESHOW_ANIMATION_SLIDE + activity.getString(R.string.fade) -> SLIDESHOW_ANIMATION_FADE + else -> SLIDESHOW_ANIMATION_NONE + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Activity.kt new file mode 100644 index 000000000..389f2fb7a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Activity.kt @@ -0,0 +1,675 @@ +package com.simplemobiletools.gallery.pro.extensions + +import android.annotation.TargetApi +import android.app.Activity +import android.content.ContentProviderOperation +import android.content.ContentValues +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Matrix +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.net.Uri +import android.os.Build +import android.provider.MediaStore +import android.provider.MediaStore.Files +import android.provider.MediaStore.Images +import android.util.DisplayMetrics +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.exifinterface.media.ExifInterface +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DecodeFormat +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.request.RequestOptions +import com.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.RECYCLE_BIN +import com.simplemobiletools.gallery.pro.models.DateTaken +import com.squareup.picasso.Picasso +import java.io.* +import java.text.SimpleDateFormat +import java.util.* + +fun Activity.sharePath(path: String) { + sharePathIntent(path, BuildConfig.APPLICATION_ID) +} + +fun Activity.sharePaths(paths: ArrayList) { + sharePathsIntent(paths, BuildConfig.APPLICATION_ID) +} + +fun Activity.shareMediumPath(path: String) { + sharePath(path) +} + +fun Activity.shareMediaPaths(paths: ArrayList) { + sharePaths(paths) +} + +fun Activity.setAs(path: String) { + setAsIntent(path, BuildConfig.APPLICATION_ID) +} + +fun Activity.openPath(path: String, forceChooser: Boolean, extras: HashMap = HashMap()) { + openPathIntent(path, forceChooser, BuildConfig.APPLICATION_ID, extras = extras) +} + +fun Activity.openEditor(path: String, forceChooser: Boolean = false) { + val newPath = path.removePrefix("file://") + openEditorIntent(newPath, 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 or + LICENSE_APNG + + 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_15_title, R.string.faq_15_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), + FAQItem(R.string.faq_7_title_commons, R.string.faq_7_text_commons), + FAQItem(R.string.faq_9_title_commons, R.string.faq_9_text_commons), + FAQItem(R.string.faq_10_title_commons, R.string.faq_10_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 (getDoesFilePathExist(file.absolutePath)) { + callback() + return + } + + if (needsStupidWritePermissions(path)) { + handleSAFDialog(file.absolutePath) { + if (!it) { + return@handleSAFDialog + } + + val fileDocument = getDocumentFile(path) + if (fileDocument?.exists() == true && fileDocument.isDirectory) { + fileDocument.createFile("", NOMEDIA) + addNoMediaIntoMediaStore(file.absolutePath) + callback() + } else { + toast(R.string.unknown_error_occurred) + callback() + } + } + } else { + try { + if (file.createNewFile()) { + addNoMediaIntoMediaStore(file.absolutePath) + } else { + toast(R.string.unknown_error_occurred) + } + } catch (e: Exception) { + showErrorToast(e) + } + callback() + } +} + +fun BaseSimpleActivity.addNoMediaIntoMediaStore(path: String) { + try { + val content = ContentValues().apply { + put(Files.FileColumns.TITLE, NOMEDIA) + put(Files.FileColumns.DATA, path) + put(Files.FileColumns.MEDIA_TYPE, Files.FileColumns.MEDIA_TYPE_NONE) + } + contentResolver.insert(Files.getContentUri("external"), content) + } catch (e: Exception) { + showErrorToast(e) + } +} + +fun BaseSimpleActivity.removeNoMedia(path: String, callback: (() -> Unit)? = null) { + val file = File(path, NOMEDIA) + if (!getDoesFilePathExist(file.absolutePath)) { + callback?.invoke() + return + } + + tryDeleteFileDirItem(file.toFileDirItem(applicationContext), false, false) { + callback?.invoke() + deleteFromMediaStore(file.absolutePath) + rescanFolderMedia(path) + } +} + +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) + ensureBackgroundThread { + updateDBMediaPath(oldPath, newPath) + } + } +} + +fun BaseSimpleActivity.tryCopyMoveFilesTo(fileDirItems: ArrayList, 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, false) { + val destination = it + handleSAFDialog(source) { + if (it) { + copyMoveFilesTo(fileDirItems, source.trimEnd('/'), destination, 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) { + ensureBackgroundThread { + deleteDBPath(fileDirItem.path) + runOnUiThread { + callback?.invoke(it) + } + } + } else { + callback?.invoke(it) + } + } +} + +fun BaseSimpleActivity.movePathsInRecycleBin(paths: ArrayList, callback: ((wasSuccess: Boolean) -> Unit)?) { + ensureBackgroundThread { + var pathsCnt = paths.size + val OTGPath = config.OTGPath + + for (source in paths) { + if (OTGPath.isNotEmpty() && source.startsWith(OTGPath)) { + var inputStream: InputStream? = null + var out: OutputStream? = null + try { + val destination = "$recycleBinPath/$source" + val fileDocument = getSomeDocumentFile(source) + inputStream = applicationContext.contentResolver.openInputStream(fileDocument?.uri!!) + out = getFileOutputStreamSync(destination, source.getMimeType()) + + var copiedSize = 0L + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + var bytes = inputStream!!.read(buffer) + while (bytes >= 0) { + out!!.write(buffer, 0, bytes) + copiedSize += bytes + bytes = inputStream.read(buffer) + } + + out?.flush() + + if (fileDocument.getItemSize(true) == copiedSize && getDoesFilePathExist(destination)) { + mediaDB.updateDeleted("$RECYCLE_BIN$source", System.currentTimeMillis(), source) + pathsCnt-- + } + } catch (e: Exception) { + showErrorToast(e) + return@ensureBackgroundThread + } finally { + inputStream?.close() + out?.close() + } + } else { + val file = File(source) + val internalFile = File(recycleBinPath, source) + val lastModified = file.lastModified() + try { + if (file.copyRecursively(internalFile, true)) { + mediaDB.updateDeleted("$RECYCLE_BIN$source", System.currentTimeMillis(), source) + pathsCnt-- + + if (config.keepLastModified) { + internalFile.setLastModified(lastModified) + } + } + } catch (e: Exception) { + showErrorToast(e) + return@ensureBackgroundThread + } + } + } + callback?.invoke(pathsCnt == 0) + } +} + +fun BaseSimpleActivity.restoreRecycleBinPath(path: String, callback: () -> Unit) { + restoreRecycleBinPaths(arrayListOf(path), callback) +} + +fun BaseSimpleActivity.restoreRecycleBinPaths(paths: ArrayList, callback: () -> Unit) { + ensureBackgroundThread { + val newPaths = ArrayList() + for (source in paths) { + val destination = source.removePrefix(recycleBinPath) + val lastModified = File(source).lastModified() + + val isShowingSAF = handleSAFDialog(destination) {} + if (isShowingSAF) { + return@ensureBackgroundThread + } + + var inputStream: InputStream? = null + var out: OutputStream? = null + try { + out = getFileOutputStreamSync(destination, source.getMimeType()) + inputStream = getFileInputStreamSync(source) + + var copiedSize = 0L + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + var bytes = inputStream!!.read(buffer) + while (bytes >= 0) { + out!!.write(buffer, 0, bytes) + copiedSize += bytes + bytes = inputStream.read(buffer) + } + + out?.flush() + + if (File(source).length() == copiedSize) { + mediaDB.updateDeleted(destination.removePrefix(recycleBinPath), 0, "$RECYCLE_BIN$destination") + } + newPaths.add(destination) + + if (config.keepLastModified) { + File(destination).setLastModified(lastModified) + } + } catch (e: Exception) { + showErrorToast(e) + } finally { + inputStream?.close() + out?.close() + } + } + + runOnUiThread { + callback() + } + + rescanPaths(newPaths) { + fixDateTaken(newPaths, false) + } + } +} + +fun BaseSimpleActivity.emptyTheRecycleBin(callback: (() -> Unit)? = null) { + ensureBackgroundThread { + try { + recycleBin.deleteRecursively() + mediaDB.clearRecycleBin() + directoryDao.deleteRecycleBin() + toast(R.string.recycle_bin_emptied) + callback?.invoke() + } catch (e: Exception) { + toast(R.string.unknown_error_occurred) + } + } +} + +fun BaseSimpleActivity.emptyAndDisableTheRecycleBin(callback: () -> Unit) { + ensureBackgroundThread { + emptyTheRecycleBin { + config.useRecycleBin = false + callback() + } + } +} + +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, destination: String) { + ensureBackgroundThread { + fileDirItems.forEach { + val newPath = "$destination/${it.name}" + updateDBMediaPath(it.path, newPath) + } + } +} + +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, showToasts: Boolean, hasRescanned: Boolean = false, callback: (() -> Unit)? = null) { + val BATCH_SIZE = 50 + if (showToasts) { + toast(R.string.fixing) + } + + val pathsToRescan = ArrayList() + try { + var didUpdateFile = false + val operations = ArrayList() + + ensureBackgroundThread { + val dateTakens = ArrayList() + + 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 = "${Images.Media.DATA} = ?" + val selectionArgs = arrayOf(path) + withSelection(selection, selectionArgs) + withValue(Images.Media.DATE_TAKEN, timestamp) + operations.add(build()) + } + + if (operations.size % BATCH_SIZE == 0) { + contentResolver.applyBatch(MediaStore.AUTHORITY, operations) + operations.clear() + } + + mediaDB.updateFavoriteDateTaken(path, timestamp) + didUpdateFile = true + + val dateTaken = DateTaken(null, path, path.getFilenameFromPath(), path.getParentPath(), timestamp, (System.currentTimeMillis() / 1000).toInt(), File(path).lastModified()) + dateTakens.add(dateTaken) + if (!hasRescanned && getFileDateTaken(path) == 0L) { + pathsToRescan.add(path) + } + } + + if (!didUpdateFile) { + if (showToasts) { + toast(R.string.no_date_takens_found) + } + + runOnUiThread { + callback?.invoke() + } + return@ensureBackgroundThread + } + + val resultSize = contentResolver.applyBatch(MediaStore.AUTHORITY, operations).size + if (resultSize == 0) { + didUpdateFile = false + } + + if (hasRescanned || pathsToRescan.isEmpty()) { + if (dateTakens.isNotEmpty()) { + dateTakensDB.insertAll(dateTakens) + } + + runOnUiThread { + if (showToasts) { + toast(if (didUpdateFile) R.string.dates_fixed_successfully else R.string.unknown_error_occurred) + } + + callback?.invoke() + } + } else { + rescanPaths(pathsToRescan) { + fixDateTaken(paths, showToasts, true) + } + } + } + } catch (e: Exception) { + if (showToasts) { + 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) + } + + copyFile(tmpPath, newPath) + rescanPaths(arrayListOf(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(lastModified)) + // 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!!) + } catch (e: Exception) { + showErrorToast(e) + } 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) +} + +fun Activity.getShortcutImage(tmb: String, drawable: Drawable, callback: () -> Unit) { + ensureBackgroundThread { + val options = RequestOptions() + .format(DecodeFormat.PREFER_ARGB_8888) + .skipMemoryCache(true) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .fitCenter() + + val size = resources.getDimension(R.dimen.shortcut_size).toInt() + val builder = Glide.with(this) + .asDrawable() + .load(tmb) + .apply(options) + .centerCrop() + .into(size, size) + + try { + (drawable as LayerDrawable).setDrawableByLayerId(R.id.shortcut_image, builder.get()) + } catch (e: Exception) { + } + + runOnUiThread { + callback() + } + } +} + +@TargetApi(Build.VERSION_CODES.N) +fun Activity.showFileOnMap(path: String) { + val exif = try { + if (path.startsWith("content://") && isNougatPlus()) { + ExifInterface(contentResolver.openInputStream(Uri.parse(path))!!) + } else { + ExifInterface(path) + } + } catch (e: Exception) { + showErrorToast(e) + return + } + + val latLon = FloatArray(2) + if (exif.getLatLong(latLon)) { + showLocationOnMap("${latLon[0]}, ${latLon[1]}") + } else { + toast(R.string.unknown_location) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/ArrayList.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/ArrayList.kt new file mode 100644 index 000000000..551b47d61 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/ArrayList.kt @@ -0,0 +1,33 @@ +package com.simplemobiletools.gallery.pro.extensions + +import com.simplemobiletools.gallery.pro.helpers.* +import com.simplemobiletools.gallery.pro.models.Medium + +fun ArrayList.getDirMediaTypes(): Int { + var types = 0 + if (any { it.isImage() }) { + types += TYPE_IMAGES + } + + if (any { it.isVideo() }) { + types += TYPE_VIDEOS + } + + if (any { it.isGIF() }) { + types += TYPE_GIFS + } + + if (any { it.isRaw() }) { + types += TYPE_RAWS + } + + if (any { it.isSVG() }) { + types += TYPE_SVGS + } + + if (any { it.isPortrait() }) { + types += TYPE_PORTRAITS + } + + return types +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt new file mode 100644 index 000000000..f2d55eb95 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt @@ -0,0 +1,978 @@ +package com.simplemobiletools.gallery.pro.extensions + +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.graphics.drawable.PictureDrawable +import android.media.AudioManager +import android.provider.MediaStore.Files +import android.provider.MediaStore.Images +import android.widget.ImageView +import com.bumptech.glide.Glide +import com.bumptech.glide.Priority +import com.bumptech.glide.load.DecodeFormat +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.signature.ObjectKey +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.* +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.* +import com.simplemobiletools.gallery.pro.models.* +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 +import kotlin.collections.HashMap +import kotlin.collections.set + +val Context.audioManager get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager + +fun Context.getHumanizedFilename(path: String): String { + val humanized = humanizePath(path) + return humanized.substring(humanized.lastIndexOf("/") + 1) +} + +fun Context.launchSettings() { + startActivity(Intent(applicationContext, SettingsActivity::class.java)) +} + +val Context.config: Config get() = Config.newInstance(applicationContext) + +val Context.widgetsDB: WidgetsDao get() = GalleryDatabase.getInstance(applicationContext).WidgetsDao() + +val Context.mediaDB: MediumDao get() = GalleryDatabase.getInstance(applicationContext).MediumDao() + +val Context.directoryDao: DirectoryDao get() = GalleryDatabase.getInstance(applicationContext).DirectoryDao() + +val Context.favoritesDB: FavoritesDao get() = GalleryDatabase.getInstance(applicationContext).FavoritesDao() + +val Context.dateTakensDB: DateTakensDao get() = GalleryDatabase.getInstance(applicationContext).DateTakensDao() + +val Context.recycleBin: File get() = filesDir + +val Context.recycleBinPath: String get() = filesDir.absolutePath + +fun Context.movePinnedDirectoriesToFront(dirs: ArrayList): ArrayList { + val foundFolders = ArrayList() + val pinnedFolders = config.pinnedFolders + + dirs.forEach { + if (pinnedFolders.contains(it.path)) { + foundFolders.add(it) + } + } + + dirs.removeAll(foundFolders) + dirs.addAll(0, foundFolders) + if (config.tempFolderPath.isNotEmpty()) { + val newFolder = dirs.firstOrNull { it.path == config.tempFolderPath } + if (newFolder != null) { + dirs.remove(newFolder) + dirs.add(0, newFolder) + } + } + + if (config.showRecycleBinAtFolders && config.showRecycleBinLast) { + val binIndex = dirs.indexOfFirst { it.isRecycleBin() } + if (binIndex != -1) { + val bin = dirs.removeAt(binIndex) + dirs.add(bin) + } + } + return dirs +} + +@Suppress("UNCHECKED_CAST") +fun Context.getSortedDirectories(source: ArrayList): ArrayList { + val sorting = config.directorySorting + val dirs = source.clone() as ArrayList + + if (sorting and SORT_BY_RANDOM != 0) { + dirs.shuffle() + return movePinnedDirectoriesToFront(dirs) + } + + dirs.sortWith(Comparator { o1, o2 -> + o1 as Directory + o2 as Directory + + var result = when { + sorting and SORT_BY_NAME != 0 -> { + if (sorting and SORT_USE_NUMERIC_VALUE != 0) { + AlphanumericComparator().compare(o1.sortValue.toLowerCase(), o2.sortValue.toLowerCase()) + } else { + o1.sortValue.toLowerCase().compareTo(o2.sortValue.toLowerCase()) + } + } + sorting and SORT_BY_PATH != 0 -> { + if (sorting and SORT_USE_NUMERIC_VALUE != 0) { + AlphanumericComparator().compare(o1.sortValue.toLowerCase(), o2.sortValue.toLowerCase()) + } else { + o1.sortValue.toLowerCase().compareTo(o2.sortValue.toLowerCase()) + } + } + sorting and SORT_BY_PATH != 0 -> AlphanumericComparator().compare(o1.sortValue.toLowerCase(), o2.sortValue.toLowerCase()) + sorting and SORT_BY_SIZE != 0 -> (o1.sortValue.toLongOrNull() ?: 0).compareTo(o2.sortValue.toLongOrNull() ?: 0) + sorting and SORT_BY_DATE_MODIFIED != 0 -> (o1.sortValue.toLongOrNull() ?: 0).compareTo(o2.sortValue.toLongOrNull() ?: 0) + else -> (o1.sortValue.toLongOrNull() ?: 0).compareTo(o2.sortValue.toLongOrNull() ?: 0) + } + + if (sorting and SORT_DESCENDING != 0) { + result *= -1 + } + result + }) + + return movePinnedDirectoriesToFront(dirs) +} + +fun Context.getDirsToShow(dirs: ArrayList, allDirs: ArrayList, currentPathPrefix: String): ArrayList { + return if (config.groupDirectSubfolders) { + dirs.forEach { + it.subfoldersCount = 0 + it.subfoldersMediaCount = it.mediaCnt + } + + val parentDirs = getDirectParentSubfolders(dirs, currentPathPrefix) + 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) + } + } + + getSortedDirectories(parentDirs) + } else { + dirs.forEach { it.subfoldersMediaCount = it.mediaCnt } + dirs + } +} + +fun Context.getDirectParentSubfolders(dirs: ArrayList, currentPathPrefix: String): ArrayList { + val folders = dirs.map { it.path }.sorted().toMutableSet() as HashSet + val currentPaths = LinkedHashSet() + val foldersWithoutMediaFiles = ArrayList() + var newDirId = 1000L + + for (path in folders) { + if (path == RECYCLE_BIN || path == FAVORITES) { + continue + } + + if (currentPathPrefix.isNotEmpty()) { + if (!path.startsWith(currentPathPrefix, true)) { + continue + } + + if (!File(path).parent.equals(currentPathPrefix, true)) { + continue + } + } + + if (currentPathPrefix.isNotEmpty() && 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, still Pictures with the first folders thumbnails and proper other info + val parent = File(path).parent + if (parent != null && !folders.contains(parent) && dirs.none { it.path == parent }) { + currentPaths.add(parent) + val isSortingAscending = config.sorting.isSortingAscending() + val subDirs = dirs.filter { File(it.path).parent.equals(File(path).parent, true) } as ArrayList + if (subDirs.isNotEmpty()) { + val lastModified = if (isSortingAscending) { + subDirs.minBy { it.modified }?.modified + } else { + subDirs.maxBy { it.modified }?.modified + } ?: 0 + + val dateTaken = if (isSortingAscending) { + subDirs.minBy { it.taken }?.taken + } else { + subDirs.maxBy { it.taken }?.taken + } ?: 0 + + var mediaTypes = 0 + subDirs.forEach { + mediaTypes = mediaTypes or it.types + } + + val directory = Directory(newDirId++, + parent, + subDirs.first().tmb, + getFolderNameFromPath(parent), + subDirs.sumBy { it.mediaCnt }, + lastModified, + dateTaken, + subDirs.sumByLong { it.size }, + getPathLocation(parent), + mediaTypes, + "") + + directory.containsMediaFilesDirectly = false + dirs.add(directory) + currentPaths.add(parent) + foldersWithoutMediaFiles.add(parent) + } + } + } else { + currentPaths.add(path) + } + } + + var areDirectSubfoldersAvailable = false + currentPaths.forEach { + val path = it + currentPaths.forEach { + if (!foldersWithoutMediaFiles.contains(it) && !it.equals(path, true) && File(it).parent?.equals(path, true) == 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 dirs.filter { currentPaths.contains(it.path) } as ArrayList + } + + folders.clear() + folders.addAll(currentPaths) + + val dirsToShow = dirs.filter { folders.contains(it.path) } as ArrayList + return if (areDirectSubfoldersAvailable) { + getDirectParentSubfolders(dirsToShow, currentPathPrefix) + } else { + dirsToShow + } +} + +fun Context.updateSubfolderCounts(children: ArrayList, parentDirs: ArrayList) { + 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) }) { + if (child.containsMediaFilesDirectly) { + subfoldersCount++ + } + + if (path != child.path) { + subfoldersMediaCount += child.mediaCnt + } + } + } + } +} + +fun Context.getNoMediaFolders(callback: (folders: ArrayList) -> Unit) { + ensureBackgroundThread { + callback(getNoMediaFoldersSync()) + } +} + +fun Context.getNoMediaFoldersSync(): ArrayList { + val folders = ArrayList() + + val uri = Files.getContentUri("external") + val projection = arrayOf(Files.FileColumns.DATA) + val selection = "${Files.FileColumns.MEDIA_TYPE} = ? AND ${Files.FileColumns.TITLE} LIKE ?" + val selectionArgs = arrayOf(Files.FileColumns.MEDIA_TYPE_NONE.toString(), "%$NOMEDIA%") + val sortOrder = "${Files.FileColumns.DATE_MODIFIED} DESC" + val OTGPath = config.OTGPath + + var cursor: Cursor? = null + try { + cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder) + if (cursor?.moveToFirst() == true) { + do { + val path = cursor.getStringValue(Files.FileColumns.DATA) ?: continue + val noMediaFile = File(path) + if (getDoesFilePathExist(noMediaFile.absolutePath, OTGPath) && noMediaFile.name == NOMEDIA) { + folders.add(noMediaFile.parent) + } + } while (cursor.moveToNext()) + } + } catch (ignored: Exception) { + } finally { + cursor?.close() + } + + return folders +} + +fun Context.rescanFolderMedia(path: String) { + ensureBackgroundThread { + rescanFolderMediaSync(path) + } +} + +fun Context.rescanFolderMediaSync(path: String) { + getCachedMedia(path) { + val cached = it + GetMediaAsynctask(applicationContext, path, false, false, false) { + ensureBackgroundThread { + val newMedia = it + val media = newMedia.filter { it is Medium } as ArrayList + try { + mediaDB.insertAll(media) + + cached.forEach { + if (!newMedia.contains(it)) { + val mediumPath = (it as? Medium)?.path + if (mediumPath != null) { + deleteDBPath(mediumPath) + } + } + } + } catch (ignored: Exception) { + } + } + }.execute() + } +} + +fun Context.storeDirectoryItems(items: ArrayList) { + ensureBackgroundThread { + directoryDao.insertAll(items) + } +} + +fun Context.checkAppendingHidden(path: String, hidden: String, includedFolders: MutableSet, noMediaFolders: ArrayList): String { + val dirName = getFolderNameFromPath(path) + val folderNoMediaStatuses = java.util.HashMap() + noMediaFolders.forEach { folder -> + folderNoMediaStatuses["$folder/$NOMEDIA"] = true + } + + return if (path.doesThisOrParentHaveNoMedia(folderNoMediaStatuses, null) && !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) + otgPath -> getString(R.string.usb) + FAVORITES -> getString(R.string.favorites) + RECYCLE_BIN -> getString(R.string.recycle_bin) + else -> path.getFilenameFromPath() + } +} + +fun Context.loadImage(type: Int, path: String, target: MySquareImageView, horizontalScroll: Boolean, animateGifs: Boolean, cropThumbnails: Boolean, + roundCorners: Int, signature: ObjectKey, skipMemoryCacheAtPaths: ArrayList? = null) { + target.isHorizontalScrolling = horizontalScroll + if (type == TYPE_IMAGES || type == TYPE_VIDEOS || type == TYPE_RAWS || type == TYPE_PORTRAITS) { + if (type == TYPE_IMAGES && path.isPng()) { + loadPng(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) + } else { + loadJpg(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) + } + } else if (type == TYPE_GIFS) { + if (!animateGifs) { + loadStaticGIF(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) + return + } + + try { + val gifDrawable = GifDrawable(path) + target.setImageDrawable(gifDrawable) + gifDrawable.start() + + target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER + } catch (e: Exception) { + loadStaticGIF(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) + } catch (e: OutOfMemoryError) { + loadStaticGIF(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) + } + } else if (type == TYPE_SVGS) { + loadSVG(path, target, cropThumbnails, roundCorners, signature) + } +} + +fun Context.addTempFolderIfNeeded(dirs: ArrayList): ArrayList { + val tempFolderPath = config.tempFolderPath + return if (tempFolderPath.isNotEmpty()) { + val directories = ArrayList() + val newFolder = Directory(null, tempFolderPath, "", tempFolderPath.getFilenameFromPath(), 0, 0, 0, 0L, getPathLocation(tempFolderPath), 0, "") + directories.add(newFolder) + directories.addAll(dirs) + directories + } else { + dirs + } +} + +fun Context.getPathLocation(path: String): Int { + return when { + isPathOnSD(path) -> LOCATION_SD + isPathOnOTG(path) -> LOCATION_OTG + else -> LOCATION_INTERNAL + } +} + +fun Context.loadPng(path: String, target: MySquareImageView, cropThumbnails: Boolean, roundCorners: Int, signature: ObjectKey, skipMemoryCacheAtPaths: ArrayList? = null) { + val options = RequestOptions() + .signature(signature) + .skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .priority(Priority.LOW) + .format(DecodeFormat.PREFER_ARGB_8888) + + if (cropThumbnails) options.centerCrop() else options.fitCenter() + var builder = Glide.with(applicationContext) + .asBitmap() + .load(path) + .apply(options) + + if (roundCorners != ROUNDED_CORNERS_NONE) { + val cornerSize = if (roundCorners == ROUNDED_CORNERS_SMALL) R.dimen.rounded_corner_radius_small else R.dimen.rounded_corner_radius_big + val cornerRadius = resources.getDimension(cornerSize).toInt() + builder = builder.transform(CenterCrop(), RoundedCorners(cornerRadius)) + } + + builder.into(target) +} + +fun Context.loadJpg(path: String, target: MySquareImageView, cropThumbnails: Boolean, roundCorners: Int, signature: ObjectKey, skipMemoryCacheAtPaths: ArrayList? = null) { + val options = RequestOptions() + .signature(signature) + .skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true) + .priority(Priority.LOW) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + + if (cropThumbnails) options.centerCrop() else options.fitCenter() + var builder = Glide.with(applicationContext) + .load(path) + .apply(options) + .transition(DrawableTransitionOptions.withCrossFade()) + + if (roundCorners != ROUNDED_CORNERS_NONE) { + val cornerSize = if (roundCorners == ROUNDED_CORNERS_SMALL) R.dimen.rounded_corner_radius_small else R.dimen.rounded_corner_radius_big + val cornerRadius = resources.getDimension(cornerSize).toInt() + builder = builder.transform(CenterCrop(), RoundedCorners(cornerRadius)) + } + + builder.into(target) +} + +fun Context.loadStaticGIF(path: String, target: MySquareImageView, cropThumbnails: Boolean, roundCorners: Int, signature: ObjectKey, skipMemoryCacheAtPaths: ArrayList? = null) { + val options = RequestOptions() + .signature(signature) + .skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true) + .priority(Priority.LOW) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + + if (cropThumbnails) options.centerCrop() else options.fitCenter() + var builder = Glide.with(applicationContext) + .asBitmap() // make sure the GIF wont animate + .load(path) + .apply(options) + + if (roundCorners != ROUNDED_CORNERS_NONE) { + val cornerSize = if (roundCorners == ROUNDED_CORNERS_SMALL) R.dimen.rounded_corner_radius_small else R.dimen.rounded_corner_radius_big + val cornerRadius = resources.getDimension(cornerSize).toInt() + builder = builder.transform(CenterCrop(), RoundedCorners(cornerRadius)) + } + + builder.into(target) +} + +fun Context.loadSVG(path: String, target: MySquareImageView, cropThumbnails: Boolean, roundCorners: Int, signature: ObjectKey) { + target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER + + val options = RequestOptions().signature(signature) + var builder = Glide.with(applicationContext) + .`as`(PictureDrawable::class.java) + .listener(SvgSoftwareLayerSetter()) + .load(path) + .apply(options) + .transition(DrawableTransitionOptions.withCrossFade()) + + if (roundCorners != ROUNDED_CORNERS_NONE) { + val cornerSize = if (roundCorners == ROUNDED_CORNERS_SMALL) R.dimen.rounded_corner_radius_small else R.dimen.rounded_corner_radius_big + val cornerRadius = resources.getDimension(cornerSize).toInt() + builder = builder.transform(CenterCrop(), RoundedCorners(cornerRadius)) + } + + builder.into(target) +} + +fun Context.getCachedDirectories(getVideosOnly: Boolean = false, getImagesOnly: Boolean = false, forceShowHidden: Boolean = false, callback: (ArrayList) -> Unit) { + ensureBackgroundThread { + val directories = try { + directoryDao.getAll() as ArrayList + } catch (e: Exception) { + ArrayList() + } + + if (!config.showRecycleBinAtFolders) { + directories.removeAll { it.isRecycleBin() } + } + + val shouldShowHidden = config.shouldShowHidden || forceShowHidden + val excludedPaths = config.excludedFolders + val includedPaths = config.includedFolders + + val folderNoMediaStatuses = HashMap() + val noMediaFolders = getNoMediaFoldersSync() + noMediaFolders.forEach { folder -> + folderNoMediaStatuses["$folder/$NOMEDIA"] = true + } + + var filteredDirectories = directories.filter { + it.path.shouldFolderBeVisible(excludedPaths, includedPaths, shouldShowHidden, folderNoMediaStatuses) { path, hasNoMedia -> + folderNoMediaStatuses[path] = hasNoMedia + } + } as ArrayList + val filterMedia = config.filterMedia + + filteredDirectories = (when { + getVideosOnly -> filteredDirectories.filter { it.types and TYPE_VIDEOS != 0 } + getImagesOnly -> filteredDirectories.filter { it.types and TYPE_IMAGES != 0 } + else -> filteredDirectories.filter { + (filterMedia and TYPE_IMAGES != 0 && it.types and TYPE_IMAGES != 0) || + (filterMedia and TYPE_VIDEOS != 0 && it.types and TYPE_VIDEOS != 0) || + (filterMedia and TYPE_GIFS != 0 && it.types and TYPE_GIFS != 0) || + (filterMedia and TYPE_RAWS != 0 && it.types and TYPE_RAWS != 0) || + (filterMedia and TYPE_SVGS != 0 && it.types and TYPE_SVGS != 0) || + (filterMedia and TYPE_PORTRAITS != 0 && it.types and TYPE_PORTRAITS != 0) + } + }) as ArrayList + + if (shouldShowHidden) { + val hiddenString = resources.getString(R.string.hidden) + filteredDirectories.forEach { + val noMediaPath = "${it.path}/$NOMEDIA" + val hasNoMedia = if (folderNoMediaStatuses.keys.contains(noMediaPath)) { + folderNoMediaStatuses[noMediaPath]!! + } else { + it.path.doesThisOrParentHaveNoMedia(folderNoMediaStatuses) { path, hasNoMedia -> + val newPath = "$path/$NOMEDIA" + folderNoMediaStatuses[newPath] = hasNoMedia + } + } + + it.name = if (hasNoMedia && !it.path.isThisOrParentIncluded(includedPaths)) { + "${it.name.removeSuffix(hiddenString).trim()} $hiddenString" + } else { + it.name.removeSuffix(hiddenString).trim() + } + } + } + + val clone = filteredDirectories.clone() as ArrayList + callback(clone.distinctBy { it.path.getDistinctPath() } as ArrayList) + removeInvalidDBDirectories(filteredDirectories) + } +} + +fun Context.getCachedMedia(path: String, getVideosOnly: Boolean = false, getImagesOnly: Boolean = false, callback: (ArrayList) -> Unit) { + ensureBackgroundThread { + val mediaFetcher = MediaFetcher(this) + val foldersToScan = if (path.isEmpty()) mediaFetcher.getFoldersToScan() else arrayListOf(path) + var media = ArrayList() + if (path == FAVORITES) { + media.addAll(mediaDB.getFavorites()) + } + + if (path == RECYCLE_BIN) { + media.addAll(getUpdatedDeletedMedia()) + } + + if (config.filterMedia and TYPE_PORTRAITS != 0) { + val foldersToAdd = ArrayList() + for (folder in foldersToScan) { + val allFiles = File(folder).listFiles() ?: continue + allFiles.filter { it.name.startsWith("img_", true) && it.isDirectory }.forEach { + foldersToAdd.add(it.absolutePath) + } + } + foldersToScan.addAll(foldersToAdd) + } + + val shouldShowHidden = config.shouldShowHidden + foldersToScan.filter { path.isNotEmpty() || !config.isFolderProtected(it) }.forEach { + try { + val currMedia = mediaDB.getMediaFromPath(it) + media.addAll(currMedia) + } catch (ignored: Exception) { + } + } + + if (!shouldShowHidden) { + media = media.filter { !it.path.contains("/.") } as ArrayList + } + + val filterMedia = config.filterMedia + media = (when { + getVideosOnly -> media.filter { it.type == TYPE_VIDEOS } + getImagesOnly -> media.filter { it.type == TYPE_IMAGES } + else -> media.filter { + (filterMedia and TYPE_IMAGES != 0 && it.type == TYPE_IMAGES) || + (filterMedia and TYPE_VIDEOS != 0 && it.type == TYPE_VIDEOS) || + (filterMedia and TYPE_GIFS != 0 && it.type == TYPE_GIFS) || + (filterMedia and TYPE_RAWS != 0 && it.type == TYPE_RAWS) || + (filterMedia and TYPE_SVGS != 0 && it.type == TYPE_SVGS) || + (filterMedia and TYPE_PORTRAITS != 0 && it.type == TYPE_PORTRAITS) + } + }) as ArrayList + + val pathToUse = if (path.isEmpty()) SHOW_ALL else path + mediaFetcher.sortMedia(media, config.getFolderSorting(pathToUse)) + val grouped = mediaFetcher.groupMedia(media, pathToUse) + callback(grouped.clone() as ArrayList) + val OTGPath = config.OTGPath + + try { + val mediaToDelete = ArrayList() + // creating a new thread intentionally, do not reuse the common background thread + Thread { + media.filter { !getDoesFilePathExist(it.path, OTGPath) }.forEach { + if (it.path.startsWith(recycleBinPath)) { + deleteDBPath(it.path) + } else { + mediaToDelete.add(it) + } + } + + if (mediaToDelete.isNotEmpty()) { + mediaDB.deleteMedia(*mediaToDelete.toTypedArray()) + + mediaToDelete.filter { it.isFavorite }.forEach { + favoritesDB.deleteFavoritePath(it.path) + } + } + }.start() + } catch (ignored: Exception) { + } + } +} + +fun Context.removeInvalidDBDirectories(dirs: ArrayList? = null) { + val dirsToCheck = dirs ?: directoryDao.getAll() + val OTGPath = config.OTGPath + dirsToCheck.filter { !it.areFavorites() && !it.isRecycleBin() && !getDoesFilePathExist(it.path, OTGPath) && it.path != config.tempFolderPath }.forEach { + try { + directoryDao.deleteDirPath(it.path) + } catch (ignored: Exception) { + } + } +} + +fun Context.updateDBMediaPath(oldPath: String, newPath: String) { + val newFilename = newPath.getFilenameFromPath() + val newParentPath = newPath.getParentPath() + try { + mediaDB.updateMedium(newFilename, newPath, newParentPath, oldPath) + favoritesDB.updateFavorite(newFilename, newPath, newParentPath, oldPath) + } catch (ignored: Exception) { + } +} + +fun Context.updateDBDirectory(directory: Directory) { + try { + directoryDao.updateDirectory(directory.path, directory.tmb, directory.mediaCnt, directory.modified, directory.taken, directory.size, directory.types, directory.sortValue) + } catch (ignored: Exception) { + } +} + +fun Context.getOTGFolderChildren(path: String) = getDocumentFile(path)?.listFiles() + +fun Context.getOTGFolderChildrenNames(path: String) = getOTGFolderChildren(path)?.map { it.name }?.toMutableList() + +fun Context.getFavoritePaths(): ArrayList { + return try { + favoritesDB.getValidFavoritePaths() as ArrayList + } catch (e: Exception) { + ArrayList() + } +} + +fun Context.getFavoriteFromPath(path: String) = Favorite(null, path, path.getFilenameFromPath(), path.getParentPath()) + +fun Context.updateFavorite(path: String, isFavorite: Boolean) { + if (isFavorite) { + favoritesDB.insert(getFavoriteFromPath(path)) + } else { + favoritesDB.deleteFavoritePath(path) + } +} + +// remove the "recycle_bin" from the file path prefix, replace it with real bin path /data/user... +fun Context.getUpdatedDeletedMedia(): ArrayList { + val media = try { + mediaDB.getDeletedMedia() as ArrayList + } catch (ignored: Exception) { + ArrayList() + } + + media.forEach { + it.path = File(recycleBinPath, it.path.removePrefix(RECYCLE_BIN)).toString() + } + return media +} + +fun Context.deleteDBPath(path: String) { + deleteMediumWithPath(path.replaceFirst(recycleBinPath, RECYCLE_BIN)) +} + +fun Context.deleteMediumWithPath(path: String) { + try { + mediaDB.deleteMediumPath(path) + } catch (ignored: Exception) { + } +} + +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 (sb.length < size) { + 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) { + } +} + +fun Context.addPathToDB(path: String) { + ensureBackgroundThread { + if (!getDoesFilePathExist(path)) { + return@ensureBackgroundThread + } + + val type = when { + path.isVideoFast() -> TYPE_VIDEOS + path.isGif() -> TYPE_GIFS + path.isRawFast() -> TYPE_RAWS + path.isSvg() -> TYPE_SVGS + path.isPortrait() -> TYPE_PORTRAITS + else -> TYPE_IMAGES + } + + try { + val isFavorite = favoritesDB.isFavorite(path) + val videoDuration = if (type == TYPE_VIDEOS) getDuration(path) ?: 0 else 0 + val medium = Medium(null, path.getFilenameFromPath(), path, path.getParentPath(), System.currentTimeMillis(), System.currentTimeMillis(), + File(path).length(), type, videoDuration, isFavorite, 0L) + + mediaDB.insert(medium) + } catch (ignored: Exception) { + } + } +} + +fun Context.createDirectoryFromMedia(path: String, curMedia: ArrayList, albumCovers: ArrayList, hiddenString: String, + includedFolders: MutableSet, getProperFileSize: Boolean, noMediaFolders: ArrayList): Directory { + val OTGPath = config.OTGPath + val grouped = MediaFetcher(this).groupMedia(curMedia, path) + var thumbnail: String? = null + + albumCovers.forEach { + if (it.path == path && getDoesFilePathExist(it.tmb, OTGPath)) { + thumbnail = it.tmb + } + } + + if (thumbnail == null) { + val sortedMedia = grouped.filter { it is Medium }.toMutableList() as ArrayList + thumbnail = sortedMedia.firstOrNull { getDoesFilePathExist(it.path, OTGPath) }?.path ?: "" + } + + if (config.OTGPath.isNotEmpty() && thumbnail!!.startsWith(config.OTGPath)) { + thumbnail = thumbnail!!.getOTGPublicPath(applicationContext) + } + + val isSortingAscending = config.directorySorting.isSortingAscending() + val defaultMedium = Medium(0, "", "", "", 0L, 0L, 0L, 0, 0, false, 0L) + val firstItem = curMedia.firstOrNull() ?: defaultMedium + val lastItem = curMedia.lastOrNull() ?: defaultMedium + val dirName = checkAppendingHidden(path, hiddenString, includedFolders, noMediaFolders) + 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 = if (getProperFileSize) curMedia.sumByLong { it.size } else 0L + val mediaTypes = curMedia.getDirMediaTypes() + val sortValue = getDirectorySortingValue(curMedia, path, dirName, size) + return Directory(null, path, thumbnail!!, dirName, curMedia.size, lastModified, dateTaken, size, getPathLocation(path), mediaTypes, sortValue) +} + +fun Context.getDirectorySortingValue(media: ArrayList, path: String, name: String, size: Long): String { + val sorting = config.directorySorting + val sorted = when { + sorting and SORT_BY_NAME != 0 -> return name + sorting and SORT_BY_PATH != 0 -> return path + sorting and SORT_BY_SIZE != 0 -> return size.toString() + sorting and SORT_BY_DATE_MODIFIED != 0 -> media.sortedBy { it.modified } + sorting and SORT_BY_DATE_TAKEN != 0 -> media.sortedBy { it.taken } + else -> media + } + + val relevantMedium = if (sorting.isSortingAscending()) { + sorted.firstOrNull() ?: return "" + } else { + sorted.lastOrNull() ?: return "" + } + + val result: Any = when { + sorting and SORT_BY_DATE_MODIFIED != 0 -> relevantMedium.modified + sorting and SORT_BY_DATE_TAKEN != 0 -> relevantMedium.taken + else -> 0 + } + + return result.toString() +} + +fun Context.updateDirectoryPath(path: String) { + val mediaFetcher = MediaFetcher(applicationContext) + val getImagesOnly = false + val getVideosOnly = false + val hiddenString = getString(R.string.hidden) + val albumCovers = config.parseAlbumCovers() + val includedFolders = config.includedFolders + val noMediaFolders = getNoMediaFoldersSync() + + val sorting = config.getFolderSorting(path) + val grouping = config.getFolderGrouping(path) + val getProperDateTaken = config.directorySorting and SORT_BY_DATE_TAKEN != 0 || + sorting and SORT_BY_DATE_TAKEN != 0 || + grouping and GROUP_BY_DATE_TAKEN_DAILY != 0 || + grouping and GROUP_BY_DATE_TAKEN_MONTHLY != 0 + + val getProperLastModified = config.directorySorting and SORT_BY_DATE_MODIFIED != 0 || + sorting and SORT_BY_DATE_MODIFIED != 0 || + grouping and GROUP_BY_LAST_MODIFIED_DAILY != 0 || + grouping and GROUP_BY_LAST_MODIFIED_MONTHLY != 0 + + val getProperFileSize = config.directorySorting and SORT_BY_SIZE != 0 + + val lastModifieds = if (getProperLastModified) mediaFetcher.getFolderLastModifieds(path) else HashMap() + val dateTakens = mediaFetcher.getFolderDateTakens(path) + val favoritePaths = getFavoritePaths() + val curMedia = mediaFetcher.getFilesFrom(path, getImagesOnly, getVideosOnly, getProperDateTaken, getProperLastModified, getProperFileSize, + favoritePaths, false, lastModifieds, dateTakens) + val directory = createDirectoryFromMedia(path, curMedia, albumCovers, hiddenString, includedFolders, getProperFileSize, noMediaFolders) + updateDBDirectory(directory) +} + +fun Context.getFileDateTaken(path: String): Long { + val projection = arrayOf( + Images.Media.DATE_TAKEN + ) + + val uri = Files.getContentUri("external") + val selection = "${Images.Media.DATA} = ?" + val selectionArgs = arrayOf(path) + + try { + val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) + cursor?.use { + if (cursor.moveToFirst()) { + return cursor.getLongValue(Images.Media.DATE_TAKEN) + } + } + } catch (ignored: Exception) { + } + + return 0L +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/ExifInterface.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/ExifInterface.kt new file mode 100644 index 000000000..26d923fc0 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/ExifInterface.kt @@ -0,0 +1,57 @@ +package com.simplemobiletools.gallery.pro.extensions + +import androidx.exifinterface.media.ExifInterface +import java.lang.reflect.Field +import java.lang.reflect.Modifier + +fun ExifInterface.copyNonDimensionAttributesTo(destination: ExifInterface) { + val attributes = ExifInterfaceAttributes.AllNonDimensionAttributes + + attributes.forEach { + val value = getAttribute(it) + if (value != null) { + destination.setAttribute(it, value) + } + } + + try { + destination.saveAttributes() + } catch (ignored: Exception) { + } +} + +private class ExifInterfaceAttributes { + companion object { + val AllNonDimensionAttributes = getAllNonDimensionExifAttributes() + + private fun getAllNonDimensionExifAttributes(): List { + val tagFields = ExifInterface::class.java.fields.filter { field -> isExif(field) } + + val excludeAttributes = arrayListOf( + ExifInterface.TAG_IMAGE_LENGTH, + ExifInterface.TAG_IMAGE_WIDTH, + ExifInterface.TAG_PIXEL_X_DIMENSION, + ExifInterface.TAG_PIXEL_Y_DIMENSION, + ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH, + ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH, + ExifInterface.TAG_ORIENTATION) + + return tagFields + .map { tagField -> tagField.get(null) as String } + .filter { x -> !excludeAttributes.contains(x) } + .distinct() + } + + private fun isExif(field: Field): Boolean { + return field.type == String::class.java && + isPublicStaticFinal(field.modifiers) && + field.name.startsWith("TAG_") + } + + private const val publicStaticFinal = Modifier.PUBLIC or Modifier.STATIC or Modifier.FINAL + + private fun isPublicStaticFinal(modifiers: Int): Boolean { + return modifiers and publicStaticFinal > 0 + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/FileDirItem.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/FileDirItem.kt new file mode 100644 index 000000000..c67eebabb --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/FileDirItem.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.gallery.pro.extensions + +import com.simplemobiletools.commons.models.FileDirItem + +fun FileDirItem.isDownloadsFolder() = path.isDownloadsFolder() diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Int.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Int.kt new file mode 100644 index 000000000..7792ef112 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Int.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.gallery.pro.extensions + +import com.simplemobiletools.commons.helpers.SORT_DESCENDING + +fun Int.isSortingAscending() = this and SORT_DESCENDING == 0 diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/resources.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Resources.kt similarity index 69% rename from app/src/main/kotlin/com/simplemobiletools/gallery/extensions/resources.kt rename to app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Resources.kt index a2a2ef7af..d086fb7a5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/resources.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Resources.kt @@ -1,4 +1,4 @@ -package com.simplemobiletools.gallery.extensions +package com.simplemobiletools.gallery.pro.extensions import android.content.Context import android.content.res.Resources @@ -6,11 +6,10 @@ import android.util.TypedValue fun Resources.getActionBarHeight(context: Context): Int { val tv = TypedValue() - var height = 0 - if (context.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) { - height = TypedValue.complexToDimensionPixelSize(tv.data, displayMetrics) - } - return height + return if (context.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) { + TypedValue.complexToDimensionPixelSize(tv.data, displayMetrics) + } else + 0 } fun Resources.getStatusBarHeight(): Int { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/String.kt new file mode 100644 index 000000000..2a4f646c6 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/String.kt @@ -0,0 +1,87 @@ +package com.simplemobiletools.gallery.pro.extensions + +import android.os.Environment +import com.simplemobiletools.commons.helpers.NOMEDIA +import java.io.File +import java.io.IOException + +fun String.isThisOrParentIncluded(includedPaths: MutableSet) = includedPaths.any { equals(it, true) } || includedPaths.any { "$this/".startsWith("$it/", true) } + +fun String.isThisOrParentExcluded(excludedPaths: MutableSet) = excludedPaths.any { equals(it, true) } || excludedPaths.any { "$this/".startsWith("$it/", true) } + +// cache which folders contain .nomedia files to avoid checking them over and over again +fun String.shouldFolderBeVisible(excludedPaths: MutableSet, includedPaths: MutableSet, showHidden: Boolean, + folderNoMediaStatuses: HashMap, callback: (path: String, hasNoMedia: Boolean) -> Unit): Boolean { + if (isEmpty()) { + return false + } + + val file = File(this) + val filename = file.name + if (filename.startsWith("img_", true) && file.isDirectory) { + val files = file.list() + if (files != null) { + if (files.any { it.contains("burst", true) }) { + return false + } + } + } + + if (!showHidden && filename.startsWith('.')) { + return false + } else if (includedPaths.contains(this)) { + return true + } + + val containsNoMedia = if (showHidden) { + false + } else { + folderNoMediaStatuses.getOrElse("$this/$NOMEDIA", { false }) || File(this, NOMEDIA).exists() + } + + return if (!showHidden && containsNoMedia) { + false + } else if (excludedPaths.contains(this)) { + false + } else if (isThisOrParentIncluded(includedPaths)) { + true + } else if (isThisOrParentExcluded(excludedPaths)) { + false + } else if (!showHidden) { + var containsNoMediaOrDot = containsNoMedia || contains("/.") + if (!containsNoMediaOrDot) { + var curPath = this + for (i in 0 until count { it == '/' } - 1) { + curPath = curPath.substringBeforeLast('/') + val pathToCheck = "$curPath/$NOMEDIA" + if (folderNoMediaStatuses.contains(pathToCheck)) { + if (folderNoMediaStatuses[pathToCheck] == true) { + containsNoMediaOrDot = true + break + } + } else { + val noMediaExists = folderNoMediaStatuses.getOrElse(pathToCheck, { false }) || File(pathToCheck).exists() + callback(pathToCheck, noMediaExists) + if (noMediaExists) { + containsNoMediaOrDot = true + break + } + } + } + } + !containsNoMediaOrDot + } else { + true + } +} + +// recognize /sdcard/DCIM as the same folder as /storage/emulated/0/DCIM +fun String.getDistinctPath(): String { + return try { + File(this).canonicalPath.toLowerCase() + } catch (e: IOException) { + toLowerCase() + } +} + +fun String.isDownloadsFolder() = equals(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString(), true) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/View.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/View.kt new file mode 100644 index 000000000..24107d2a2 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/View.kt @@ -0,0 +1,13 @@ +package com.simplemobiletools.gallery.pro.extensions + +import android.os.SystemClock +import android.view.MotionEvent +import android.view.View + +fun View.sendFakeClick(x: Float, y: Float) { + val uptime = SystemClock.uptimeMillis() + val event = MotionEvent.obtain(uptime, uptime, MotionEvent.ACTION_DOWN, x, y, 0) + dispatchTouchEvent(event) + event.action = MotionEvent.ACTION_UP + dispatchTouchEvent(event) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/PhotoFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/PhotoFragment.kt new file mode 100644 index 000000000..4e22c5b14 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/PhotoFragment.kt @@ -0,0 +1,834 @@ +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.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.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.RelativeLayout +import androidx.exifinterface.media.ExifInterface.* +import com.alexvasilkov.gestures.GestureController +import com.alexvasilkov.gestures.State +import com.bumptech.glide.Glide +import com.bumptech.glide.Priority +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.load.resource.bitmap.Rotate +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.github.penfeizhou.animation.webp.WebPDrawable +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +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.adapters.PortraitPhotosAdapter +import com.simplemobiletools.gallery.pro.extensions.config +import com.simplemobiletools.gallery.pro.extensions.saveRotatedImageToFile +import com.simplemobiletools.gallery.pro.extensions.sendFakeClick +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.* +import kotlin.math.ceil + +class PhotoFragment : ViewPagerFragment() { + private val DEFAULT_DOUBLE_TAP_ZOOM = 2f + private val ZOOMABLE_VIEW_LOAD_DELAY = 100L + 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 mCurrentPortraitPhotoPath = "" + private var mOriginalPath = "" + 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) + if (!arguments!!.getBoolean(SHOULD_INIT_FRAGMENT, true)) { + return mView + } + + mMedium = arguments!!.getSerializable(MEDIUM) as Medium + mOriginalPath = mMedium.path + + mView.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, singleTap = { 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 + } + + if (mMedium.path.startsWith("content://") && !mMedium.path.startsWith("content://mms/")) { + mMedium.path = context!!.getRealPathFromURI(Uri.parse(mOriginalPath)) ?: mMedium.path + + if (mMedium.path.isEmpty()) { + var out: FileOutputStream? = null + try { + var inputStream = context!!.contentResolver.openInputStream(Uri.parse(mOriginalPath)) + 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(mOriginalPath)) + 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(mOriginalPath).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() + + try { + if (context != null) { + Glide.with(context!!).clear(mView.gestures_view) + } + } catch (ignored: Exception) { + } + } + + mLoadZoomableViewHandler.removeCallbacksAndMessages(null) + if (mCurrentRotationDegrees != 0) { + ensureBackgroundThread { + val path = mMedium.path + (activity as? BaseSimpleActivity)?.saveRotatedImageToFile(path, path, mCurrentRotationDegrees, false) {} + } + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + if (!mWasInit) { + return + } + + // avoid GIFs being skewed, played in wrong aspect ratio + if (mMedium.isGIF()) { + mView.onGlobalLayout { + if (activity != null) { + measureScreen() + Handler().postDelayed({ + mView.gif_view_frame.controller.resetState() + loadGif() + }, 50) + } + } + } else { + hideZoomableView() + loadImage() + } + + measureScreen() + initExtendedDetails() + updateInstantSwitchWidths() + } + + override fun setMenuVisibility(menuVisible: Boolean) { + super.setMenuVisibility(menuVisible) + mIsFragmentVisible = menuVisible + if (mWasInit) { + if (!mMedium.isGIF() && !mMedium.isWebP()) { + 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() + + if (mMedium.isPortrait() && context != null) { + showPortraitStripe() + } + + 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) { + if (context == null) { + return + } + + val path = getFilePathToShow() + if (path.isWebP()) { + val drawable = WebPDrawable.fromFile(path) + if (drawable.intrinsicWidth == 0) { + loadWithGlide(path, addZoomableView) + } else { + drawable.setLoopLimit(0) + mView.gestures_view.setImageDrawable(drawable) + } + } else { + loadWithGlide(path, addZoomableView) + } + } + + private fun loadWithGlide(path: String, addZoomableView: Boolean) { + val priority = if (mIsFragmentVisible) Priority.IMMEDIATE else Priority.NORMAL + val options = RequestOptions() + .signature(mMedium.getKey()) + .format(DecodeFormat.PREFER_ARGB_8888) + .priority(priority) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .fitCenter() + + if (mCurrentRotationDegrees != 0) { + options.transform(Rotate(mCurrentRotationDegrees)) + options.diskCacheStrategy(DiskCacheStrategy.NONE) + } + + Glide.with(context!!) + .load(path) + .apply(options) + .listener(object : RequestListener { + override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { + if (activity != null && !activity!!.isDestroyed && !activity!!.isFinishing) { + tryLoadingWithPicasso(addZoomableView) + } + return false + } + + override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { + mView.gestures_view.controller.settings.isZoomEnabled = mMedium.isRaw() || mCurrentRotationDegrees != 0 || context?.config?.allowZoomingImages == false + if (mIsFragmentVisible && addZoomableView) { + scheduleZoomableView() + } + return false + } + }).into(mView.gestures_view) + } + + private fun tryLoadingWithPicasso(addZoomableView: Boolean) { + var pathToLoad = if (getFilePathToShow().startsWith("content://")) getFilePathToShow() else "file://${getFilePathToShow()}" + pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23") + + try { + val picasso = Picasso.get() + .load(pathToLoad) + .centerInside() + .stableKey(mMedium.getSignature()) + .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 = mMedium.isRaw() || mCurrentRotationDegrees != 0 || context?.config?.allowZoomingImages == false + if (mIsFragmentVisible && addZoomableView) { + scheduleZoomableView() + } + } + + override fun onError(e: Exception?) { + if (mMedium.path != mOriginalPath) { + mMedium.path = mOriginalPath + loadImage() + checkIfPanorama() + } + } + }) + } catch (ignored: Exception) { + } + } + + private fun showPortraitStripe() { + val files = File(mMedium.parentPath).listFiles()?.toMutableList() as? ArrayList + if (files != null) { + val screenWidth = context!!.realScreenSize.x + val itemWidth = context!!.resources.getDimension(R.dimen.portrait_photos_stripe_height).toInt() + context!!.resources.getDimension(R.dimen.one_dp).toInt() + val sideWidth = screenWidth / 2 - itemWidth / 2 + val fakeItemsCnt = ceil(sideWidth / itemWidth.toDouble()).toInt() + + val paths = fillPhotoPaths(files, fakeItemsCnt) + var curWidth = itemWidth + while (curWidth < screenWidth) { + curWidth += itemWidth + } + + val sideElementWidth = curWidth - screenWidth + val adapter = PortraitPhotosAdapter(context!!, paths, sideElementWidth) { position, x -> + if (mIsFullscreen) { + return@PortraitPhotosAdapter + } + + mView.photo_portrait_stripe.smoothScrollBy((x + itemWidth / 2) - screenWidth / 2, 0) + if (paths[position] != mCurrentPortraitPhotoPath) { + mCurrentPortraitPhotoPath = paths[position] + hideZoomableView() + loadBitmap() + } + } + + mView.photo_portrait_stripe.adapter = adapter + setupStripeBottomMargin() + + val coverIndex = getCoverImageIndex(paths) + if (coverIndex != -1) { + mCurrentPortraitPhotoPath = paths[coverIndex] + setupStripeUpListener(adapter, screenWidth, itemWidth) + + mView.photo_portrait_stripe.onGlobalLayout { + mView.photo_portrait_stripe.scrollBy((coverIndex - fakeItemsCnt) * itemWidth, 0) + adapter.setCurrentPhoto(coverIndex) + mView.photo_portrait_stripe_wrapper.beVisible() + if (mIsFullscreen) { + mView.photo_portrait_stripe_wrapper.alpha = 0f + } + } + } + } + } + + private fun fillPhotoPaths(files: ArrayList, fakeItemsCnt: Int): ArrayList { + val paths = ArrayList() + for (i in 0 until fakeItemsCnt) { + paths.add("") + } + + files.forEach { + paths.add(it.absolutePath) + } + + for (i in 0 until fakeItemsCnt) { + paths.add("") + } + return paths + } + + private fun setupStripeBottomMargin() { + var bottomMargin = context!!.navigationBarHeight + context!!.resources.getDimension(R.dimen.normal_margin).toInt() + if (context!!.config.bottomActions) { + bottomMargin += context!!.resources.getDimension(R.dimen.bottom_actions_height).toInt() + } + (mView.photo_portrait_stripe_wrapper.layoutParams as RelativeLayout.LayoutParams).bottomMargin = bottomMargin + } + + private fun getCoverImageIndex(paths: ArrayList): Int { + var coverIndex = -1 + paths.forEachIndexed { index, path -> + if (path.contains("cover", true)) { + coverIndex = index + } + } + + if (coverIndex == -1) { + paths.forEachIndexed { index, path -> + if (path.isNotEmpty()) { + coverIndex = index + } + } + } + return coverIndex + } + + private fun setupStripeUpListener(adapter: PortraitPhotosAdapter, screenWidth: Int, itemWidth: Int) { + mView.photo_portrait_stripe.setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) { + var closestIndex = -1 + var closestDistance = Integer.MAX_VALUE + val center = screenWidth / 2 + for ((key, value) in adapter.views) { + val distance = Math.abs(value.x.toInt() + itemWidth / 2 - center) + if (distance < closestDistance) { + closestDistance = distance + closestIndex = key + } + } + + Handler().postDelayed({ + adapter.performClickOn(closestIndex) + }, 100) + } + false + } + } + + private fun getFilePathToShow() = if (mMedium.isPortrait()) mCurrentPortraitPhotoPath else getPathToLoad(mMedium) + + 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() || mMedium.isPortrait()) && !mIsSubsamplingVisible) { + addZoomableView() + } + }, ZOOMABLE_VIEW_LOAD_DELAY) + } + + private fun addZoomableView() { + val rotation = degreesForRotation(mImageOrientation) + mIsSubsamplingVisible = true + val config = context!!.config + val showHighestQuality = config.showHighestQuality + val minTileDpi = if (showHighestQuality) -1 else getMinTileDpi() + + val bitmapDecoder = object : DecoderFactory { + override fun make() = MyGlideImageDecoder(rotation, mMedium.getKey()) + } + + val regionDecoder = object : DecoderFactory { + override fun make() = PicassoRegionDecoder(showHighestQuality, mScreenWidth, mScreenHeight, minTileDpi) + } + + var newOrientation = (rotation + mCurrentRotationDegrees) % 360 + if (newOrientation < 0) { + newOrientation += 360 + } + + mView.subsampling_view.apply { + setMaxTileSize(if (showHighestQuality) Integer.MAX_VALUE else 4096) + setMinimumTileDpi(minTileDpi) + background = ColorDrawable(Color.TRANSPARENT) + bitmapDecoderFactory = bitmapDecoder + regionDecoderFactory = regionDecoder + maxScale = 10f + beVisible() + rotationEnabled = config.allowRotatingWithGestures + isOneToOneZoomEnabled = config.allowOneToOneZoom + orientation = newOrientation + setImage(getFilePathToShow()) + + 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) -> WEIRD_TILE_DPI + averageDpi > 400 -> HIGH_TILE_DPI + averageDpi > 300 -> NORMAL_TILE_DPI + else -> LOW_TILE_DPI + } + } + + 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()) + imageParser.contains("GPano:UsePanoramaViewer=\"True\"", true) || + imageParser.contains("True", true) || + imageParser.contains("GPano:FullPanoWidthPixels=") || + imageParser.contains("GPano:ProjectionType>Equirectangular") + } catch (e: Exception) { + false + } catch (e: OutOfMemoryError) { + false + } + + mView.panorama_outline.beVisibleIf(mIsPanorama) + if (mIsFullscreen) { + mView.panorama_outline.alpha = 0f + } + } + + private fun getImageOrientation(): Int { + val defaultOrientation = -1 + var orient = defaultOrientation + + try { + val path = getFilePathToShow() + orient = if (path.startsWith("content:/")) { + val inputStream = context!!.contentResolver.openInputStream(Uri.parse(path)) + val exif = ExifInterface() + exif.readExif(inputStream, ExifInterface.Options.OPTION_ALL) + val tag = exif.getTag(ExifInterface.TAG_ORIENTATION) + tag?.getValueAsInt(defaultOrientation) ?: defaultOrientation + } else { + val exif = androidx.exifinterface.media.ExifInterface(path) + exif.getAttributeInt(TAG_ORIENTATION, defaultOrientation) + } + + if (orient == defaultOrientation || context!!.isPathOnOTG(getFilePathToShow())) { + val uri = if (path.startsWith("content:/")) Uri.parse(path) else Uri.fromFile(File(path)) + 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() { + mView.instant_prev_item.layoutParams.width = mScreenWidth / 7 + mView.instant_next_item.layoutParams.width = mScreenWidth / 7 + } + + override fun fullscreenToggled(isFullscreen: Boolean) { + this.mIsFullscreen = isFullscreen + mView.apply { + photo_details.apply { + if (mStoredShowExtendedDetails && isVisible() && context != null && resources != null) { + animate().y(getExtendedDetailsY(height)) + + if (mStoredHideExtendedDetails) { + animate().alpha(if (isFullscreen) 0f else 1f).start() + } + } + } + + if (mIsPanorama) { + panorama_outline.animate().alpha(if (isFullscreen) 0f else 1f).start() + panorama_outline.isClickable = !isFullscreen + } + + if (mWasInit && mMedium.isPortrait()) { + photo_portrait_stripe_wrapper.animate().alpha(if (isFullscreen) 0f else 1f).start() + } + } + } + + private fun getExtendedDetailsY(height: Int): Float { + val smallMargin = context?.resources?.getDimension(R.dimen.small_margin) ?: return 0f + 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 + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/VideoFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/VideoFragment.kt new file mode 100644 index 000000000..35c1b8913 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/VideoFragment.kt @@ -0,0 +1,802 @@ +package com.simplemobiletools.gallery.pro.fragments + +import android.content.Intent +import android.content.res.Configuration +import android.graphics.Point +import android.graphics.SurfaceTexture +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.util.DisplayMetrics +import android.view.* +import android.widget.ImageView +import android.widget.RelativeLayout +import android.widget.SeekBar +import android.widget.TextView +import com.bumptech.glide.Glide +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.upstream.FileDataSource +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +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.config +import com.simplemobiletools.gallery.pro.extensions.hasNavBar +import com.simplemobiletools.gallery.pro.extensions.parseFileChannel +import com.simplemobiletools.gallery.pro.helpers.* +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 var mIsFullscreen = false + private var mWasFragmentInit = false + private var mIsPanorama = false + private var mIsFragmentVisible = false + private var mIsDragged = false + private var mWasVideoStarted = false + private var mWasPlayerInited = false + private var mWasLastPositionRestored = false + private var mPlayOnPrepared = false + private var mIsPlayerPrepared = false + private var mCurrTime = 0 + private var mDuration = 0 + private var mPositionWhenInit = 0 + private var mPositionAtPause = 0L + var mIsPlaying = false + + private var mExoPlayer: SimpleExoPlayer? = null + private var mVideoSize = Point(1, 1) + private var mTimerHandler = Handler() + + private var mStoredShowExtendedDetails = false + private var mStoredHideExtendedDetails = false + private var mStoredBottomActions = true + private var mStoredExtendedDetails = 0 + private var mStoredRememberLastVideoPosition = false + + 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? { + mMedium = arguments!!.getSerializable(MEDIUM) as Medium + mConfig = context!!.config + mView = inflater.inflate(R.layout.pager_video_item, container, false).apply { + panorama_outline.setOnClickListener { openPanorama() } + video_curr_time.setOnClickListener { skip(false) } + video_duration.setOnClickListener { skip(true) } + video_holder.setOnClickListener { toggleFullscreen() } + video_preview.setOnClickListener { toggleFullscreen() } + video_surface_frame.controller.settings.swallowDoubleTaps = true + + 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 + mTextureView.surfaceTextureListener = this@VideoFragment + + val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { + if (!mConfig.allowInstantChange) { + toggleFullscreen() + return true + } + + val viewWidth = width + val instantWidth = viewWidth / 7 + val clickedX = e?.rawX ?: 0f + when { + clickedX <= instantWidth -> listener?.goToPrevItem() + clickedX >= viewWidth - instantWidth -> listener?.goToNextItem() + else -> toggleFullscreen() + } + return true + } + + override fun onDoubleTap(e: MotionEvent?): Boolean { + if (e != null) { + handleDoubleTap(e.rawX) + } + + return true + } + }) + + video_preview.setOnTouchListener { view, event -> + handleEvent(event) + false + } + + video_surface_frame.setOnTouchListener { view, event -> + if (video_surface_frame.controller.state.zoom == 1f) { + handleEvent(event) + } + + gestureDetector.onTouchEvent(event) + false + } + } + + if (!arguments!!.getBoolean(SHOULD_INIT_FRAGMENT, true)) { + return mView + } + + storeStateVariables() + Glide.with(context!!).load(mMedium.path).into(mView.video_preview) + + // setMenuVisibility is not called at VideoActivity (third party intent) + if (!mIsFragmentVisible && activity is VideoActivity) { + mIsFragmentVisible = true + } + + mIsFullscreen = activity!!.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN == View.SYSTEM_UI_FLAG_FULLSCREEN + initTimeHolder() + checkIfPanorama() + + ensureBackgroundThread { + activity?.getVideoResolution(mMedium.path)?.apply { + mVideoSize.x = x + mVideoSize.y = y + } + } + + 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) { + if (savedInstanceState != null) { + mCurrTime = savedInstanceState.getInt(PROGRESS) + } + + mWasFragmentInit = true + setVideoSize() + + mView.apply { + mBrightnessSideScroll.initialize(activity!!, slide_info, true, container, singleTap = { x, y -> + if (mConfig.allowInstantChange) { + listener?.goToPrevItem() + } else { + toggleFullscreen() + } + }, doubleTap = { x, y -> + doSkip(false) + }) + + mVolumeSideScroll.initialize(activity!!, slide_info, false, container, singleTap = { x, y -> + if (mConfig.allowInstantChange) { + listener?.goToNextItem() + } else { + toggleFullscreen() + } + }, doubleTap = { x, y -> + doSkip(true) + }) + + video_surface.onGlobalLayout { + if (mIsFragmentVisible && mConfig.autoplayVideos && !mConfig.openVideosOnSeparateScreen) { + playVideo() + } + } + } + } + + setupVideoDuration() + if (mStoredRememberLastVideoPosition) { + restoreLastVideoSavedPosition() + } + + return mView + } + + override fun onResume() { + super.onResume() + 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 + mTextureView.beGoneIf(mConfig.openVideosOnSeparateScreen || mIsPanorama) + mView.video_surface_frame.beGoneIf(mTextureView.isGone()) + + mVolumeSideScroll.beVisibleIf(allowVideoGestures && !mIsPanorama) + mBrightnessSideScroll.beVisibleIf(allowVideoGestures && !mIsPanorama) + + checkExtendedDetails() + initTimeHolder() + storeStateVariables() + } + + override fun onPause() { + super.onPause() + storeStateVariables() + pauseVideo() + if (mStoredRememberLastVideoPosition && mIsFragmentVisible && mWasVideoStarted) { + saveVideoProgress() + } + } + + override fun onDestroy() { + super.onDestroy() + if (activity?.isChangingConfigurations == false) { + cleanup() + } + } + + override fun setMenuVisibility(menuVisible: Boolean) { + super.setMenuVisibility(menuVisible) + if (mIsFragmentVisible && !menuVisible) { + pauseVideo() + } + + mIsFragmentVisible = menuVisible + if (mWasFragmentInit && menuVisible && mConfig.autoplayVideos && !mConfig.openVideosOnSeparateScreen) { + playVideo() + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + setVideoSize() + initTimeHolder() + checkExtendedDetails() + 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() { + mConfig.apply { + mStoredShowExtendedDetails = showExtendedDetails + mStoredHideExtendedDetails = hideExtendedDetails + mStoredExtendedDetails = extendedDetails + mStoredBottomActions = bottomActions + mStoredRememberLastVideoPosition = rememberLastVideoPosition + } + } + + private fun saveVideoProgress() { + if (!videoEnded()) { + if (mExoPlayer != null) { + mConfig.saveLastVideoPosition(mMedium.path, mExoPlayer!!.currentPosition.toInt() / 1000) + } else { + mConfig.saveLastVideoPosition(mMedium.path, mPositionAtPause.toInt() / 1000) + } + } + } + + private fun restoreLastVideoSavedPosition() { + val pos = mConfig.getLastVideoPosition(mMedium.path) + if (pos > 0) { + mPositionAtPause = pos * 1000L + setPosition(pos) + } + } + + 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() { + if (activity == null || mConfig.openVideosOnSeparateScreen || mIsPanorama || mExoPlayer != null) { + return + } + + mExoPlayer = ExoPlayerFactory.newSimpleInstance(context) + mExoPlayer!!.seekParameters = SeekParameters.CLOSEST_SYNC + if (mConfig.loopVideos && listener?.isSlideShowActive() == false) { + mExoPlayer?.repeatMode = Player.REPEAT_MODE_ONE + } + + 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 { + fileDataSource.open(dataSpec) + } catch (e: Exception) { + activity?.showErrorToast(e) + return + } + + val factory = DataSource.Factory { fileDataSource } + val audioSource = ExtractorMediaSource(fileDataSource.uri, factory, DefaultExtractorsFactory(), null, null) + mPlayOnPrepared = true + mExoPlayer!!.audioStreamType = C.STREAM_TYPE_MUSIC + mExoPlayer!!.prepare(audioSource) + + if (mTextureView.surfaceTexture != null) { + mExoPlayer!!.setVideoSurface(Surface(mTextureView.surfaceTexture)) + } + + 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) { + // Reset progress views when video loops. + if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) { + mSeekBar.progress = 0 + mCurrTimeView.text = 0.getFormattedDuration() + } + } + + 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 : SimpleExoPlayer.VideoListener { + override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) { + mVideoSize.x = width + mVideoSize.y = (height / pixelWidthHeightRatio).toInt() + setVideoSize() + } + + override fun onRenderedFirstFrame() {} + }) + } + + private fun launchVideoPlayer() { + listener?.launchViewVideoIntent(mMedium.path) + } + + private fun toggleFullscreen() { + listener?.fragmentClicked() + } + + private fun handleDoubleTap(x: Float) { + val viewWidth = mView.width + val instantWidth = viewWidth / 7 + when { + x <= instantWidth -> doSkip(false) + x >= viewWidth - instantWidth -> doSkip(true) + else -> togglePlayPause() + } + } + + 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() + } + } + + private fun initTimeHolder() { + var right = 0 + var bottom = context!!.navigationBarHeight + if (mConfig.bottomActions) { + bottom += resources.getDimension(R.dimen.bottom_actions_height).toInt() + } + + if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && activity?.hasNavBar() == true) { + right += activity!!.navigationBarWidth + } + + (mTimeHolder.layoutParams as RelativeLayout.LayoutParams).apply { + bottomMargin = bottom + rightMargin = right + } + mTimeHolder.beInvisibleIf(mIsFullscreen) + } + + private fun checkIfPanorama() { + try { + val fis = FileInputStream(File(mMedium.path)) + fis.use { + context!!.parseFileChannel(mMedium.path, it.channel, 0, 0, 0) { + mIsPanorama = true + } + } + } catch (ignored: Exception) { + } catch (ignored: OutOfMemoryError) { + } + } + + private fun openPanorama() { + Intent(context, PanoramaVideoActivity::class.java).apply { + putExtra(PATH, mMedium.path) + startActivity(this) + } + } + + 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, mView.video_toggle_play_pause).forEach { + it.isClickable = !mIsFullscreen + } + + mTimeHolder.animate().alpha(newAlpha).start() + mView.video_details.apply { + if (mStoredShowExtendedDetails && isVisible() && context != null && resources != null) { + animate().y(getExtendedDetailsY(height)) + + if (mStoredHideExtendedDetails) { + animate().alpha(newAlpha).start() + } + } + } + } + + private fun getExtendedDetailsY(height: Int): Float { + val smallMargin = context?.resources?.getDimension(R.dimen.small_margin) ?: return 0f + 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 (mIsPanorama) { + return + } else if (mExoPlayer == null) { + playVideo() + return + } + + mPositionAtPause = 0L + doSkip(forward) + } + + private fun doSkip(forward: Boolean) { + if (mExoPlayer == null) { + return + } + + val curr = mExoPlayer!!.currentPosition + val newProgress = if (forward) curr + FAST_FORWARD_VIDEO_MS else curr - FAST_FORWARD_VIDEO_MS + val roundProgress = Math.round(newProgress / 1000f) + val limitedProgress = Math.max(Math.min(mExoPlayer!!.duration.toInt() / 1000, roundProgress), 0) + setPosition(limitedProgress) + if (!mIsPlaying) { + togglePlayPause() + } + } + + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (fromUser) { + if (mExoPlayer != null) { + if (!mWasPlayerInited) { + mPositionWhenInit = progress + } + setPosition(progress) + } + + if (mExoPlayer == null) { + mPositionAtPause = progress * 1000L + playVideo() + } + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + if (mExoPlayer == null) { + return + } + + mExoPlayer!!.playWhenReady = false + mIsDragged = true + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + if (mIsPanorama) { + openPanorama() + return + } + + if (mExoPlayer == null) { + return + } + + if (mIsPlaying) { + mExoPlayer!!.playWhenReady = true + } else { + playVideo() + } + + mIsDragged = false + } + + private fun togglePlayPause() { + if (activity == null || !isAdded) { + return + } + + if (mIsPlaying) { + pauseVideo() + } else { + playVideo() + } + } + + fun playVideo() { + if (mExoPlayer == null) { + initExoPlayer() + return + } + + if (mView.video_preview.isVisible()) { + mView.video_preview.beGone() + initExoPlayer() + } + + val wasEnded = videoEnded() + if (wasEnded) { + setPosition(0) + } + + if (mStoredRememberLastVideoPosition && !mWasLastPositionRestored) { + mWasLastPositionRestored = true + restoreLastVideoSavedPosition() + } + + if (!wasEnded || !mConfig.loopVideos) { + mPlayPauseButton.setImageResource(R.drawable.ic_pause_outline_vector) + } + + if (!mWasVideoStarted) { + mView.video_play_outline.beGone() + mPlayPauseButton.beVisible() + } + + mWasVideoStarted = true + if (mIsPlayerPrepared) { + mIsPlaying = true + } + mExoPlayer?.playWhenReady = true + activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + + private fun pauseVideo() { + if (mExoPlayer == null) { + return + } + + mIsPlaying = false + if (!videoEnded()) { + mExoPlayer?.playWhenReady = false + } + + mPlayPauseButton.setImageResource(R.drawable.ic_play_outline_vector) + activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + mPositionAtPause = mExoPlayer?.currentPosition ?: 0L + releaseExoPlayer() + } + + private fun videoEnded(): Boolean { + val currentPos = mExoPlayer?.currentPosition ?: 0 + val duration = mExoPlayer?.duration ?: 0 + return currentPos != 0L && currentPos >= duration + } + + private fun setPosition(seconds: Int) { + mExoPlayer?.seekTo(seconds * 1000L) + mSeekBar.progress = seconds + mCurrTimeView.text = seconds.getFormattedDuration() + + if (!mIsPlaying) { + mPositionAtPause = mExoPlayer?.currentPosition ?: 0L + } + } + + private fun setupVideoDuration() { + ensureBackgroundThread { + mDuration = context?.getDuration(mMedium.path) ?: 0 + + activity?.runOnUiThread { + setupTimeHolder() + setPosition(0) + } + } + } + + private fun videoPrepared() { + if (mDuration == 0) { + mDuration = (mExoPlayer!!.duration / 1000).toInt() + setupTimeHolder() + setPosition(mCurrTime) + + if (mIsFragmentVisible && (mConfig.autoplayVideos)) { + playVideo() + } + } + + if (mPositionWhenInit != 0 && !mWasPlayerInited) { + setPosition(mPositionWhenInit) + mPositionWhenInit = 0 + } + + mIsPlayerPrepared = true + if (mPlayOnPrepared && !mIsPlaying) { + if (mPositionAtPause != 0L) { + mExoPlayer?.seekTo(mPositionAtPause) + mPositionAtPause = 0L + } + playVideo() + } + mWasPlayerInited = true + mPlayOnPrepared = false + } + + private fun videoCompleted() { + if (!isAdded || mExoPlayer == null) { + return + } + + mCurrTime = (mExoPlayer!!.duration / 1000).toInt() + if (listener?.videoEnded() == false && mConfig.loopVideos) { + playVideo() + } else { + mSeekBar.progress = mSeekBar.max + mCurrTimeView.text = mDuration.getFormattedDuration() + pauseVideo() + } + } + + private fun cleanup() { + pauseVideo() + releaseExoPlayer() + + if (mWasFragmentInit) { + mCurrTimeView.text = 0.getFormattedDuration() + mSeekBar.progress = 0 + mTimerHandler.removeCallbacksAndMessages(null) + } + } + + private fun releaseExoPlayer() { + mIsPlayerPrepared = false + mExoPlayer?.stop() + ensureBackgroundThread { + mExoPlayer?.release() + mExoPlayer = null + } + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {} + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {} + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?) = false + + override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) { + ensureBackgroundThread { + mExoPlayer?.setVideoSurface(Surface(mTextureView.surfaceTexture)) + } + } + + private fun setVideoSize() { + 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 + + val realMetrics = DisplayMetrics() + display.getRealMetrics(realMetrics) + screenWidth = realMetrics.widthPixels + screenHeight = realMetrics.heightPixels + + val screenProportion = screenWidth.toFloat() / screenHeight.toFloat() + + mTextureView.layoutParams.apply { + if (videoProportion > screenProportion) { + width = screenWidth + height = (screenWidth.toFloat() / videoProportion).toInt() + } else { + width = (videoProportion * screenHeight.toFloat()).toInt() + height = screenHeight + } + mTextureView.layoutParams = this + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/ViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/ViewPagerFragment.kt new file mode 100644 index 000000000..66fbd8df5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/ViewPagerFragment.kt @@ -0,0 +1,154 @@ +package com.simplemobiletools.gallery.pro.fragments + +import android.provider.MediaStore +import android.provider.MediaStore.Files +import android.provider.MediaStore.Images +import android.view.MotionEvent +import androidx.exifinterface.media.ExifInterface +import androidx.fragment.app.Fragment +import com.simplemobiletools.commons.extensions.* +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 + + private var mTouchDownTime = 0L + private var mTouchDownX = 0f + private var mTouchDownY = 0f + private var mCloseDownThreshold = 100f + private var mIgnoreCloseDown = false + + abstract fun fullscreenToggled(isFullscreen: Boolean) + + interface FragmentListener { + fun fragmentClicked() + + fun videoEnded(): Boolean + + fun goToPrevItem() + + fun goToNextItem() + + fun launchViewVideoIntent(path: String) + + fun isSlideShowActive(): Boolean + } + + fun getMediumExtendedDetails(medium: Medium): String { + val file = File(medium.path) + if (context?.getDoesFilePathExist(file.absolutePath) == false) { + return "" + } + + val path = "${file.parent.trimEnd('/')}/" + val exif = try { + ExifInterface(medium.path) + } catch (e: Exception) { + return "" + } + + 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) { + context!!.getResolution(file.absolutePath)?.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) { + exif.getExifDateTaken(context!!).let { if (it.isNotEmpty()) details.appendln(it) } + } + + if (detailsFlag and EXT_CAMERA_MODEL != 0) { + exif.getExifCameraModel().let { if (it.isNotEmpty()) details.appendln(it) } + } + + if (detailsFlag and EXT_EXIF_PROPERTIES != 0) { + exif.getExifProperties().let { if (it.isNotEmpty()) details.appendln(it) } + } + + if (detailsFlag and EXT_GPS != 0) { + getLatLonAltitude(medium.path).let { if (it.isNotEmpty()) details.appendln(it) } + } + return details.toString().trim() + } + + fun getPathToLoad(medium: Medium) = if (context?.isPathOnOTG(medium.path) == true) medium.path.getOTGPublicPath(context!!) else medium.path + + private fun getFileLastModified(file: File): String { + val projection = arrayOf(Images.Media.DATE_MODIFIED) + val uri = 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(Images.Media.DATE_MODIFIED) * 1000L + dateModified.formatDate(context!!) + } else { + file.lastModified().formatDate(context!!) + } + } + return "" + } + + private fun getLatLonAltitude(path: String): String { + var result = "" + val exif = try { + ExifInterface(path) + } catch (e: Exception) { + return "" + } + + val latLon = FloatArray(2) + + if (exif.getLatLong(latLon)) { + result = "${latLon[0]}, ${latLon[1]}" + } + + val altitude = exif.getAltitude(0.0) + if (altitude != 0.0) { + result += ", ${altitude}m" + } + + return result.trimStart(',').trim() + } + + 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 && context?.config?.allowDownGesture == true) { + activity?.supportFinishAfterTransition() + } + mIgnoreCloseDown = false + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Config.kt new file mode 100644 index 000000000..bc5e2f9a2 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Config.kt @@ -0,0 +1,518 @@ +package com.simplemobiletools.gallery.pro.helpers + +import android.content.Context +import android.content.res.Configuration +import android.os.Environment +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.simplemobiletools.commons.helpers.BaseConfig +import com.simplemobiletools.commons.helpers.SORT_BY_DATE_MODIFIED +import com.simplemobiletools.commons.helpers.SORT_DESCENDING +import com.simplemobiletools.commons.helpers.VIEW_TYPE_GRID +import com.simplemobiletools.gallery.pro.R +import com.simplemobiletools.gallery.pro.models.AlbumCover +import java.util.* + +class Config(context: Context) : BaseConfig(context) { + companion object { + fun newInstance(context: Context) = Config(context) + } + + var directorySorting: Int + get(): Int = prefs.getInt(DIRECTORY_SORT_ORDER, SORT_BY_DATE_MODIFIED or SORT_DESCENDING) + set(order) = prefs.edit().putInt(DIRECTORY_SORT_ORDER, order).apply() + + fun saveFolderGrouping(path: String, value: Int) { + if (path.isEmpty()) { + groupBy = value + } else { + prefs.edit().putInt(GROUP_FOLDER_PREFIX + path.toLowerCase(), value).apply() + } + } + + fun getFolderGrouping(path: String): Int { + var groupBy = prefs.getInt(GROUP_FOLDER_PREFIX + path.toLowerCase(), groupBy) + if (path != SHOW_ALL && groupBy and GROUP_BY_FOLDER != 0) { + groupBy -= GROUP_BY_FOLDER + 1 + } + return groupBy + } + + fun removeFolderGrouping(path: String) { + prefs.edit().remove(GROUP_FOLDER_PREFIX + path.toLowerCase()).apply() + } + + 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() + + var shouldShowHidden = showHiddenMedia || temporarilyShowHidden + + var showHiddenMedia: Boolean + get() = prefs.getBoolean(SHOW_HIDDEN_MEDIA, false) + set(showHiddenFolders) = prefs.edit().putBoolean(SHOW_HIDDEN_MEDIA, showHiddenFolders).apply() + + var temporarilyShowHidden: Boolean + get() = prefs.getBoolean(TEMPORARILY_SHOW_HIDDEN, false) + set(temporarilyShowHidden) = prefs.edit().putBoolean(TEMPORARILY_SHOW_HIDDEN, temporarilyShowHidden).apply() + + var isThirdPartyIntent: Boolean + get() = prefs.getBoolean(IS_THIRD_PARTY_INTENT, false) + set(isThirdPartyIntent) = prefs.edit().putBoolean(IS_THIRD_PARTY_INTENT, isThirdPartyIntent).apply() + + var pinnedFolders: Set + get() = prefs.getStringSet(PINNED_FOLDERS, HashSet())!! + set(pinnedFolders) = prefs.edit().putStringSet(PINNED_FOLDERS, pinnedFolders).apply() + + var showAll: Boolean + get() = prefs.getBoolean(SHOW_ALL, false) + set(showAll) = prefs.edit().putBoolean(SHOW_ALL, showAll).apply() + + fun addPinnedFolders(paths: Set) { + val currPinnedFolders = HashSet(pinnedFolders) + currPinnedFolders.addAll(paths) + pinnedFolders = currPinnedFolders.filter { it.isNotEmpty() }.toHashSet() + if (paths.contains(RECYCLE_BIN)) { + showRecycleBinLast = false + } + } + + fun removePinnedFolders(paths: Set) { + val currPinnedFolders = HashSet(pinnedFolders) + currPinnedFolders.removeAll(paths) + pinnedFolders = currPinnedFolders + } + + fun addExcludedFolder(path: String) { + addExcludedFolders(HashSet(Arrays.asList(path))) + } + + fun addExcludedFolders(paths: Set) { + val currExcludedFolders = HashSet(excludedFolders) + currExcludedFolders.addAll(paths) + excludedFolders = currExcludedFolders.filter { it.isNotEmpty() }.toHashSet() + } + + fun removeExcludedFolder(path: String) { + val currExcludedFolders = HashSet(excludedFolders) + currExcludedFolders.remove(path) + excludedFolders = currExcludedFolders + } + + var excludedFolders: MutableSet + get() = prefs.getStringSet(EXCLUDED_FOLDERS, HashSet())!! + set(excludedFolders) = prefs.edit().remove(EXCLUDED_FOLDERS).putStringSet(EXCLUDED_FOLDERS, excludedFolders).apply() + + fun addIncludedFolder(path: String) { + val currIncludedFolders = HashSet(includedFolders) + currIncludedFolders.add(path) + includedFolders = currIncludedFolders + } + + fun addIncludedFolders(paths: Set) { + val currIncludedFolders = HashSet(includedFolders) + currIncludedFolders.addAll(paths) + includedFolders = currIncludedFolders.filter { it.isNotEmpty() }.toHashSet() + } + + fun removeIncludedFolder(path: String) { + val currIncludedFolders = HashSet(includedFolders) + currIncludedFolders.remove(path) + includedFolders = currIncludedFolders + } + + var includedFolders: MutableSet + get() = prefs.getStringSet(INCLUDED_FOLDERS, HashSet())!! + set(includedFolders) = prefs.edit().remove(INCLUDED_FOLDERS).putStringSet(INCLUDED_FOLDERS, includedFolders).apply() + + var autoplayVideos: Boolean + get() = prefs.getBoolean(AUTOPLAY_VIDEOS, false) + set(autoplayVideos) = prefs.edit().putBoolean(AUTOPLAY_VIDEOS, autoplayVideos).apply() + + var animateGifs: Boolean + get() = prefs.getBoolean(ANIMATE_GIFS, false) + set(animateGifs) = prefs.edit().putBoolean(ANIMATE_GIFS, animateGifs).apply() + + var maxBrightness: Boolean + get() = prefs.getBoolean(MAX_BRIGHTNESS, false) + set(maxBrightness) = prefs.edit().putBoolean(MAX_BRIGHTNESS, maxBrightness).apply() + + var cropThumbnails: Boolean + 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 showThumbnailFileTypes: Boolean + get() = prefs.getBoolean(SHOW_THUMBNAIL_FILE_TYPES, true) + set(showThumbnailFileTypes) = prefs.edit().putBoolean(SHOW_THUMBNAIL_FILE_TYPES, showThumbnailFileTypes).apply() + + var screenRotation: Int + get() = prefs.getInt(SCREEN_ROTATION, ROTATE_BY_SYSTEM_SETTING) + set(screenRotation) = prefs.edit().putInt(SCREEN_ROTATION, screenRotation).apply() + + var fileLoadingPriority: Int + get() = prefs.getInt(FILE_LOADING_PRIORITY, PRIORITY_COMPROMISE) + set(fileLoadingPriority) = prefs.edit().putInt(FILE_LOADING_PRIORITY, fileLoadingPriority).apply() + + var loopVideos: Boolean + 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(BLACK_BACKGROUND, false) + set(blackBackground) = prefs.edit().putBoolean(BLACK_BACKGROUND, blackBackground).apply() + + var filterMedia: Int + get() = prefs.getInt(FILTER_MEDIA, getDefaultFileFilter()) + set(filterMedia) = prefs.edit().putInt(FILTER_MEDIA, filterMedia).apply() + + var dirColumnCnt: Int + get() = prefs.getInt(getDirectoryColumnsField(), getDefaultDirectoryColumnCount()) + set(dirColumnCnt) = prefs.edit().putInt(getDirectoryColumnsField(), dirColumnCnt).apply() + + var defaultFolder: String + get() = prefs.getString(DEFAULT_FOLDER, "")!! + set(defaultFolder) = prefs.edit().putString(DEFAULT_FOLDER, defaultFolder).apply() + + var allowInstantChange: Boolean + get() = prefs.getBoolean(ALLOW_INSTANT_CHANGE, false) + set(allowInstantChange) = prefs.edit().putBoolean(ALLOW_INSTANT_CHANGE, allowInstantChange).apply() + + private fun getDirectoryColumnsField(): String { + val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT + return if (isPortrait) { + if (scrollHorizontally) { + DIR_HORIZONTAL_COLUMN_CNT + } else { + DIR_COLUMN_CNT + } + } else { + if (scrollHorizontally) { + DIR_LANDSCAPE_HORIZONTAL_COLUMN_CNT + } else { + DIR_LANDSCAPE_COLUMN_CNT + } + } + } + + private fun getDefaultDirectoryColumnCount() = context.resources.getInteger(if (scrollHorizontally) R.integer.directory_columns_horizontal_scroll + else R.integer.directory_columns_vertical_scroll) + + var mediaColumnCnt: Int + get() = prefs.getInt(getMediaColumnsField(), getDefaultMediaColumnCount()) + set(mediaColumnCnt) = prefs.edit().putInt(getMediaColumnsField(), mediaColumnCnt).apply() + + private fun getMediaColumnsField(): String { + val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT + return if (isPortrait) { + if (scrollHorizontally) { + MEDIA_HORIZONTAL_COLUMN_CNT + } else { + MEDIA_COLUMN_CNT + } + } else { + if (scrollHorizontally) { + MEDIA_LANDSCAPE_HORIZONTAL_COLUMN_CNT + } else { + MEDIA_LANDSCAPE_COLUMN_CNT + } + } + } + + private fun getDefaultMediaColumnCount() = context.resources.getInteger(if (scrollHorizontally) R.integer.media_columns_horizontal_scroll + else R.integer.media_columns_vertical_scroll) + + var albumCovers: String + get() = prefs.getString(ALBUM_COVERS, "")!! + set(albumCovers) = prefs.edit().putString(ALBUM_COVERS, albumCovers).apply() + + fun parseAlbumCovers(): ArrayList { + val listType = object : TypeToken>() {}.type + return Gson().fromJson>(albumCovers, listType) ?: ArrayList(1) + } + + var hideSystemUI: Boolean + get() = prefs.getBoolean(HIDE_SYSTEM_UI, false) + set(hideSystemUI) = prefs.edit().putBoolean(HIDE_SYSTEM_UI, hideSystemUI).apply() + + var deleteEmptyFolders: Boolean + get() = prefs.getBoolean(DELETE_EMPTY_FOLDERS, false) + set(deleteEmptyFolders) = prefs.edit().putBoolean(DELETE_EMPTY_FOLDERS, deleteEmptyFolders).apply() + + var allowPhotoGestures: Boolean + get() = prefs.getBoolean(ALLOW_PHOTO_GESTURES, true) + set(allowPhotoGestures) = prefs.edit().putBoolean(ALLOW_PHOTO_GESTURES, allowPhotoGestures).apply() + + var allowVideoGestures: Boolean + get() = prefs.getBoolean(ALLOW_VIDEO_GESTURES, true) + set(allowVideoGestures) = prefs.edit().putBoolean(ALLOW_VIDEO_GESTURES, allowVideoGestures).apply() + + var slideshowInterval: Int + get() = prefs.getInt(SLIDESHOW_INTERVAL, SLIDESHOW_DEFAULT_INTERVAL) + set(slideshowInterval) = prefs.edit().putInt(SLIDESHOW_INTERVAL, slideshowInterval).apply() + + var slideshowIncludeVideos: Boolean + get() = prefs.getBoolean(SLIDESHOW_INCLUDE_VIDEOS, false) + set(slideshowIncludeVideos) = prefs.edit().putBoolean(SLIDESHOW_INCLUDE_VIDEOS, slideshowIncludeVideos).apply() + + var slideshowIncludeGIFs: Boolean + get() = prefs.getBoolean(SLIDESHOW_INCLUDE_GIFS, false) + set(slideshowIncludeGIFs) = prefs.edit().putBoolean(SLIDESHOW_INCLUDE_GIFS, slideshowIncludeGIFs).apply() + + var slideshowRandomOrder: Boolean + get() = prefs.getBoolean(SLIDESHOW_RANDOM_ORDER, false) + set(slideshowRandomOrder) = prefs.edit().putBoolean(SLIDESHOW_RANDOM_ORDER, slideshowRandomOrder).apply() + + var slideshowMoveBackwards: Boolean + get() = prefs.getBoolean(SLIDESHOW_MOVE_BACKWARDS, false) + set(slideshowMoveBackwards) = prefs.edit().putBoolean(SLIDESHOW_MOVE_BACKWARDS, slideshowMoveBackwards).apply() + + var slideshowAnimation: Int + get() = prefs.getInt(SLIDESHOW_ANIMATION, SLIDESHOW_ANIMATION_SLIDE) + set(slideshowAnimation) = prefs.edit().putInt(SLIDESHOW_ANIMATION, slideshowAnimation).apply() + + var loopSlideshow: Boolean + get() = prefs.getBoolean(SLIDESHOW_LOOP, false) + set(loopSlideshow) = prefs.edit().putBoolean(SLIDESHOW_LOOP, loopSlideshow).apply() + + var tempFolderPath: String + get() = prefs.getString(TEMP_FOLDER_PATH, "")!! + set(tempFolderPath) = prefs.edit().putString(TEMP_FOLDER_PATH, tempFolderPath).apply() + + var viewTypeFolders: Int + get() = prefs.getInt(VIEW_TYPE_FOLDERS, VIEW_TYPE_GRID) + set(viewTypeFolders) = prefs.edit().putInt(VIEW_TYPE_FOLDERS, viewTypeFolders).apply() + + var viewTypeFiles: Int + get() = prefs.getInt(VIEW_TYPE_FILES, VIEW_TYPE_GRID) + set(viewTypeFiles) = prefs.edit().putInt(VIEW_TYPE_FILES, viewTypeFiles).apply() + + var showExtendedDetails: Boolean + get() = prefs.getBoolean(SHOW_EXTENDED_DETAILS, false) + set(showExtendedDetails) = prefs.edit().putBoolean(SHOW_EXTENDED_DETAILS, showExtendedDetails).apply() + + var hideExtendedDetails: Boolean + get() = prefs.getBoolean(HIDE_EXTENDED_DETAILS, false) + set(hideExtendedDetails) = prefs.edit().putBoolean(HIDE_EXTENDED_DETAILS, hideExtendedDetails).apply() + + var extendedDetails: Int + get() = prefs.getInt(EXTENDED_DETAILS, EXT_RESOLUTION or EXT_LAST_MODIFIED or EXT_EXIF_PROPERTIES) + set(extendedDetails) = prefs.edit().putInt(EXTENDED_DETAILS, extendedDetails).apply() + + var wasNewAppShown: Boolean + get() = prefs.getBoolean(WAS_NEW_APP_SHOWN, false) + set(wasNewAppShown) = prefs.edit().putBoolean(WAS_NEW_APP_SHOWN, wasNewAppShown).apply() + + var lastFilepickerPath: String + get() = prefs.getString(LAST_FILEPICKER_PATH, "")!! + set(lastFilepickerPath) = prefs.edit().putString(LAST_FILEPICKER_PATH, lastFilepickerPath).apply() + + var tempSkipDeleteConfirmation: Boolean + get() = prefs.getBoolean(TEMP_SKIP_DELETE_CONFIRMATION, false) + set(tempSkipDeleteConfirmation) = prefs.edit().putBoolean(TEMP_SKIP_DELETE_CONFIRMATION, tempSkipDeleteConfirmation).apply() + + var wereFavoritesPinned: Boolean + get() = prefs.getBoolean(WERE_FAVORITES_PINNED, false) + set(wereFavoritesPinned) = prefs.edit().putBoolean(WERE_FAVORITES_PINNED, wereFavoritesPinned).apply() + + var wasRecycleBinPinned: Boolean + get() = prefs.getBoolean(WAS_RECYCLE_BIN_PINNED, false) + set(wasRecycleBinPinned) = prefs.edit().putBoolean(WAS_RECYCLE_BIN_PINNED, wasRecycleBinPinned).apply() + + var wasSVGShowingHandled: Boolean + get() = prefs.getBoolean(WAS_SVG_SHOWING_HANDLED, false) + set(wasSVGShowingHandled) = prefs.edit().putBoolean(WAS_SVG_SHOWING_HANDLED, wasSVGShowingHandled).apply() + + var groupBy: Int + get() = prefs.getInt(GROUP_BY, GROUP_BY_NONE) + set(groupBy) = prefs.edit().putInt(GROUP_BY, groupBy).apply() + + var useRecycleBin: Boolean + get() = prefs.getBoolean(USE_RECYCLE_BIN, true) + set(useRecycleBin) = prefs.edit().putBoolean(USE_RECYCLE_BIN, useRecycleBin).apply() + + var bottomActions: Boolean + get() = prefs.getBoolean(BOTTOM_ACTIONS, true) + set(bottomActions) = prefs.edit().putBoolean(BOTTOM_ACTIONS, bottomActions).apply() + + fun removeLastVideoPosition(path: String) { + prefs.edit().remove("$LAST_VIDEO_POSITION_PREFIX${path.toLowerCase()}").apply() + } + + fun saveLastVideoPosition(path: String, value: Int) { + if (path.isNotEmpty()) { + prefs.edit().putInt("$LAST_VIDEO_POSITION_PREFIX${path.toLowerCase()}", value).apply() + } + } + + fun getLastVideoPosition(path: String) = prefs.getInt("$LAST_VIDEO_POSITION_PREFIX${path.toLowerCase()}", 0) + + fun getAllLastVideoPositions() = prefs.all.filterKeys { + it.startsWith(LAST_VIDEO_POSITION_PREFIX) + } + + var rememberLastVideoPosition: Boolean + get() = prefs.getBoolean(REMEMBER_LAST_VIDEO_POSITION, false) + set(rememberLastVideoPosition) { + if (!rememberLastVideoPosition) { + getAllLastVideoPositions().forEach { + prefs.edit().remove(it.key).apply() + } + } + prefs.edit().putBoolean(REMEMBER_LAST_VIDEO_POSITION, rememberLastVideoPosition).apply() + } + + var visibleBottomActions: Int + get() = prefs.getInt(VISIBLE_BOTTOM_ACTIONS, DEFAULT_BOTTOM_ACTIONS) + set(visibleBottomActions) = prefs.edit().putInt(VISIBLE_BOTTOM_ACTIONS, visibleBottomActions).apply() + + // if a user hides a folder, then enables temporary hidden folder displaying, make sure we show it properly + var everShownFolders: Set + get() = prefs.getStringSet(EVER_SHOWN_FOLDERS, getEverShownFolders())!! + set(everShownFolders) = prefs.edit().putStringSet(EVER_SHOWN_FOLDERS, everShownFolders).apply() + + private fun getEverShownFolders() = hashSetOf( + internalStoragePath, + 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", + "$internalStoragePath/WhatsApp/Media/WhatsApp Images", + "$internalStoragePath/WhatsApp/Media/WhatsApp Images/Sent", + "$internalStoragePath/WhatsApp/Media/WhatsApp Video", + "$internalStoragePath/WhatsApp/Media/WhatsApp Video/Sent", + ) + + var showRecycleBinAtFolders: Boolean + get() = prefs.getBoolean(SHOW_RECYCLE_BIN_AT_FOLDERS, true) + set(showRecycleBinAtFolders) = prefs.edit().putBoolean(SHOW_RECYCLE_BIN_AT_FOLDERS, showRecycleBinAtFolders).apply() + + var allowZoomingImages: Boolean + get() = prefs.getBoolean(ALLOW_ZOOMING_IMAGES, true) + set(allowZoomingImages) = prefs.edit().putBoolean(ALLOW_ZOOMING_IMAGES, allowZoomingImages).apply() + + 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: Float + get() = prefs.getFloat(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X, 2f) + set(lastEditorCropOtherAspectRatioX) = prefs.edit().putFloat(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X, lastEditorCropOtherAspectRatioX).apply() + + var lastEditorCropOtherAspectRatioY: Float + get() = prefs.getFloat(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y, 1f) + set(lastEditorCropOtherAspectRatioY) = prefs.edit().putFloat(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 allowRotatingWithGestures: Boolean + get() = prefs.getBoolean(ALLOW_ROTATING_WITH_GESTURES, true) + set(allowRotatingWithGestures) = prefs.edit().putBoolean(ALLOW_ROTATING_WITH_GESTURES, allowRotatingWithGestures).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() + + var spamFoldersChecked: Boolean + get() = prefs.getBoolean(SPAM_FOLDERS_CHECKED, false) + set(spamFoldersChecked) = prefs.edit().putBoolean(SPAM_FOLDERS_CHECKED, spamFoldersChecked).apply() + + var editorBrushColor: Int + get() = prefs.getInt(EDITOR_BRUSH_COLOR, -1) + set(editorBrushColor) = prefs.edit().putInt(EDITOR_BRUSH_COLOR, editorBrushColor).apply() + + var editorBrushHardness: Float + get() = prefs.getFloat(EDITOR_BRUSH_HARDNESS, 0.5f) + set(editorBrushHardness) = prefs.edit().putFloat(EDITOR_BRUSH_HARDNESS, editorBrushHardness).apply() + + var editorBrushSize: Float + get() = prefs.getFloat(EDITOR_BRUSH_SIZE, 0.05f) + set(editorBrushSize) = prefs.edit().putFloat(EDITOR_BRUSH_SIZE, editorBrushSize).apply() + + var wereFavoritesMigrated: Boolean + get() = prefs.getBoolean(WERE_FAVORITES_MIGRATED, false) + set(wereFavoritesMigrated) = prefs.edit().putBoolean(WERE_FAVORITES_MIGRATED, wereFavoritesMigrated).apply() + + var showFolderMediaCount: Int + get() = prefs.getInt(FOLDER_MEDIA_COUNT, FOLDER_MEDIA_CNT_LINE) + set(showFolderMediaCount) = prefs.edit().putInt(FOLDER_MEDIA_COUNT, showFolderMediaCount).apply() + + var folderStyle: Int + get() = prefs.getInt(FOLDER_THUMBNAIL_STYLE, FOLDER_STYLE_SQUARE) + set(folderStyle) = prefs.edit().putInt(FOLDER_THUMBNAIL_STYLE, folderStyle).apply() + + var limitFolderTitle: Boolean + get() = prefs.getBoolean(LIMIT_FOLDER_TITLE, false) + set(limitFolderTitle) = prefs.edit().putBoolean(LIMIT_FOLDER_TITLE, limitFolderTitle).apply() + + var thumbnailSpacing: Int + get() = prefs.getInt(THUMBNAIL_SPACING, 1) + set(thumbnailSpacing) = prefs.edit().putInt(THUMBNAIL_SPACING, thumbnailSpacing).apply() + + var fileRoundedCorners: Boolean + get() = prefs.getBoolean(FILE_ROUNDED_CORNERS, false) + set(fileRoundedCorners) = prefs.edit().putBoolean(FILE_ROUNDED_CORNERS, fileRoundedCorners).apply() +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Constants.kt new file mode 100644 index 000000000..7f622edd2 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Constants.kt @@ -0,0 +1,238 @@ +package com.simplemobiletools.gallery.pro.helpers + +import com.simplemobiletools.commons.helpers.MONTH_SECONDS +import com.simplemobiletools.commons.helpers.isQPlus + +// shared preferences +const val DIRECTORY_SORT_ORDER = "directory_sort_order" +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 BLACK_BACKGROUND = "dark_background" +const val PINNED_FOLDERS = "pinned_folders" +const val FILTER_MEDIA = "filter_media" +const val DEFAULT_FOLDER = "default_folder" +const val DIR_COLUMN_CNT = "dir_column_cnt" +const val DIR_LANDSCAPE_COLUMN_CNT = "dir_landscape_column_cnt" +const val DIR_HORIZONTAL_COLUMN_CNT = "dir_horizontal_column_cnt" +const val DIR_LANDSCAPE_HORIZONTAL_COLUMN_CNT = "dir_landscape_horizontal_column_cnt" +const val MEDIA_COLUMN_CNT = "media_column_cnt" +const val MEDIA_LANDSCAPE_COLUMN_CNT = "media_landscape_column_cnt" +const val MEDIA_HORIZONTAL_COLUMN_CNT = "media_horizontal_column_cnt" +const val MEDIA_LANDSCAPE_HORIZONTAL_COLUMN_CNT = "media_landscape_horizontal_column_cnt" +const val SHOW_ALL = "show_all" // display images and videos from all folders together +const val HIDE_FOLDER_TOOLTIP_SHOWN = "hide_folder_tooltip_shown" +const val EXCLUDED_FOLDERS = "excluded_folders" +const val INCLUDED_FOLDERS = "included_folders" +const val ALBUM_COVERS = "album_covers" +const val HIDE_SYSTEM_UI = "hide_system_ui" +const val DELETE_EMPTY_FOLDERS = "delete_empty_folders" +const val ALLOW_PHOTO_GESTURES = "allow_photo_gestures" +const val ALLOW_VIDEO_GESTURES = "allow_video_gestures" +const val TEMP_FOLDER_PATH = "temp_folder_path" +const val VIEW_TYPE_FOLDERS = "view_type_folders" +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 ALLOW_INSTANT_CHANGE = "allow_instant_change" +const val WAS_NEW_APP_SHOWN = "was_new_app_shown_clock" +const val LAST_FILEPICKER_PATH = "last_filepicker_path" +const val TEMP_SKIP_DELETE_CONFIRMATION = "temp_skip_delete_confirmation" +const val BOTTOM_ACTIONS = "bottom_actions" +const val LAST_VIDEO_POSITION_PREFIX = "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" +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_2" +const val LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y = "last_editor_crop_other_aspect_ratio_y_2" +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 ALLOW_ROTATING_WITH_GESTURES = "allow_rotating_with_gestures" +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" +const val FILE_LOADING_PRIORITY = "file_loading_priority" +const val SPAM_FOLDERS_CHECKED = "spam_folders_checked" +const val SHOW_THUMBNAIL_FILE_TYPES = "show_thumbnail_file_types" +const val EDITOR_BRUSH_COLOR = "editor_brush_color" +const val EDITOR_BRUSH_HARDNESS = "editor_brush_hardness" +const val EDITOR_BRUSH_SIZE = "editor_brush_size" +const val WERE_FAVORITES_MIGRATED = "were_favorites_migrated" +const val FOLDER_THUMBNAIL_STYLE = "folder_thumbnail_style" +const val FOLDER_MEDIA_COUNT = "folder_media_count" +const val LIMIT_FOLDER_TITLE = "folder_limit_title" +const val THUMBNAIL_SPACING = "thumbnail_spacing" +const val FILE_ROUNDED_CORNERS = "file_rounded_corners" + +// slideshow +const val SLIDESHOW_INTERVAL = "slideshow_interval" +const val SLIDESHOW_INCLUDE_VIDEOS = "slideshow_include_videos" +const val SLIDESHOW_INCLUDE_GIFS = "slideshow_include_gifs" +const val SLIDESHOW_RANDOM_ORDER = "slideshow_random_order" +const val SLIDESHOW_MOVE_BACKWARDS = "slideshow_move_backwards" +const val SLIDESHOW_ANIMATION = "slideshow_animation" +const val SLIDESHOW_LOOP = "loop_slideshow" +const val SLIDESHOW_DEFAULT_INTERVAL = 5 +const val SLIDESHOW_SLIDE_DURATION = 500L +const val SLIDESHOW_FADE_DURATION = 1500L +const val SLIDESHOW_START_ON_ENTER = "slideshow_start_on_enter" + +// slideshow animations +const val SLIDESHOW_ANIMATION_NONE = 0 +const val SLIDESHOW_ANIMATION_SLIDE = 1 +const val SLIDESHOW_ANIMATION_FADE = 2 + +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 MAX_PRINT_SIDE_SIZE = 4096 +const val FAST_FORWARD_VIDEO_MS = 10000 + +const val DIRECTORY = "directory" +const val MEDIUM = "medium" +const val PATH = "path" +const val GET_IMAGE_INTENT = "get_image_intent" +const val GET_VIDEO_INTENT = "get_video_intent" +const val GET_ANY_INTENT = "get_any_intent" +const val SET_WALLPAPER_INTENT = "set_wallpaper_intent" +const val IS_VIEW_INTENT = "is_view_intent" +const val PICKED_PATHS = "picked_paths" +const val SHOULD_INIT_FRAGMENT = "should_init_fragment" +const val PORTRAIT_PATH = "portrait_path" +const val SKIP_AUTHENTICATION = "skip_authentication" + +// rotations +const val ROTATE_BY_SYSTEM_SETTING = 0 +const val ROTATE_BY_DEVICE_ROTATION = 1 +const val ROTATE_BY_ASPECT_RATIO = 2 + +// file loading priority +const val PRIORITY_SPEED = 0 +const val PRIORITY_COMPROMISE = 1 +const val PRIORITY_VALIDITY = 2 + +// extended details values +const val EXT_NAME = 1 +const val EXT_PATH = 2 +const val EXT_SIZE = 4 +const val EXT_RESOLUTION = 8 +const val EXT_LAST_MODIFIED = 16 +const val EXT_DATE_TAKEN = 32 +const val EXT_CAMERA_MODEL = 64 +const val EXT_EXIF_PROPERTIES = 128 +const val EXT_DURATION = 256 +const val EXT_ARTIST = 512 +const val EXT_ALBUM = 1024 +const val EXT_GPS = 2048 + +// media types +const val TYPE_IMAGES = 1 +const val TYPE_VIDEOS = 2 +const val TYPE_GIFS = 4 +const val TYPE_RAWS = 8 +const val TYPE_SVGS = 16 +const val TYPE_PORTRAITS = 32 + +fun getDefaultFileFilter() = TYPE_IMAGES or TYPE_VIDEOS or TYPE_GIFS or TYPE_RAWS or TYPE_SVGS + +const val LOCATION_INTERNAL = 1 +const val LOCATION_SD = 2 +const val LOCATION_OTG = 3 + +const val GROUP_BY_NONE = 1 +const val GROUP_BY_LAST_MODIFIED_DAILY = 2 +const val GROUP_BY_DATE_TAKEN_DAILY = 4 +const val GROUP_BY_FILE_TYPE = 8 +const val GROUP_BY_EXTENSION = 16 +const val GROUP_BY_FOLDER = 32 +const val GROUP_BY_LAST_MODIFIED_MONTHLY = 64 +const val GROUP_BY_DATE_TAKEN_MONTHLY = 128 +const val GROUP_DESCENDING = 1024 + +// bottom actions +const val BOTTOM_ACTION_TOGGLE_FAVORITE = 1 +const val BOTTOM_ACTION_EDIT = 2 +const val BOTTOM_ACTION_SHARE = 4 +const val BOTTOM_ACTION_DELETE = 8 +const val BOTTOM_ACTION_ROTATE = 16 +const val BOTTOM_ACTION_PROPERTIES = 32 +const val BOTTOM_ACTION_CHANGE_ORIENTATION = 64 +const val BOTTOM_ACTION_SLIDESHOW = 128 +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 BOTTOM_ACTION_MOVE = 8192 +const val BOTTOM_ACTION_RESIZE = 16384 + +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 + +// constants related to image quality +const val LOW_TILE_DPI = 160 +const val NORMAL_TILE_DPI = 220 +const val WEIRD_TILE_DPI = 240 +const val HIGH_TILE_DPI = 280 + +const val ROUNDED_CORNERS_NONE = 1 +const val ROUNDED_CORNERS_SMALL = 2 +const val ROUNDED_CORNERS_BIG = 3 + +const val FOLDER_MEDIA_CNT_LINE = 1 +const val FOLDER_MEDIA_CNT_BRACKETS = 2 +const val FOLDER_MEDIA_CNT_NONE = 3 + +const val FOLDER_STYLE_SQUARE = 1 +const val FOLDER_STYLE_ROUNDED_CORNERS = 2 diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/DefaultPageTransformer.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/DefaultPageTransformer.kt new file mode 100644 index 000000000..95981ba4a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/DefaultPageTransformer.kt @@ -0,0 +1,8 @@ +package com.simplemobiletools.gallery.pro.helpers + +import android.view.View +import androidx.viewpager.widget.ViewPager + +class DefaultPageTransformer : ViewPager.PageTransformer { + override fun transformPage(view: View, position: Float) {} +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/FadePageTransformer.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/FadePageTransformer.kt new file mode 100644 index 000000000..3ed113b40 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/FadePageTransformer.kt @@ -0,0 +1,18 @@ +package com.simplemobiletools.gallery.pro.helpers + +import android.view.View +import androidx.viewpager.widget.ViewPager + +class FadePageTransformer : ViewPager.PageTransformer { + override fun transformPage(view: View, position: Float) { + view.translationX = view.width * -position + + view.alpha = if (position <= -1f || position >= 1f) { + 0f + } else if (position == 0f) { + 1f + } else { + 1f - Math.abs(position) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/FilterThumbnailsManager.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/FilterThumbnailsManager.kt new file mode 100644 index 000000000..ae043ddde --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/FilterThumbnailsManager.kt @@ -0,0 +1,27 @@ +package com.simplemobiletools.gallery.pro.helpers + +import android.graphics.Bitmap +import com.simplemobiletools.gallery.pro.models.FilterItem +import java.util.* + +class FilterThumbnailsManager { + private var filterThumbnails = ArrayList(10) + private var processedThumbnails = ArrayList(10) + + fun addThumb(filterItem: FilterItem) { + filterThumbnails.add(filterItem) + } + + fun processThumbs(): ArrayList { + for (filterItem in filterThumbnails) { + filterItem.bitmap = filterItem.filter.processFilter(Bitmap.createBitmap(filterItem.bitmap)) + processedThumbnails.add(filterItem) + } + return processedThumbnails + } + + fun clearThumbs() { + filterThumbnails = ArrayList() + processedThumbnails = ArrayList() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/GridSpacingItemDecoration.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/GridSpacingItemDecoration.kt new file mode 100644 index 000000000..7b082fe78 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/GridSpacingItemDecoration.kt @@ -0,0 +1,60 @@ +package com.simplemobiletools.gallery.pro.helpers + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.simplemobiletools.gallery.pro.models.Medium +import com.simplemobiletools.gallery.pro.models.ThumbnailItem + +class GridSpacingItemDecoration(val spanCount: Int, val spacing: Int, val isScrollingHorizontally: Boolean, val addSideSpacing: Boolean, + var items: ArrayList, val useGridPosition: Boolean) : RecyclerView.ItemDecoration() { + + override fun toString() = "spanCount: $spanCount, spacing: $spacing, isScrollingHorizontally: $isScrollingHorizontally, addSideSpacing: $addSideSpacing, " + + "items: ${items.hashCode()}, useGridPosition: $useGridPosition" + + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + if (spacing <= 1) { + return + } + + val position = parent.getChildAdapterPosition(view) + val medium = items.getOrNull(position) as? Medium ?: return + val gridPositionToUse = if (useGridPosition) medium.gridPosition else position + val column = gridPositionToUse % spanCount + + if (isScrollingHorizontally) { + if (addSideSpacing) { + outRect.top = spacing - column * spacing / spanCount + outRect.bottom = (column + 1) * spacing / spanCount + outRect.right = spacing + + if (position < spanCount) { + outRect.left = spacing + } + } else { + outRect.top = column * spacing / spanCount + outRect.bottom = spacing - (column + 1) * spacing / spanCount + if (position >= spanCount) { + outRect.left = spacing + } + } + } else { + if (addSideSpacing) { + outRect.left = spacing - column * spacing / spanCount + outRect.right = (column + 1) * spacing / spanCount + outRect.bottom = spacing + + if (position < spanCount && !useGridPosition) { + outRect.top = spacing + } + } else { + outRect.left = column * spacing / spanCount + outRect.right = spacing - (column + 1) * spacing / spanCount + + if (gridPositionToUse >= spanCount) { + outRect.top = spacing + } + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/IsoTypeReader.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/IsoTypeReader.kt new file mode 100644 index 000000000..d8c33e72d --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/IsoTypeReader.kt @@ -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 + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MediaFetcher.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MediaFetcher.kt new file mode 100644 index 000000000..25d2ffb60 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MediaFetcher.kt @@ -0,0 +1,728 @@ +package com.simplemobiletools.gallery.pro.helpers + +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.os.Environment +import android.provider.BaseColumns +import android.provider.MediaStore.Files +import android.provider.MediaStore.Images +import android.text.format.DateFormat +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.* +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, getProperLastModified: Boolean, + getProperFileSize: Boolean, favoritePaths: ArrayList, getVideoDurations: Boolean, + lastModifieds: HashMap, dateTakens: HashMap): ArrayList { + val filterMedia = context.config.filterMedia + if (filterMedia == 0) { + return ArrayList() + } + + val curMedia = ArrayList() + if (context.isPathOnOTG(curPath)) { + if (context.hasOTGConnected()) { + val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia, favoritePaths, getVideoDurations) + curMedia.addAll(newMedia) + } + } else { + val newMedia = getMediaInFolder(curPath, isPickImage, isPickVideo, filterMedia, getProperDateTaken, getProperLastModified, getProperFileSize, + favoritePaths, getVideoDurations, lastModifieds, dateTakens) + curMedia.addAll(newMedia) + } + + sortMedia(curMedia, context.config.getFolderSorting(curPath)) + + return curMedia + } + + fun getFoldersToScan(): ArrayList { + return try { + val OTGPath = context.config.OTGPath + val folders = getLatestFileFolders() + folders.addAll(arrayListOf( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString(), + "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)}/Camera", + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() + ).filter { context.getDoesFilePathExist(it, OTGPath) }) + + val filterMedia = context.config.filterMedia + val uri = Files.getContentUri("external") + val projection = arrayOf(Images.Media.DATA) + val selection = getSelectionQuery(filterMedia) + val selectionArgs = getSelectionArgsQuery(filterMedia).toTypedArray() + val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) + folders.addAll(parseCursor(cursor!!)) + + val config = context.config + val shouldShowHidden = config.shouldShowHidden + val excludedPaths = config.excludedFolders + val includedPaths = config.includedFolders + + val folderNoMediaStatuses = HashMap() + val distinctPathsMap = HashMap() + val distinctPaths = folders.distinctBy { + when { + distinctPathsMap.containsKey(it) -> distinctPathsMap[it] + else -> { + val distinct = it.getDistinctPath() + distinctPathsMap[it.getParentPath()] = distinct.getParentPath() + distinct + } + } + } + + val noMediaFolders = context.getNoMediaFoldersSync() + noMediaFolders.forEach { folder -> + folderNoMediaStatuses["$folder/$NOMEDIA"] = true + } + + distinctPaths.filter { + it.shouldFolderBeVisible(excludedPaths, includedPaths, shouldShowHidden, folderNoMediaStatuses) { path, hasNoMedia -> + folderNoMediaStatuses[path] = hasNoMedia + } + }.toMutableList() as ArrayList + } catch (e: Exception) { + ArrayList() + } + } + + private fun getLatestFileFolders(): LinkedHashSet { + val uri = Files.getContentUri("external") + val projection = arrayOf(Images.ImageColumns.DATA) + val parents = LinkedHashSet() + val sorting = "${BaseColumns._ID} DESC LIMIT 50" + var cursor: Cursor? = null + try { + cursor = context.contentResolver.query(uri, projection, null, null, sorting) + if (cursor?.moveToFirst() == true) { + do { + val path = cursor.getStringValue(Images.ImageColumns.DATA) ?: continue + parents.add(path.getParentPath()) + } while (cursor.moveToNext()) + } + } catch (e: Exception) { + context.showErrorToast(e) + } finally { + cursor?.close() + } + + return parents + } + + private fun getSelectionQuery(filterMedia: Int): String { + val query = StringBuilder() + if (filterMedia and TYPE_IMAGES != 0) { + photoExtensions.forEach { + query.append("${Images.Media.DATA} LIKE ? OR ") + } + } + + if (filterMedia and TYPE_PORTRAITS != 0) { + query.append("${Images.Media.DATA} LIKE ? OR ") + query.append("${Images.Media.DATA} LIKE ? OR ") + } + + if (filterMedia and TYPE_VIDEOS != 0) { + videoExtensions.forEach { + query.append("${Images.Media.DATA} LIKE ? OR ") + } + } + + if (filterMedia and TYPE_GIFS != 0) { + query.append("${Images.Media.DATA} LIKE ? OR ") + } + + if (filterMedia and TYPE_RAWS != 0) { + rawExtensions.forEach { + query.append("${Images.Media.DATA} LIKE ? OR ") + } + } + + if (filterMedia and TYPE_SVGS != 0) { + query.append("${Images.Media.DATA} LIKE ? OR ") + } + + return query.toString().trim().removeSuffix("OR") + } + + private fun getSelectionArgsQuery(filterMedia: Int): ArrayList { + val args = ArrayList() + if (filterMedia and TYPE_IMAGES != 0) { + photoExtensions.forEach { + args.add("%$it") + } + } + + if (filterMedia and TYPE_PORTRAITS != 0) { + args.add("%.jpg") + args.add("%.jpeg") + } + + if (filterMedia and TYPE_VIDEOS != 0) { + videoExtensions.forEach { + args.add("%$it") + } + } + + if (filterMedia and TYPE_GIFS != 0) { + args.add("%.gif") + } + + if (filterMedia and TYPE_RAWS != 0) { + rawExtensions.forEach { + args.add("%$it") + } + } + + if (filterMedia and TYPE_SVGS != 0) { + args.add("%.svg") + } + + return args + } + + private fun parseCursor(cursor: Cursor): LinkedHashSet { + val foldersToIgnore = arrayListOf("/storage/emulated/legacy") + val config = context.config + val includedFolders = config.includedFolders + val OTGPath = config.OTGPath + val foldersToScan = config.everShownFolders.filter { it == FAVORITES || it == RECYCLE_BIN || context.getDoesFilePathExist(it, OTGPath) }.toHashSet() + + cursor.use { + if (cursor.moveToFirst()) { + do { + val path = cursor.getStringValue(Images.Media.DATA) + val parentPath = File(path).parent ?: continue + if (!includedFolders.contains(parentPath) && !foldersToIgnore.contains(parentPath)) { + foldersToScan.add(parentPath) + } + } while (cursor.moveToNext()) + } + } + + includedFolders.forEach { + addFolder(foldersToScan, it) + } + + return foldersToScan.toMutableSet() as LinkedHashSet + } + + private fun addFolder(curFolders: HashSet, folder: String) { + curFolders.add(folder) + val files = File(folder).listFiles() ?: return + for (file in files) { + if (file.isDirectory) { + addFolder(curFolders, file.absolutePath) + } + } + } + + private fun getMediaInFolder(folder: String, isPickImage: Boolean, isPickVideo: Boolean, filterMedia: Int, getProperDateTaken: Boolean, + getProperLastModified: Boolean, getProperFileSize: Boolean, favoritePaths: ArrayList, + getVideoDurations: Boolean, lastModifieds: HashMap, dateTakens: HashMap): ArrayList { + val media = ArrayList() + val isRecycleBin = folder == RECYCLE_BIN + val deletedMedia = if (isRecycleBin) { + context.getUpdatedDeletedMedia() + } else { + ArrayList() + } + + val config = context.config + val checkProperFileSize = getProperFileSize || config.fileLoadingPriority == PRIORITY_COMPROMISE + val checkFileExistence = config.fileLoadingPriority == PRIORITY_VALIDITY + val showHidden = config.shouldShowHidden + val showPortraits = filterMedia and TYPE_PORTRAITS != 0 + val fileSizes = if (checkProperFileSize || checkFileExistence) getFolderSizes(folder) else HashMap() + + val files = when (folder) { + FAVORITES -> favoritePaths.filter { showHidden || !it.contains("/.") }.map { File(it) }.toMutableList() as ArrayList + RECYCLE_BIN -> deletedMedia.map { File(it.path) }.toMutableList() as ArrayList + else -> File(folder).listFiles()?.toMutableList() ?: return media + } + + for (curFile in files) { + var file = curFile + if (shouldStop) { + break + } + + var path = file.absolutePath + var isPortrait = false + 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) { + if (showPortraits && file.name.startsWith("img_", true) && file.isDirectory) { + val portraitFiles = file.listFiles() ?: continue + val cover = portraitFiles.firstOrNull { it.name.contains("cover", true) } ?: portraitFiles.firstOrNull() + if (cover != null && !files.contains(cover)) { + file = cover + path = cover.absolutePath + isPortrait = true + } else { + continue + } + } else { + continue + } + } + + if (isVideo && (isPickImage || filterMedia and TYPE_VIDEOS == 0)) + continue + + if (isImage && (isPickVideo || filterMedia and TYPE_IMAGES == 0)) + continue + + if (isGif && filterMedia and TYPE_GIFS == 0) + continue + + if (isRaw && filterMedia and TYPE_RAWS == 0) + continue + + if (isSvg && filterMedia and TYPE_SVGS == 0) + continue + + val filename = file.name + if (!showHidden && filename.startsWith('.')) + continue + + var size = 0L + if (checkProperFileSize || checkFileExistence) { + var newSize = fileSizes.remove(path) + if (newSize == null) { + newSize = file.length() + } + size = newSize + } + + if ((checkProperFileSize || checkFileExistence) && size <= 0L) { + continue + } + + if (checkFileExistence && (!file.exists() || !file.isFile)) { + continue + } + + if (isRecycleBin) { + deletedMedia.firstOrNull { it.path == path }?.apply { + media.add(this) + } + } else { + var lastModified: Long + var newLastModified = lastModifieds.remove(path) + if (newLastModified == null) { + newLastModified = if (getProperLastModified) { + file.lastModified() + } else { + 0L + } + } + lastModified = newLastModified + + var dateTaken = lastModified + val videoDuration = if (getVideoDurations && isVideo) context.getDuration(path) ?: 0 else 0 + + if (getProperDateTaken) { + var newDateTaken = dateTakens.remove(path) + if (newDateTaken == null) { + newDateTaken = if (getProperLastModified) { + lastModified + } else { + file.lastModified() + } + } + dateTaken = newDateTaken + } + + val type = when { + isVideo -> TYPE_VIDEOS + isGif -> TYPE_GIFS + isRaw -> TYPE_RAWS + isSvg -> TYPE_SVGS + isPortrait -> TYPE_PORTRAITS + else -> TYPE_IMAGES + } + + val isFavorite = favoritePaths.contains(path) + 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, + getVideoDurations: Boolean): ArrayList { + val media = ArrayList() + val files = context.getDocumentFile(folder)?.listFiles() ?: return media + val checkFileExistence = context.config.fileLoadingPriority == PRIORITY_VALIDITY + val showHidden = context.config.shouldShowHidden + val OTGPath = context.config.OTGPath + + for (file in files) { + if (shouldStop) { + break + } + + val filename = file.name ?: continue + val isImage = filename.isImageFast() + val isVideo = if (isImage) false else filename.isVideoFast() + val isGif = if (isImage || isVideo) false else filename.isGif() + val isRaw = if (isImage || isVideo || isGif) false else filename.isRawFast() + val isSvg = if (isImage || isVideo || isGif || isRaw) false else filename.isSvg() + + if (!isImage && !isVideo && !isGif && !isRaw && !isSvg) + continue + + if (isVideo && (isPickImage || filterMedia and TYPE_VIDEOS == 0)) + continue + + if (isImage && (isPickVideo || filterMedia and TYPE_IMAGES == 0)) + continue + + if (isGif && filterMedia and TYPE_GIFS == 0) + continue + + if (isRaw && filterMedia and TYPE_RAWS == 0) + continue + + if (isSvg && filterMedia and TYPE_SVGS == 0) + continue + + if (!showHidden && filename.startsWith('.')) + continue + + val size = file.length() + if (size <= 0L || (checkFileExistence && !context.getDoesFilePathExist(file.uri.toString(), OTGPath))) + continue + + val dateTaken = file.lastModified() + val dateModified = file.lastModified() + + val type = when { + isVideo -> TYPE_VIDEOS + isGif -> TYPE_GIFS + isRaw -> TYPE_RAWS + isSvg -> TYPE_SVGS + else -> TYPE_IMAGES + } + + val path = Uri.decode(file.uri.toString().replaceFirst("${context.config.OTGTreeUri}/document/${context.config.OTGPartition}%3A", "${context.config.OTGPath}/")) + val videoDuration = if (getVideoDurations) context.getDuration(path) ?: 0 else 0 + val isFavorite = favoritePaths.contains(path) + val medium = Medium(null, filename, path, folder, dateModified, dateTaken, size, type, videoDuration, isFavorite, 0L) + media.add(medium) + } + + return media + } + + fun getFolderDateTakens(folder: String): HashMap { + val dateTakens = HashMap() + if (folder != FAVORITES) { + val projection = arrayOf( + Images.Media.DISPLAY_NAME, + Images.Media.DATE_TAKEN + ) + + val uri = Files.getContentUri("external") + val selection = "${Images.Media.DATA} LIKE ? AND ${Images.Media.DATA} NOT LIKE ?" + val selectionArgs = arrayOf("$folder/%", "$folder/%/%") + + context.queryCursor(uri, projection, selection, selectionArgs) { cursor -> + try { + val dateTaken = cursor.getLongValue(Images.Media.DATE_TAKEN) + if (dateTaken != 0L) { + val name = cursor.getStringValue(Images.Media.DISPLAY_NAME) + dateTakens["$folder/$name"] = dateTaken + } + } catch (e: Exception) { + } + } + } + + val dateTakenValues = try { + if (folder == FAVORITES) { + context.dateTakensDB.getAllDateTakens() + } else { + context.dateTakensDB.getDateTakensFromPath(folder) + } + } catch (e: Exception) { + return dateTakens + } + + dateTakenValues.forEach { + dateTakens[it.fullPath] = it.taken + } + + return dateTakens + } + + fun getDateTakens(): HashMap { + val dateTakens = HashMap() + val projection = arrayOf( + Images.Media.DATA, + Images.Media.DATE_TAKEN + ) + + val uri = Files.getContentUri("external") + + try { + context.queryCursor(uri, projection) { cursor -> + try { + val dateTaken = cursor.getLongValue(Images.Media.DATE_TAKEN) + if (dateTaken != 0L) { + val path = cursor.getStringValue(Images.Media.DATA) + dateTakens[path] = dateTaken + } + } catch (e: Exception) { + } + } + + val dateTakenValues = context.dateTakensDB.getAllDateTakens() + + dateTakenValues.forEach { + dateTakens[it.fullPath] = it.taken + } + } catch (e: Exception) { + } + + return dateTakens + } + + fun getFolderLastModifieds(folder: String): HashMap { + val lastModifieds = HashMap() + if (folder != FAVORITES) { + val projection = arrayOf( + Images.Media.DISPLAY_NAME, + Images.Media.DATE_MODIFIED + ) + + val uri = Files.getContentUri("external") + val selection = "${Images.Media.DATA} LIKE ? AND ${Images.Media.DATA} NOT LIKE ?" + val selectionArgs = arrayOf("$folder/%", "$folder/%/%") + + context.queryCursor(uri, projection, selection, selectionArgs) { cursor -> + try { + val lastModified = cursor.getLongValue(Images.Media.DATE_MODIFIED) * 1000 + if (lastModified != 0L) { + val name = cursor.getStringValue(Images.Media.DISPLAY_NAME) + lastModifieds["$folder/$name"] = lastModified + } + } catch (e: Exception) { + } + } + } + + return lastModifieds + } + + fun getLastModifieds(): HashMap { + val lastModifieds = HashMap() + val projection = arrayOf( + Images.Media.DATA, + Images.Media.DATE_MODIFIED + ) + + val uri = Files.getContentUri("external") + + try { + context.queryCursor(uri, projection) { cursor -> + try { + val lastModified = cursor.getLongValue(Images.Media.DATE_MODIFIED) * 1000 + if (lastModified != 0L) { + val path = cursor.getStringValue(Images.Media.DATA) + lastModifieds[path] = lastModified + } + } catch (e: Exception) { + } + } + } catch (e: Exception) { + } + + return lastModifieds + } + + private fun getFolderSizes(folder: String): HashMap { + val sizes = HashMap() + if (folder != FAVORITES) { + val projection = arrayOf( + Images.Media.DISPLAY_NAME, + Images.Media.SIZE + ) + + val uri = Files.getContentUri("external") + val selection = "${Images.Media.DATA} LIKE ? AND ${Images.Media.DATA} NOT LIKE ?" + val selectionArgs = arrayOf("$folder/%", "$folder/%/%") + + context.queryCursor(uri, projection, selection, selectionArgs) { cursor -> + try { + val size = cursor.getLongValue(Images.Media.SIZE) + if (size != 0L) { + val name = cursor.getStringValue(Images.Media.DISPLAY_NAME) + sizes["$folder/$name"] = size + } + } catch (e: Exception) { + } + } + } + + return sizes + } + + fun sortMedia(media: ArrayList, sorting: Int) { + if (sorting and SORT_BY_RANDOM != 0) { + media.shuffle() + return + } + + media.sortWith { o1, o2 -> + o1 as Medium + o2 as Medium + var result = when { + sorting and SORT_BY_NAME != 0 -> { + if (sorting and SORT_USE_NUMERIC_VALUE != 0) { + AlphanumericComparator().compare(o1.name.toLowerCase(), o2.name.toLowerCase()) + } else { + o1.name.toLowerCase().compareTo(o2.name.toLowerCase()) + } + } + sorting and SORT_BY_PATH != 0 -> { + if (sorting and SORT_USE_NUMERIC_VALUE != 0) { + AlphanumericComparator().compare(o1.path.toLowerCase(), o2.path.toLowerCase()) + } else { + o1.path.toLowerCase().compareTo(o2.path.toLowerCase()) + } + } + sorting and SORT_BY_SIZE != 0 -> o1.size.compareTo(o2.size) + sorting and SORT_BY_DATE_MODIFIED != 0 -> o1.modified.compareTo(o2.modified) + else -> o1.taken.compareTo(o2.taken) + } + + if (sorting and SORT_DESCENDING != 0) { + result *= -1 + } + result + } + } + + fun groupMedia(media: ArrayList, path: String): ArrayList { + val pathToCheck = if (path.isEmpty()) SHOW_ALL else path + val currentGrouping = context.config.getFolderGrouping(pathToCheck) + if (currentGrouping and GROUP_BY_NONE != 0) { + return media as ArrayList + } + + val thumbnailItems = ArrayList() + if (context.config.scrollHorizontally) { + media.mapTo(thumbnailItems) { it } + return thumbnailItems + } + + val mediumGroups = LinkedHashMap>() + media.forEach { + val key = it.getGroupingKey(currentGrouping) + if (!mediumGroups.containsKey(key)) { + mediumGroups[key] = ArrayList() + } + mediumGroups[key]!!.add(it) + } + + val sortDescending = currentGrouping and GROUP_DESCENDING != 0 + val sorted = if (currentGrouping and GROUP_BY_LAST_MODIFIED_DAILY != 0 || currentGrouping and GROUP_BY_LAST_MODIFIED_MONTHLY != 0 || + currentGrouping and GROUP_BY_DATE_TAKEN_DAILY != 0 || currentGrouping and GROUP_BY_DATE_TAKEN_MONTHLY != 0) { + mediumGroups.toSortedMap(if (sortDescending) compareByDescending { + it.toLongOrNull() ?: 0L + } else { + compareBy { it.toLongOrNull() ?: 0L } + }) + } else { + mediumGroups.toSortedMap(if (sortDescending) compareByDescending { it } else compareBy { it }) + } + + mediumGroups.clear() + for ((key, value) in sorted) { + mediumGroups[key] = value + } + + val today = formatDate(System.currentTimeMillis().toString(), true) + val yesterday = formatDate((System.currentTimeMillis() - DAY_SECONDS * 1000).toString(), true) + for ((key, value) in mediumGroups) { + var currentGridPosition = 0 + val sectionKey = getFormattedKey(key, currentGrouping, today, yesterday) + thumbnailItems.add(ThumbnailSection(sectionKey)) + + value.forEach { + it.gridPosition = currentGridPosition++ + } + + thumbnailItems.addAll(value) + } + + return thumbnailItems + } + + private fun getFormattedKey(key: String, grouping: Int, today: String, yesterday: String): String { + var result = when { + grouping and GROUP_BY_LAST_MODIFIED_DAILY != 0 || grouping and GROUP_BY_DATE_TAKEN_DAILY != 0 -> getFinalDate(formatDate(key, true), today, yesterday) + grouping and GROUP_BY_LAST_MODIFIED_MONTHLY != 0 || grouping and GROUP_BY_DATE_TAKEN_MONTHLY != 0 -> formatDate(key, false) + grouping and GROUP_BY_FILE_TYPE != 0 -> getFileTypeString(key) + grouping and GROUP_BY_EXTENSION != 0 -> key.toUpperCase() + grouping and GROUP_BY_FOLDER != 0 -> context.humanizePath(key) + else -> key + } + + if (result.isEmpty()) { + result = context.getString(R.string.unknown) + } + + return result + } + + private fun getFinalDate(date: String, today: String, yesterday: String): String { + return when (date) { + today -> context.getString(R.string.today) + yesterday -> context.getString(R.string.yesterday) + else -> date + } + } + + private fun formatDate(timestamp: String, showDay: Boolean): String { + return if (timestamp.areDigitsOnly()) { + val cal = Calendar.getInstance(Locale.ENGLISH) + cal.timeInMillis = timestamp.toLong() + val format = if (showDay) context.config.dateFormat else "MMMM yyyy" + DateFormat.format(format, cal).toString() + } else { + "" + } + } + + private fun getFileTypeString(key: String): String { + val stringId = when (key.toInt()) { + TYPE_IMAGES -> R.string.images + TYPE_VIDEOS -> R.string.videos + TYPE_GIFS -> R.string.gifs + TYPE_RAWS -> R.string.raw_images + TYPE_SVGS -> R.string.svgs + else -> R.string.portraits + } + return context.getString(stringId) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MyGlideImageDecoder.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MyGlideImageDecoder.kt new file mode 100644 index 000000000..0287a3b39 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MyGlideImageDecoder.kt @@ -0,0 +1,30 @@ +package com.simplemobiletools.gallery.pro.helpers + +import android.content.Context +import android.graphics.Bitmap +import android.net.Uri +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DecodeFormat +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.Target +import com.bumptech.glide.signature.ObjectKey +import com.davemorrissey.labs.subscaleview.ImageDecoder + +class MyGlideImageDecoder(val degrees: Int, val signature: ObjectKey) : ImageDecoder { + + override fun decode(context: Context, uri: Uri): Bitmap { + val options = RequestOptions() + .format(DecodeFormat.PREFER_ARGB_8888) + .signature(signature) + .fitCenter() + + val builder = Glide.with(context) + .asBitmap() + .load(uri.toString().substringAfter("file://")) + .apply(options) + .transform(RotateTransformation(-degrees)) + .into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + + return builder.get() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MyWidgetProvider.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MyWidgetProvider.kt new file mode 100644 index 000000000..f2e1b3d02 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MyWidgetProvider.kt @@ -0,0 +1,99 @@ +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.getFileSignature +import com.simplemobiletools.commons.extensions.setBackgroundColor +import com.simplemobiletools.commons.extensions.setText +import com.simplemobiletools.commons.extensions.setVisibleIf +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.gallery.pro.R +import com.simplemobiletools.gallery.pro.activities.MediaActivity +import com.simplemobiletools.gallery.pro.extensions.config +import com.simplemobiletools.gallery.pro.extensions.directoryDao +import com.simplemobiletools.gallery.pro.extensions.getFolderNameFromPath +import com.simplemobiletools.gallery.pro.extensions.widgetsDB +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) + ensureBackgroundThread { + 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.directoryDao.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) + + try { + appWidgetManager.updateAppWidget(it.widgetId, views) + } catch (ignored: Exception) { + } + } + } + } + + 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) + ensureBackgroundThread { + appWidgetIds.forEach { + context.widgetsDB.deleteWidgetId(it) + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/PicassoRegionDecoder.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/PicassoRegionDecoder.kt new file mode 100644 index 000000000..2c98321e9 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/PicassoRegionDecoder.kt @@ -0,0 +1,44 @@ +package com.simplemobiletools.gallery.pro.helpers + +import android.content.Context +import android.graphics.* +import android.net.Uri +import com.davemorrissey.labs.subscaleview.ImageRegionDecoder + +class PicassoRegionDecoder(val showHighestQuality: Boolean, val screenWidth: Int, val screenHeight: Int, val minTileDpi: Int) : ImageRegionDecoder { + private var decoder: BitmapRegionDecoder? = null + private val decoderLock = Any() + + override fun init(context: Context, uri: Uri): Point { + 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) + } + + override fun decodeRegion(rect: Rect, sampleSize: Int): Bitmap { + synchronized(decoderLock) { + var newSampleSize = sampleSize + if (!showHighestQuality && minTileDpi == LOW_TILE_DPI) { + if ((rect.width() > rect.height() && screenWidth > screenHeight) || (rect.height() > rect.width() && screenHeight > screenWidth)) { + if ((rect.width() / sampleSize > screenWidth || rect.height() / sampleSize > screenHeight)) { + newSampleSize *= 2 + } + } + } + + val options = BitmapFactory.Options() + options.inSampleSize = newSampleSize + options.inPreferredConfig = if (showHighestQuality) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565 + val bitmap = decoder!!.decodeRegion(rect, options) + + return bitmap ?: throw RuntimeException("Region decoder returned null bitmap - image format may not be supported") + } + } + + override fun isReady() = decoder != null && !decoder!!.isRecycled + + override fun recycle() { + decoder!!.recycle() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/RotateTransformation.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/RotateTransformation.kt new file mode 100644 index 000000000..9fb0fd127 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/RotateTransformation.kt @@ -0,0 +1,17 @@ +package com.simplemobiletools.gallery.pro.helpers + +import android.graphics.Bitmap +import android.graphics.Matrix +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import java.security.MessageDigest + +class RotateTransformation(var degrees: Int) : BitmapTransformation() { + override fun updateDiskCacheKey(messageDigest: MessageDigest) {} + + override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap { + val matrix = Matrix() + matrix.postRotate(degrees.toFloat()) + return Bitmap.createBitmap(toTransform, 0, 0, toTransform.width, toTransform.height, matrix, true) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/DateTakensDao.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/DateTakensDao.kt new file mode 100644 index 000000000..5041727e9 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/DateTakensDao.kt @@ -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.DateTaken + +@Dao +interface DateTakensDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertAll(dateTakens: List) + + @Query("SELECT full_path, filename, parent_path, date_taken, last_fixed, last_modified FROM date_takens WHERE parent_path = :path COLLATE NOCASE") + fun getDateTakensFromPath(path: String): List + + @Query("SELECT full_path, filename, parent_path, date_taken, last_fixed, last_modified FROM date_takens") + fun getAllDateTakens(): List +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/DirectoryDao.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/DirectoryDao.kt new file mode 100644 index 000000000..75acd43c4 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/DirectoryDao.kt @@ -0,0 +1,35 @@ +package com.simplemobiletools.gallery.pro.interfaces + +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 { + @Query("SELECT path, thumbnail, filename, media_count, last_modified, date_taken, size, location, media_types, sort_value FROM directories") + fun getAll(): List + + @Insert(onConflict = REPLACE) + fun insert(directory: Directory) + + @Insert(onConflict = REPLACE) + fun insertAll(directories: List) + + @Query("DELETE FROM directories WHERE path = :path COLLATE NOCASE") + fun deleteDirPath(path: String) + + @Query("UPDATE OR REPLACE directories SET thumbnail = :thumbnail, media_count = :mediaCnt, last_modified = :lastModified, date_taken = :dateTaken, size = :size, media_types = :mediaTypes, sort_value = :sortValue WHERE path = :path COLLATE NOCASE") + fun updateDirectory(path: String, thumbnail: String, mediaCnt: Int, lastModified: Long, dateTaken: Long, size: Long, mediaTypes: Int, sortValue: String) + + @Query("UPDATE directories SET thumbnail = :thumbnail, filename = :name, path = :newPath WHERE path = :oldPath COLLATE NOCASE") + fun updateDirectoryAfterRename(thumbnail: String, name: String, newPath: String, oldPath: String) + + @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? +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/DirectoryOperationsListener.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/DirectoryOperationsListener.kt new file mode 100644 index 000000000..b4f265969 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/DirectoryOperationsListener.kt @@ -0,0 +1,14 @@ +package com.simplemobiletools.gallery.pro.interfaces + +import com.simplemobiletools.gallery.pro.models.Directory +import java.io.File + +interface DirectoryOperationsListener { + fun refreshItems() + + fun deleteFolders(folders: ArrayList) + + fun recheckPinnedFolders() + + fun updateDirectories(directories: ArrayList) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/FavoritesDao.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/FavoritesDao.kt new file mode 100644 index 000000000..39a4c4530 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/FavoritesDao.kt @@ -0,0 +1,31 @@ +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.Favorite + +@Dao +interface FavoritesDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(favorite: Favorite) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertAll(favorites: List) + + @Query("SELECT favorites.full_path FROM favorites INNER JOIN media ON favorites.full_path = media.full_path WHERE media.deleted_ts = 0") + fun getValidFavoritePaths(): List + + @Query("SELECT id FROM favorites WHERE full_path = :path COLLATE NOCASE") + fun isFavorite(path: String): Boolean + + @Query("UPDATE OR REPLACE favorites SET filename = :newFilename, full_path = :newFullPath, parent_path = :newParentPath WHERE full_path = :oldPath COLLATE NOCASE") + fun updateFavorite(newFilename: String, newFullPath: String, newParentPath: String, oldPath: String) + + @Query("DELETE FROM favorites WHERE full_path = :path COLLATE NOCASE") + fun deleteFavoritePath(path: String) + + @Query("DELETE FROM favorites") + fun clearFavorites() +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/MediaOperationsListener.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/MediaOperationsListener.kt new file mode 100644 index 000000000..997adc44f --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/MediaOperationsListener.kt @@ -0,0 +1,14 @@ +package com.simplemobiletools.gallery.pro.interfaces + +import com.simplemobiletools.commons.models.FileDirItem +import com.simplemobiletools.gallery.pro.models.ThumbnailItem + +interface MediaOperationsListener { + fun refreshItems() + + fun tryDeleteFiles(fileDirItems: ArrayList) + + fun selectedPaths(paths: ArrayList) + + fun updateMediaGridDecoration(media: ArrayList) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/MediumDao.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/MediumDao.kt new file mode 100644 index 000000000..4affa1995 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/MediumDao.kt @@ -0,0 +1,50 @@ +package com.simplemobiletools.gallery.pro.interfaces + +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, video_duration, is_favorite, deleted_ts FROM media WHERE deleted_ts = 0 AND parent_path = :path COLLATE NOCASE") + fun getMediaFromPath(path: String): List + + @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 + + @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 + + @Insert(onConflict = REPLACE) + fun insert(medium: Medium) + + @Insert(onConflict = REPLACE) + fun insertAll(media: List) + + @Delete + fun deleteMedia(vararg medium: Medium) + + @Query("DELETE FROM media WHERE full_path = :path COLLATE NOCASE") + fun deleteMediumPath(path: String) + + @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") + fun updateMedium(newFilename: String, newFullPath: String, newParentPath: String, oldPath: String) + + @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) + + @Query("UPDATE media SET is_favorite = 0") + fun clearFavorites() + + @Query("DELETE FROM media WHERE deleted_ts != 0") + fun clearRecycleBin() +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/WidgetsDao.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/WidgetsDao.kt new file mode 100644 index 000000000..b6ecb2492 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/interfaces/WidgetsDao.kt @@ -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 + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOrUpdate(widget: Widget): Long + + @Query("DELETE FROM widgets WHERE widget_id = :widgetId") + fun deleteWidgetId(widgetId: Int) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/jobs/NewPhotoFetcher.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/jobs/NewPhotoFetcher.kt new file mode 100644 index 000000000..ad5ada9f7 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/jobs/NewPhotoFetcher.kt @@ -0,0 +1,118 @@ +package com.simplemobiletools.gallery.pro.jobs + +import android.annotation.TargetApi +import android.app.job.JobInfo +import android.app.job.JobInfo.TriggerContentUri +import android.app.job.JobParameters +import android.app.job.JobScheduler +import android.app.job.JobService +import android.content.ComponentName +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.os.Build +import android.os.Handler +import android.provider.MediaStore +import android.provider.MediaStore.Images +import android.provider.MediaStore.Video +import com.simplemobiletools.commons.extensions.getParentPath +import com.simplemobiletools.commons.extensions.getStringValue +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.gallery.pro.extensions.addPathToDB +import com.simplemobiletools.gallery.pro.extensions.updateDirectoryPath + +// based on https://developer.android.com/reference/android/app/job/JobInfo.Builder.html#addTriggerContentUri(android.app.job.JobInfo.TriggerContentUri) +@TargetApi(Build.VERSION_CODES.N) +class NewPhotoFetcher : JobService() { + companion object { + const val PHOTO_VIDEO_CONTENT_JOB = 1 + private val MEDIA_URI = Uri.parse("content://${MediaStore.AUTHORITY}/") + private val PHOTO_PATH_SEGMENTS = Images.Media.EXTERNAL_CONTENT_URI.pathSegments + private val VIDEO_PATH_SEGMENTS = Video.Media.EXTERNAL_CONTENT_URI.pathSegments + } + + private val mHandler = Handler() + private val mWorker = Runnable { + scheduleJob(this@NewPhotoFetcher) + jobFinished(mRunningParams, false) + } + + private var mRunningParams: JobParameters? = null + + fun scheduleJob(context: Context) { + val componentName = ComponentName(context, NewPhotoFetcher::class.java) + val photoUri = Images.Media.EXTERNAL_CONTENT_URI + val videoUri = Video.Media.EXTERNAL_CONTENT_URI + JobInfo.Builder(PHOTO_VIDEO_CONTENT_JOB, componentName).apply { + addTriggerContentUri(TriggerContentUri(photoUri, TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)) + addTriggerContentUri(TriggerContentUri(videoUri, TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)) + addTriggerContentUri(TriggerContentUri(MEDIA_URI, 0)) + + try { + context.getSystemService(JobScheduler::class.java)?.schedule(build()) + } catch (ignored: Exception) { + } + } + } + + fun isScheduled(context: Context): Boolean { + val jobScheduler = context.getSystemService(JobScheduler::class.java) + val jobs = jobScheduler.allPendingJobs + return jobs.any { it.id == PHOTO_VIDEO_CONTENT_JOB } + } + + override fun onStartJob(params: JobParameters): Boolean { + mRunningParams = params + ensureBackgroundThread { + val affectedFolderPaths = HashSet() + if (params.triggeredContentAuthorities != null && params.triggeredContentUris != null) { + val ids = arrayListOf() + for (uri in params.triggeredContentUris!!) { + val path = uri.pathSegments + if (path != null && (path.size == PHOTO_PATH_SEGMENTS.size + 1 || path.size == VIDEO_PATH_SEGMENTS.size + 1)) { + ids.add(path[path.size - 1]) + } + } + + if (ids.isNotEmpty()) { + val selection = StringBuilder() + for (id in ids) { + if (selection.isNotEmpty()) { + selection.append(" OR ") + } + selection.append("${Images.ImageColumns._ID} = '$id'") + } + + var cursor: Cursor? = null + try { + val projection = arrayOf(Images.ImageColumns.DATA) + val uris = arrayListOf(Images.Media.EXTERNAL_CONTENT_URI, Video.Media.EXTERNAL_CONTENT_URI) + uris.forEach { + cursor = contentResolver.query(it, projection, selection.toString(), null, null) + while (cursor!!.moveToNext()) { + val path = cursor!!.getStringValue(Images.ImageColumns.DATA) + affectedFolderPaths.add(path.getParentPath()) + addPathToDB(path) + } + } + } catch (ignored: Exception) { + } finally { + cursor?.close() + } + } + } + + affectedFolderPaths.forEach { + updateDirectoryPath(it) + } + } + + mHandler.post(mWorker) + return true + } + + override fun onStopJob(params: JobParameters): Boolean { + mHandler.removeCallbacks(mWorker) + return false + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/models/AlbumCover.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/AlbumCover.kt similarity index 54% rename from app/src/main/kotlin/com/simplemobiletools/gallery/models/AlbumCover.kt rename to app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/AlbumCover.kt index f5937ad08..c4afd11b1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/models/AlbumCover.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/AlbumCover.kt @@ -1,3 +1,3 @@ -package com.simplemobiletools.gallery.models +package com.simplemobiletools.gallery.pro.models data class AlbumCover(val path: String, val tmb: String) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/DateTaken.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/DateTaken.kt new file mode 100644 index 000000000..98171df7b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/DateTaken.kt @@ -0,0 +1,18 @@ +package com.simplemobiletools.gallery.pro.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +// Date Taken in the MediaStore is unreliable and hard to work with, keep the values in an own database +// It is used at sorting files by date taken, checking EXIF file by file would be way too slow +@Entity(tableName = "date_takens", indices = [Index(value = ["full_path"], unique = true)]) +data class DateTaken( + @PrimaryKey(autoGenerate = true) var id: Int?, + @ColumnInfo(name = "full_path") var fullPath: String, + @ColumnInfo(name = "filename") var filename: String, + @ColumnInfo(name = "parent_path") var parentPath: String, + @ColumnInfo(name = "date_taken") var taken: Long, + @ColumnInfo(name = "last_fixed") var lastFixed: Int, + @ColumnInfo(name = "last_modified") var lastModified: Long) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Directory.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Directory.kt new file mode 100644 index 000000000..5f51940e6 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Directory.kt @@ -0,0 +1,45 @@ +package com.simplemobiletools.gallery.pro.models + +import android.content.Context +import androidx.room.* +import com.bumptech.glide.signature.ObjectKey +import com.simplemobiletools.commons.extensions.formatDate +import com.simplemobiletools.commons.extensions.formatSize +import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.gallery.pro.helpers.RECYCLE_BIN + +@Entity(tableName = "directories", indices = [Index(value = ["path"], unique = true)]) +data class Directory( + @PrimaryKey(autoGenerate = true) var id: Long?, + @ColumnInfo(name = "path") var path: String, + @ColumnInfo(name = "thumbnail") var tmb: String, + @ColumnInfo(name = "filename") var name: String, + @ColumnInfo(name = "media_count") var mediaCnt: Int, + @ColumnInfo(name = "last_modified") var modified: Long, + @ColumnInfo(name = "date_taken") var taken: Long, + @ColumnInfo(name = "size") var size: Long, + @ColumnInfo(name = "location") var location: Int, + @ColumnInfo(name = "media_types") var types: Int, + @ColumnInfo(name = "sort_value") var sortValue: String, + + // used with "Group direct subfolders" enabled + @Ignore var subfoldersCount: Int = 0, + @Ignore var subfoldersMediaCount: Int = 0, + @Ignore var containsMediaFilesDirectly: Boolean = true) { + + constructor() : this(null, "", "", "", 0, 0L, 0L, 0L, 0, 0, "", 0, 0) + + fun getBubbleText(sorting: Int, context: Context, dateFormat: String? = null, timeFormat: String? = null) = when { + sorting and SORT_BY_NAME != 0 -> name + sorting and SORT_BY_PATH != 0 -> path + sorting and SORT_BY_SIZE != 0 -> size.formatSize() + sorting and SORT_BY_DATE_MODIFIED != 0 -> modified.formatDate(context, dateFormat, timeFormat) + else -> taken.formatDate(context) + } + + fun areFavorites() = path == FAVORITES + + fun isRecycleBin() = path == RECYCLE_BIN + + fun getKey() = ObjectKey("$path-$modified") +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Favorite.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Favorite.kt new file mode 100644 index 000000000..8f37a9b41 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Favorite.kt @@ -0,0 +1,13 @@ +package com.simplemobiletools.gallery.pro.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity(tableName = "favorites", indices = [Index(value = ["full_path"], unique = true)]) +data class Favorite( + @PrimaryKey(autoGenerate = true) var id: Int?, + @ColumnInfo(name = "full_path") var fullPath: String, + @ColumnInfo(name = "filename") var filename: String, + @ColumnInfo(name = "parent_path") var parentPath: String) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/FilterItem.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/FilterItem.kt new file mode 100644 index 000000000..ce826e57e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/FilterItem.kt @@ -0,0 +1,6 @@ +package com.simplemobiletools.gallery.pro.models + +import android.graphics.Bitmap +import com.zomato.photofilters.imageprocessors.Filter + +data class FilterItem(var bitmap: Bitmap, val filter: Filter) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Medium.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Medium.kt new file mode 100644 index 000000000..05adfa524 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Medium.kt @@ -0,0 +1,108 @@ +package com.simplemobiletools.gallery.pro.models + +import android.content.Context +import androidx.room.* +import com.bumptech.glide.signature.ObjectKey +import com.simplemobiletools.commons.extensions.formatDate +import com.simplemobiletools.commons.extensions.formatSize +import com.simplemobiletools.commons.extensions.getFilenameExtension +import com.simplemobiletools.commons.extensions.isWebP +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.pro.helpers.* +import java.io.File +import java.io.Serializable +import java.util.* + +@Entity(tableName = "media", indices = [(Index(value = ["full_path"], unique = true))]) +data class Medium( + @PrimaryKey(autoGenerate = true) var id: Long?, + @ColumnInfo(name = "filename") var name: String, + @ColumnInfo(name = "full_path") var path: String, + @ColumnInfo(name = "parent_path") var parentPath: String, + @ColumnInfo(name = "last_modified") var modified: Long, + @ColumnInfo(name = "date_taken") var taken: Long, + @ColumnInfo(name = "size") var size: Long, + @ColumnInfo(name = "type") var type: Int, + @ColumnInfo(name = "video_duration") var videoDuration: Int, + @ColumnInfo(name = "is_favorite") var isFavorite: Boolean, + @ColumnInfo(name = "deleted_ts") var deletedTS: Long, + + @Ignore var gridPosition: Int = 0 // used at grid view decoration at Grouping enabled +) : Serializable, ThumbnailItem() { + + constructor() : this(null, "", "", "", 0L, 0L, 0L, 0, 0, false, 0L, 0) + + companion object { + private const val serialVersionUID = -6553149366975655L + } + + fun isWebP() = name.isWebP() + + fun isGIF() = type == TYPE_GIFS + + fun isImage() = type == TYPE_IMAGES + + fun isVideo() = type == TYPE_VIDEOS + + fun isRaw() = type == TYPE_RAWS + + fun isSVG() = type == TYPE_SVGS + + fun isPortrait() = type == TYPE_PORTRAITS + + fun isHidden() = name.startsWith('.') + + fun getBubbleText(sorting: Int, context: Context, dateFormat: String, timeFormat: String) = when { + sorting and SORT_BY_NAME != 0 -> name + sorting and SORT_BY_PATH != 0 -> path + sorting and SORT_BY_SIZE != 0 -> size.formatSize() + sorting and SORT_BY_DATE_MODIFIED != 0 -> modified.formatDate(context, dateFormat, timeFormat) + else -> taken.formatDate(context) + } + + fun getGroupingKey(groupBy: Int): String { + return when { + groupBy and GROUP_BY_LAST_MODIFIED_DAILY != 0 -> getDayStartTS(modified, false) + groupBy and GROUP_BY_LAST_MODIFIED_MONTHLY != 0 -> getDayStartTS(modified, true) + groupBy and GROUP_BY_DATE_TAKEN_DAILY != 0 -> getDayStartTS(taken, false) + groupBy and GROUP_BY_DATE_TAKEN_MONTHLY != 0 -> getDayStartTS(taken, true) + groupBy and GROUP_BY_FILE_TYPE != 0 -> type.toString() + groupBy and GROUP_BY_EXTENSION != 0 -> name.getFilenameExtension().toLowerCase() + groupBy and GROUP_BY_FOLDER != 0 -> parentPath + else -> "" + } + } + + fun getIsInRecycleBin() = deletedTS != 0L + + private fun getDayStartTS(ts: Long, resetDays: Boolean): String { + val calendar = Calendar.getInstance(Locale.ENGLISH).apply { + timeInMillis = ts + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + + if (resetDays) { + set(Calendar.DAY_OF_MONTH, 1) + } + } + + return calendar.timeInMillis.toString() + } + + fun getSignature(): String { + val lastModified = if (modified > 1) { + modified + } else { + File(path).lastModified() + } + + return "$path-$lastModified-$size" + } + + fun getKey() = ObjectKey(getSignature()) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/PaintOptions.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/PaintOptions.kt new file mode 100644 index 000000000..fc1cacb2a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/PaintOptions.kt @@ -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) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/ThumbnailItem.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/ThumbnailItem.kt new file mode 100644 index 000000000..4d2deaa31 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/ThumbnailItem.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.gallery.pro.models + +open class ThumbnailItem diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/ThumbnailSection.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/ThumbnailSection.kt new file mode 100644 index 000000000..1655cad89 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/ThumbnailSection.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.gallery.pro.models + +data class ThumbnailSection(val title: String) : ThumbnailItem() diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Widget.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Widget.kt new file mode 100644 index 000000000..2ef525334 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/models/Widget.kt @@ -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) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/receivers/BootCompletedReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/receivers/BootCompletedReceiver.kt new file mode 100644 index 000000000..f8aae1326 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/receivers/BootCompletedReceiver.kt @@ -0,0 +1,18 @@ +package com.simplemobiletools.gallery.pro.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.gallery.pro.extensions.updateDirectoryPath +import com.simplemobiletools.gallery.pro.helpers.MediaFetcher + +class BootCompletedReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + ensureBackgroundThread { + MediaFetcher(context).getFoldersToScan().forEach { + context.updateDirectoryPath(it) + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/receivers/RefreshMediaReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/receivers/RefreshMediaReceiver.kt new file mode 100644 index 000000000..3aa81b2f5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/receivers/RefreshMediaReceiver.kt @@ -0,0 +1,14 @@ +package com.simplemobiletools.gallery.pro.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.simplemobiletools.commons.helpers.REFRESH_PATH +import com.simplemobiletools.gallery.pro.extensions.addPathToDB + +class RefreshMediaReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val path = intent.getStringExtra(REFRESH_PATH) ?: return + context.addPathToDB(path) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgDecoder.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgDecoder.kt new file mode 100644 index 000000000..7bc47c9f8 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgDecoder.kt @@ -0,0 +1,26 @@ +package com.simplemobiletools.gallery.pro.svg + +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.ResourceDecoder +import com.bumptech.glide.load.engine.Resource +import com.bumptech.glide.load.resource.SimpleResource +import com.caverock.androidsvg.SVG +import com.caverock.androidsvg.SVGParseException + +import java.io.IOException +import java.io.InputStream + +class SvgDecoder : ResourceDecoder { + + override fun handles(source: InputStream, options: Options) = true + + @Throws(IOException::class) + override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource? { + try { + val svg = SVG.getFromInputStream(source) + return SimpleResource(svg) + } catch (ex: SVGParseException) { + throw IOException("Cannot load SVG from stream", ex) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgDrawableTranscoder.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgDrawableTranscoder.kt new file mode 100644 index 000000000..e447136e9 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgDrawableTranscoder.kt @@ -0,0 +1,17 @@ +package com.simplemobiletools.gallery.pro.svg + +import android.graphics.drawable.PictureDrawable +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.engine.Resource +import com.bumptech.glide.load.resource.SimpleResource +import com.bumptech.glide.load.resource.transcode.ResourceTranscoder +import com.caverock.androidsvg.SVG + +class SvgDrawableTranscoder : ResourceTranscoder { + override fun transcode(toTranscode: Resource, options: Options): Resource? { + val svg = toTranscode.get() + val picture = svg.renderToPicture() + val drawable = PictureDrawable(picture) + return SimpleResource(drawable) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgModule.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgModule.kt new file mode 100644 index 000000000..ee15a2274 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgModule.kt @@ -0,0 +1,21 @@ +package com.simplemobiletools.gallery.pro.svg + +import android.content.Context +import android.graphics.drawable.PictureDrawable + +import com.bumptech.glide.Glide +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.AppGlideModule +import com.caverock.androidsvg.SVG + +import java.io.InputStream + +@GlideModule +class SvgModule : AppGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + registry.register(SVG::class.java, PictureDrawable::class.java, SvgDrawableTranscoder()).append(InputStream::class.java, SVG::class.java, SvgDecoder()) + } + + override fun isManifestParsingEnabled() = false +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgSoftwareLayerSetter.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgSoftwareLayerSetter.kt new file mode 100644 index 000000000..c766ab651 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/svg/SvgSoftwareLayerSetter.kt @@ -0,0 +1,25 @@ +package com.simplemobiletools.gallery.pro.svg + +import android.graphics.drawable.PictureDrawable +import android.widget.ImageView + +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.ImageViewTarget +import com.bumptech.glide.request.target.Target + +class SvgSoftwareLayerSetter : RequestListener { + + override fun onLoadFailed(e: GlideException?, model: Any, target: Target, isFirstResource: Boolean): Boolean { + val view = (target as ImageViewTarget<*>).view + view.setLayerType(ImageView.LAYER_TYPE_NONE, null) + return false + } + + override fun onResourceReady(resource: PictureDrawable, model: Any, target: Target, dataSource: DataSource, isFirstResource: Boolean): Boolean { + val view = (target as ImageViewTarget<*>).view + view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null) + return false + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/EditorDrawCanvas.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/EditorDrawCanvas.kt new file mode 100644 index 000000000..592320b33 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/EditorDrawCanvas.kt @@ -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() + 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() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/InstantItemSwitch.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/InstantItemSwitch.kt new file mode 100644 index 000000000..18f2f154e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/InstantItemSwitch.kt @@ -0,0 +1,71 @@ +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.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) { + private var mTouchDownTime = 0L + private var mTouchDownX = 0f + private var mTouchDownY = 0f + private var passTouches = false + private var dragThreshold = DRAG_THRESHOLD * context.resources.displayMetrics.density + + var parentView: ViewGroup? = null + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + if (passTouches) { + if (ev.actionMasked == MotionEvent.ACTION_DOWN) { + passTouches = false + } + return false + } + return super.dispatchTouchEvent(ev) + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + if (passTouches) { + return false + } + + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + mTouchDownX = event.x + mTouchDownY = event.y + mTouchDownTime = System.currentTimeMillis() + } + MotionEvent.ACTION_UP -> { + 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() + } + } + MotionEvent.ACTION_MOVE -> { + if (passTouches) { + return false + } + + val diffX = mTouchDownX - event.x + val diffY = mTouchDownY - event.y + if (Math.abs(diffX) > dragThreshold || Math.abs(diffY) > dragThreshold) { + if (!passTouches) { + event.action = MotionEvent.ACTION_DOWN + event.setLocation(event.rawX, event.y) + parentView?.dispatchTouchEvent(event) + } + passTouches = true + parentView?.dispatchTouchEvent(event) + return false + } + } + } + return true + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/MediaSideScroll.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/MediaSideScroll.kt new file mode 100644 index 000000000..61ff895a0 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/MediaSideScroll.kt @@ -0,0 +1,197 @@ +package com.simplemobiletools.gallery.pro.views + +import android.app.Activity +import android.content.Context +import android.media.AudioManager +import android.os.Handler +import android.provider.Settings +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.ViewGroup +import android.widget.RelativeLayout +import android.widget.TextView +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.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) { + private val SLIDE_INFO_FADE_DELAY = 1000L + private var mTouchDownX = 0f + private var mTouchDownY = 0f + private var mTouchDownTime = 0L + 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 + + private var mSlideInfoText = "" + private var mSlideInfoFadeHandler = Handler() + private var mParentView: ViewGroup? = null + private var activity: Activity? = null + private var doubleTap: ((Float, Float) -> Unit)? = null + + private lateinit var slideInfoView: TextView + private lateinit var singleTap: (Float, Float) -> Unit + + fun initialize(activity: Activity, slideInfoView: TextView, isBrightness: Boolean, parentView: ViewGroup?, singleTap: (x: Float, y: Float) -> Unit, + doubleTap: ((x: Float, y: Float) -> Unit)? = null) { + this.activity = activity + this.slideInfoView = slideInfoView + this.singleTap = singleTap + this.doubleTap = doubleTap + mParentView = parentView + mIsBrightnessScroll = isBrightness + mSlideInfoText = activity.getString(if (isBrightness) R.string.brightness else R.string.volume) + onGlobalLayout { + mViewHeight = height + } + } + + private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { + if (e != null) { + singleTap(e.rawX, e.rawY) + } + return true + } + + override fun onDoubleTap(e: MotionEvent?): Boolean { + if (e != null && doubleTap != null) { + doubleTap!!.invoke(e.rawX, e.rawY) + } + return true + } + }) + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + if (mPassTouches) { + if (ev.actionMasked == MotionEvent.ACTION_DOWN) { + mPassTouches = false + } + return false + } + return super.dispatchTouchEvent(ev) + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + if (mPassTouches && activity == null) { + return false + } + + gestureDetector.onTouchEvent(event) + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + mTouchDownX = event.x + mTouchDownY = event.y + mLastTouchY = event.y + mTouchDownTime = System.currentTimeMillis() + if (mIsBrightnessScroll) { + if (mTouchDownValue == -1) { + mTouchDownValue = getCurrentBrightness() + } + } else { + mTouchDownValue = getCurrentVolume() + } + } + MotionEvent.ACTION_MOVE -> { + val diffX = mTouchDownX - event.x + val diffY = mTouchDownY - event.y + + if (Math.abs(diffY) > dragThreshold && Math.abs(diffY) > Math.abs(diffX)) { + 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)) { + mTouchDownY = event.y + mTouchDownValue = if (mIsBrightnessScroll) mTempBrightness else getCurrentVolume() + } + + percentChanged(percent) + } else if (Math.abs(diffX) > dragThreshold || Math.abs(diffY) > dragThreshold) { + if (!mPassTouches) { + event.action = MotionEvent.ACTION_DOWN + event.setLocation(event.rawX, event.y) + mParentView?.dispatchTouchEvent(event) + } + mPassTouches = true + mParentView?.dispatchTouchEvent(event) + return false + } + mLastTouchY = event.y + } + MotionEvent.ACTION_UP -> { + if (mIsBrightnessScroll) { + mTouchDownValue = mTempBrightness + } + } + } + return true + } + + private fun getCurrentVolume() = activity?.audioManager?.getStreamVolume(AudioManager.STREAM_MUSIC) ?: 0 + + private fun getCurrentBrightness(): Int { + return try { + Settings.System.getInt(activity!!.contentResolver, Settings.System.SCREEN_BRIGHTNESS) + } catch (e: Settings.SettingNotFoundException) { + 70 + } + } + + private fun percentChanged(percent: Int) { + if (mIsBrightnessScroll) { + brightnessPercentChanged(percent) + } else { + volumePercentChanged(percent) + } + } + + private fun volumePercentChanged(percent: Int) { + val stream = AudioManager.STREAM_MUSIC + 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) + + val absolutePercent = ((newVolume / maxVolume.toFloat()) * 100).toInt() + showValue(absolutePercent) + + mSlideInfoFadeHandler.removeCallbacksAndMessages(null) + mSlideInfoFadeHandler.postDelayed({ + slideInfoView.animate().alpha(0f) + }, SLIDE_INFO_FADE_DELAY) + } + + private fun brightnessPercentChanged(percent: Int) { + val maxBrightness = 255f + var newBrightness = (mTouchDownValue + 2.55 * percent).toFloat() + newBrightness = Math.min(maxBrightness, Math.max(0f, newBrightness)) + mTempBrightness = newBrightness.toInt() + + val absolutePercent = ((newBrightness / maxBrightness) * 100).toInt() + showValue(absolutePercent) + + val attributes = activity!!.window.attributes + attributes.screenBrightness = absolutePercent / 100f + activity!!.window.attributes = attributes + + mSlideInfoFadeHandler.removeCallbacksAndMessages(null) + mSlideInfoFadeHandler.postDelayed({ + slideInfoView.animate().alpha(0f) + }, SLIDE_INFO_FADE_DELAY) + } + + private fun showValue(percent: Int) { + slideInfoView.apply { + text = "$mSlideInfoText:\n$percent%" + alpha = 1f + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/views/MySquareImageView.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/MySquareImageView.kt similarity index 68% rename from app/src/main/kotlin/com/simplemobiletools/gallery/views/MySquareImageView.kt rename to app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/MySquareImageView.kt index 54c26947c..24cde1c2a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/views/MySquareImageView.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/MySquareImageView.kt @@ -1,10 +1,11 @@ -package com.simplemobiletools.gallery.views +package com.simplemobiletools.gallery.pro.views import android.content.Context import android.util.AttributeSet import android.widget.ImageView class MySquareImageView : ImageView { + var isHorizontalScrolling = false constructor(context: Context) : super(context) @@ -13,7 +14,7 @@ class MySquareImageView : ImageView { constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - setMeasuredDimension(measuredWidth, measuredWidth) + val spec = if (isHorizontalScrolling) heightMeasureSpec else widthMeasureSpec + super.onMeasure(spec, spec) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/InstallReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/InstallReceiver.kt deleted file mode 100644 index fc3566bc0..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/receivers/InstallReceiver.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.simplemobiletools.gallery.receivers - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import com.google.gson.Gson -import com.simplemobiletools.gallery.asynctasks.GetDirectoriesAsynctask -import com.simplemobiletools.gallery.extensions.config - -class InstallReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - GetDirectoriesAsynctask(context, false, false) { - context.config.directories = Gson().toJson(it) - }.execute() - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/views/MyScalableRecyclerView.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/views/MyScalableRecyclerView.kt deleted file mode 100644 index 3b1a6add8..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/views/MyScalableRecyclerView.kt +++ /dev/null @@ -1,208 +0,0 @@ -package com.simplemobiletools.gallery.views - -import android.content.Context -import android.os.Handler -import android.support.v7.widget.RecyclerView -import android.util.AttributeSet -import android.view.MotionEvent -import android.view.ScaleGestureDetector -import com.simplemobiletools.gallery.R - -// drag selection is based on https://github.com/afollestad/drag-select-recyclerview -class MyScalableRecyclerView : RecyclerView { - private val AUTO_SCROLL_DELAY = 25L - - private var mScaleDetector: ScaleGestureDetector - - private var dragSelectActive = false - private var lastDraggedIndex = -1 - private var minReached = 0 - private var maxReached = 0 - private var initialSelection = 0 - - private var hotspotHeight = 0 - private var hotspotOffsetTop = 0 - private var hotspotOffsetBottom = 0 - - private var hotspotTopBoundStart = 0 - private var hotspotTopBoundEnd = 0 - private var hotspotBottomBoundStart = 0 - private var hotspotBottomBoundEnd = 0 - private var autoScrollVelocity = 0 - - private var inTopHotspot = false - private var inBottomHotspot = false - - companion object { - var mListener: MyScalableRecyclerViewListener? = null - var mCurrScaleFactor = 1.0f - var mLastUp = 0L // allow only pinch zoom, not double tap - } - - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - init { - hotspotHeight = context.resources.getDimensionPixelSize(R.dimen.dragselect_hotspot_height) - mScaleDetector = ScaleGestureDetector(context, GestureListener()) - } - - override fun onMeasure(widthSpec: Int, heightSpec: Int) { - super.onMeasure(widthSpec, heightSpec) - if (hotspotHeight > -1) { - hotspotTopBoundStart = hotspotOffsetTop - hotspotTopBoundEnd = hotspotOffsetTop + hotspotHeight - hotspotBottomBoundStart = measuredHeight - hotspotHeight - hotspotOffsetBottom - hotspotBottomBoundEnd = measuredHeight - hotspotOffsetBottom - } - } - - private var autoScrollHandler = Handler() - private val autoScrollRunnable = object : Runnable { - override fun run() { - if (inTopHotspot) { - scrollBy(0, -autoScrollVelocity) - autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY) - } else if (inBottomHotspot) { - scrollBy(0, autoScrollVelocity) - autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY) - } - } - } - - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - if (!dragSelectActive) - super.dispatchTouchEvent(ev) - - when (ev.action) { - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - dragSelectActive = false - inTopHotspot = false - inBottomHotspot = false - autoScrollHandler.removeCallbacks(autoScrollRunnable) - mCurrScaleFactor = 1.0f - mLastUp = System.currentTimeMillis() - return true - } - - MotionEvent.ACTION_MOVE -> { - if (dragSelectActive) { - val itemPosition = getItemPosition(ev) - if (hotspotHeight > -1) { - if (ev.y in hotspotTopBoundStart..hotspotTopBoundEnd) { - inBottomHotspot = false - if (!inTopHotspot) { - inTopHotspot = true - autoScrollHandler.removeCallbacks(autoScrollRunnable) - autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY.toLong()) - } - - val simulatedFactor = (hotspotTopBoundEnd - hotspotTopBoundStart).toFloat() - val simulatedY = ev.y - hotspotTopBoundStart - autoScrollVelocity = (simulatedFactor - simulatedY).toInt() / 2 - } else if (ev.y in hotspotBottomBoundStart..hotspotBottomBoundEnd) { - inTopHotspot = false - if (!inBottomHotspot) { - inBottomHotspot = true - autoScrollHandler.removeCallbacks(autoScrollRunnable) - autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY.toLong()) - } - - val simulatedY = ev.y + hotspotBottomBoundEnd - val simulatedFactor = (hotspotBottomBoundStart + hotspotBottomBoundEnd).toFloat() - autoScrollVelocity = (simulatedY - simulatedFactor).toInt() / 2 - } else if (inTopHotspot || inBottomHotspot) { - autoScrollHandler.removeCallbacks(autoScrollRunnable) - inTopHotspot = false - inBottomHotspot = false - } - } - - if (itemPosition != RecyclerView.NO_POSITION && lastDraggedIndex != itemPosition) { - lastDraggedIndex = itemPosition - if (minReached == -1) { - minReached = lastDraggedIndex - } - - if (maxReached == -1) { - maxReached = lastDraggedIndex - } - - if (lastDraggedIndex > maxReached) { - maxReached = lastDraggedIndex - } - - if (lastDraggedIndex < minReached) { - minReached = lastDraggedIndex - } - - mListener?.selectRange(initialSelection, lastDraggedIndex, minReached, maxReached) - - if (initialSelection == lastDraggedIndex) { - minReached = lastDraggedIndex - maxReached = lastDraggedIndex - } - } - - return true - } - } - } - return mScaleDetector.onTouchEvent(ev) - } - - fun setDragSelectActive(initialSelection: Int) { - if (dragSelectActive) - return - - lastDraggedIndex = -1 - minReached = -1 - maxReached = -1 - this.initialSelection = initialSelection - dragSelectActive = true - mListener?.selectItem(initialSelection) - } - - private fun getItemPosition(e: MotionEvent): Int { - val v = findChildViewUnder(e.x, e.y) ?: return RecyclerView.NO_POSITION - - if (v.tag == null || v.tag !is RecyclerView.ViewHolder) { - throw IllegalStateException("Make sure your adapter makes a call to super.onBindViewHolder(), and doesn't override itemView tags.") - } - - val holder = v.tag as RecyclerView.ViewHolder - return holder.adapterPosition - } - - - class GestureListener : ScaleGestureDetector.SimpleOnScaleGestureListener() { - private val ZOOM_IN_THRESHOLD = -0.4f - private val ZOOM_OUT_THRESHOLD = 0.15f - - override fun onScale(detector: ScaleGestureDetector): Boolean { - if (System.currentTimeMillis() - mLastUp < 1000) - return false - - val diff = mCurrScaleFactor - detector.scaleFactor - if (diff < ZOOM_IN_THRESHOLD && mCurrScaleFactor == 1.0f) { - mListener?.zoomIn() - mCurrScaleFactor = detector.scaleFactor - } else if (diff > ZOOM_OUT_THRESHOLD && mCurrScaleFactor == 1.0f) { - mListener?.zoomOut() - mCurrScaleFactor = detector.scaleFactor - } - return false - } - } - - interface MyScalableRecyclerViewListener { - fun zoomOut() - - fun zoomIn() - - fun selectItem(position: Int) - - fun selectRange(initialSelection: Int, lastDraggedIndex: Int, minReached: Int, maxReached: Int) - } -} diff --git a/app/src/main/res/drawable-hdpi/ic_cardboard.png b/app/src/main/res/drawable-hdpi/ic_cardboard.png new file mode 100644 index 000000000..0df820802 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cardboard.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_edit.png b/app/src/main/res/drawable-hdpi/ic_edit.png deleted file mode 100644 index 3cf5e4e71..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_edit.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_flip.png b/app/src/main/res/drawable-hdpi/ic_flip.png deleted file mode 100644 index ae3b9dbfb..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_flip.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_hide.png b/app/src/main/res/drawable-hdpi/ic_hide.png deleted file mode 100644 index b46cb212a..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_hide.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_panorama_outline.png b/app/src/main/res/drawable-hdpi/ic_panorama_outline.png new file mode 100644 index 000000000..c932dcd78 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_panorama_outline.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_rotate_left.png b/app/src/main/res/drawable-hdpi/ic_rotate_left.png deleted file mode 100644 index df19b1863..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_rotate_left.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_rotate_one_eighty.png b/app/src/main/res/drawable-hdpi/ic_rotate_one_eighty.png deleted file mode 100644 index 6ec58172a..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_rotate_one_eighty.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_rotate_right.png b/app/src/main/res/drawable-hdpi/ic_rotate_right.png deleted file mode 100644 index 7b1d07af3..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_rotate_right.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_unhide.png b/app/src/main/res/drawable-hdpi/ic_unhide.png deleted file mode 100644 index 4791d9f7d..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_unhide.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/img_play_outline.png b/app/src/main/res/drawable-hdpi/img_play_outline.png deleted file mode 100644 index c5cf4f131..000000000 Binary files a/app/src/main/res/drawable-hdpi/img_play_outline.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/img_play_outline_big.png b/app/src/main/res/drawable-hdpi/img_play_outline_big.png deleted file mode 100644 index 96933587c..000000000 Binary files a/app/src/main/res/drawable-hdpi/img_play_outline_big.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/img_widget_preview.png b/app/src/main/res/drawable-hdpi/img_widget_preview.png new file mode 100644 index 000000000..625e39531 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/img_widget_preview.png differ diff --git a/app/src/main/res/drawable-hdpi/sample_logo.png b/app/src/main/res/drawable-hdpi/sample_logo.png new file mode 100644 index 000000000..6ef81904c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sample_logo.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_cardboard.png b/app/src/main/res/drawable-xhdpi/ic_cardboard.png new file mode 100644 index 000000000..1c978a019 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cardboard.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_edit.png b/app/src/main/res/drawable-xhdpi/ic_edit.png deleted file mode 100644 index fe2eca85c..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_edit.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_flip.png b/app/src/main/res/drawable-xhdpi/ic_flip.png deleted file mode 100644 index 5717ef76a..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_flip.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_hide.png b/app/src/main/res/drawable-xhdpi/ic_hide.png deleted file mode 100644 index 62db27510..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_hide.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_panorama_outline.png b/app/src/main/res/drawable-xhdpi/ic_panorama_outline.png new file mode 100644 index 000000000..70e0274ec Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_panorama_outline.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_rotate_left.png b/app/src/main/res/drawable-xhdpi/ic_rotate_left.png deleted file mode 100644 index e07494c64..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_rotate_left.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_rotate_one_eighty.png b/app/src/main/res/drawable-xhdpi/ic_rotate_one_eighty.png deleted file mode 100644 index fa913d37d..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_rotate_one_eighty.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_rotate_right.png b/app/src/main/res/drawable-xhdpi/ic_rotate_right.png deleted file mode 100644 index 0f5ac605f..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_rotate_right.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_unhide.png b/app/src/main/res/drawable-xhdpi/ic_unhide.png deleted file mode 100644 index 604bfd012..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_unhide.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/img_play_outline.png b/app/src/main/res/drawable-xhdpi/img_play_outline.png deleted file mode 100644 index 20fedaf45..000000000 Binary files a/app/src/main/res/drawable-xhdpi/img_play_outline.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/img_play_outline_big.png b/app/src/main/res/drawable-xhdpi/img_play_outline_big.png deleted file mode 100644 index 29599d2ba..000000000 Binary files a/app/src/main/res/drawable-xhdpi/img_play_outline_big.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/img_widget_preview.png b/app/src/main/res/drawable-xhdpi/img_widget_preview.png new file mode 100644 index 000000000..481681a63 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/img_widget_preview.png differ diff --git a/app/src/main/res/drawable-xhdpi/sample_logo.png b/app/src/main/res/drawable-xhdpi/sample_logo.png new file mode 100644 index 000000000..69c3e3684 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sample_logo.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_cardboard.png b/app/src/main/res/drawable-xxhdpi/ic_cardboard.png new file mode 100644 index 000000000..71c05230d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cardboard.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_edit.png b/app/src/main/res/drawable-xxhdpi/ic_edit.png deleted file mode 100644 index 292f93bf6..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_edit.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_flip.png b/app/src/main/res/drawable-xxhdpi/ic_flip.png deleted file mode 100644 index 79910ffe0..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_flip.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_hide.png b/app/src/main/res/drawable-xxhdpi/ic_hide.png deleted file mode 100644 index 9ab0c3759..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_hide.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_panorama_outline.png b/app/src/main/res/drawable-xxhdpi/ic_panorama_outline.png new file mode 100644 index 000000000..8ba832ca8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_panorama_outline.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_rotate_left.png b/app/src/main/res/drawable-xxhdpi/ic_rotate_left.png deleted file mode 100644 index fec5b4164..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_rotate_left.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_rotate_one_eighty.png b/app/src/main/res/drawable-xxhdpi/ic_rotate_one_eighty.png deleted file mode 100644 index 17a070205..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_rotate_one_eighty.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_rotate_right.png b/app/src/main/res/drawable-xxhdpi/ic_rotate_right.png deleted file mode 100644 index 82f3e5493..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_rotate_right.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_unhide.png b/app/src/main/res/drawable-xxhdpi/ic_unhide.png deleted file mode 100644 index 196ab015f..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_unhide.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/img_play_outline.png b/app/src/main/res/drawable-xxhdpi/img_play_outline.png deleted file mode 100644 index 5a39e380d..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/img_play_outline.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/img_play_outline_big.png b/app/src/main/res/drawable-xxhdpi/img_play_outline_big.png deleted file mode 100644 index 3d1b5c2e2..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/img_play_outline_big.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/img_widget_preview.png b/app/src/main/res/drawable-xxhdpi/img_widget_preview.png new file mode 100644 index 000000000..c400e88e9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/img_widget_preview.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sample_logo.png b/app/src/main/res/drawable-xxhdpi/sample_logo.png new file mode 100644 index 000000000..aeebc03d5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sample_logo.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_cardboard.png b/app/src/main/res/drawable-xxxhdpi/ic_cardboard.png new file mode 100644 index 000000000..d33c6ef77 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_cardboard.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_edit.png b/app/src/main/res/drawable-xxxhdpi/ic_edit.png deleted file mode 100644 index 13dc42b28..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_edit.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_flip.png b/app/src/main/res/drawable-xxxhdpi/ic_flip.png deleted file mode 100644 index d47ba4a7e..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_flip.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_hide.png b/app/src/main/res/drawable-xxxhdpi/ic_hide.png deleted file mode 100644 index 3e7242a2e..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_hide.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_panorama_outline.png b/app/src/main/res/drawable-xxxhdpi/ic_panorama_outline.png new file mode 100644 index 000000000..c7a48896b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_panorama_outline.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_rotate_left.png b/app/src/main/res/drawable-xxxhdpi/ic_rotate_left.png deleted file mode 100644 index 3ffcf9cc0..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_rotate_left.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_rotate_one_eighty.png b/app/src/main/res/drawable-xxxhdpi/ic_rotate_one_eighty.png deleted file mode 100644 index 707f4e9d1..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_rotate_one_eighty.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_rotate_right.png b/app/src/main/res/drawable-xxxhdpi/ic_rotate_right.png deleted file mode 100644 index 13e03d530..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_rotate_right.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_unhide.png b/app/src/main/res/drawable-xxxhdpi/ic_unhide.png deleted file mode 100644 index 8382da2ea..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_unhide.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/img_play_outline.png b/app/src/main/res/drawable-xxxhdpi/img_play_outline.png deleted file mode 100644 index 54e535148..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/img_play_outline.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/img_play_outline_big.png b/app/src/main/res/drawable-xxxhdpi/img_play_outline_big.png deleted file mode 100644 index 66f27f62b..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/img_play_outline_big.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/img_widget_preview.png b/app/src/main/res/drawable-xxxhdpi/img_widget_preview.png new file mode 100644 index 000000000..ffe566b2d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/img_widget_preview.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sample_logo.png b/app/src/main/res/drawable-xxxhdpi/sample_logo.png new file mode 100644 index 000000000..589331642 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sample_logo.png differ diff --git a/app/src/main/res/drawable/black_rounded_background.xml b/app/src/main/res/drawable/black_rounded_background.xml new file mode 100644 index 000000000..edf046060 --- /dev/null +++ b/app/src/main/res/drawable/black_rounded_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/circle_background.xml b/app/src/main/res/drawable/circle_background.xml new file mode 100644 index 000000000..58f030da7 --- /dev/null +++ b/app/src/main/res/drawable/circle_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/circle_black_background.xml b/app/src/main/res/drawable/circle_black_background.xml new file mode 100644 index 000000000..2afe7d524 --- /dev/null +++ b/app/src/main/res/drawable/circle_black_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/extended_details_background.xml b/app/src/main/res/drawable/extended_details_background.xml new file mode 100644 index 000000000..a2a09c0c3 --- /dev/null +++ b/app/src/main/res/drawable/extended_details_background.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/actionbar_gradient_background.xml b/app/src/main/res/drawable/gradient_background_flipped.xml similarity index 100% rename from app/src/main/res/drawable/actionbar_gradient_background.xml rename to app/src/main/res/drawable/gradient_background_flipped.xml diff --git a/app/src/main/res/drawable/ic_aspect_ratio_vector.xml b/app/src/main/res/drawable/ic_aspect_ratio_vector.xml new file mode 100644 index 000000000..d0ebe2814 --- /dev/null +++ b/app/src/main/res/drawable/ic_aspect_ratio_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_crop_rotate_vector.xml b/app/src/main/res/drawable/ic_crop_rotate_vector.xml new file mode 100644 index 000000000..681d300cd --- /dev/null +++ b/app/src/main/res/drawable/ic_crop_rotate_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_draw_vector.xml b/app/src/main/res/drawable/ic_draw_vector.xml new file mode 100644 index 000000000..1e296d845 --- /dev/null +++ b/app/src/main/res/drawable/ic_draw_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_explore_off_vector.xml b/app/src/main/res/drawable/ic_explore_off_vector.xml new file mode 100644 index 000000000..a67123134 --- /dev/null +++ b/app/src/main/res/drawable/ic_explore_off_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_explore_vector.xml b/app/src/main/res/drawable/ic_explore_vector.xml new file mode 100644 index 000000000..07d304b87 --- /dev/null +++ b/app/src/main/res/drawable/ic_explore_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_flip_horizontally_vector.xml b/app/src/main/res/drawable/ic_flip_horizontally_vector.xml new file mode 100644 index 000000000..7b82d9d7c --- /dev/null +++ b/app/src/main/res/drawable/ic_flip_horizontally_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_flip_vertically_vector.xml b/app/src/main/res/drawable/ic_flip_vertically_vector.xml new file mode 100644 index 000000000..4d9437cf6 --- /dev/null +++ b/app/src/main/res/drawable/ic_flip_vertically_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_photo_filter_vector.xml b/app/src/main/res/drawable/ic_photo_filter_vector.xml new file mode 100644 index 000000000..127f154be --- /dev/null +++ b/app/src/main/res/drawable/ic_photo_filter_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_portrait_photo_vector.xml b/app/src/main/res/drawable/ic_portrait_photo_vector.xml new file mode 100644 index 000000000..e94d39587 --- /dev/null +++ b/app/src/main/res/drawable/ic_portrait_photo_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_rotate_right_vector.xml b/app/src/main/res/drawable/ic_rotate_right_vector.xml new file mode 100644 index 000000000..ba4935170 --- /dev/null +++ b/app/src/main/res/drawable/ic_rotate_right_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_set_as_vector.xml b/app/src/main/res/drawable/ic_set_as_vector.xml new file mode 100644 index 000000000..51a983f3b --- /dev/null +++ b/app/src/main/res/drawable/ic_set_as_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_slideshow_vector.xml b/app/src/main/res/drawable/ic_slideshow_vector.xml new file mode 100644 index 000000000..a9bc8667b --- /dev/null +++ b/app/src/main/res/drawable/ic_slideshow_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/shortcut_image.xml b/app/src/main/res/drawable/shortcut_image.xml new file mode 100644 index 000000000..20dde1a45 --- /dev/null +++ b/app/src/main/res/drawable/shortcut_image.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/drawable/stroke_background.xml b/app/src/main/res/drawable/stroke_background.xml new file mode 100644 index 000000000..518fda83c --- /dev/null +++ b/app/src/main/res/drawable/stroke_background.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/src/main/res/layout-v23/directory_tmb.xml b/app/src/main/res/layout-v23/directory_tmb.xml deleted file mode 100644 index aab55c8ac..000000000 --- a/app/src/main/res/layout-v23/directory_tmb.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/app/src/main/res/layout-v23/photo_video_tmb.xml b/app/src/main/res/layout-v23/photo_video_tmb.xml deleted file mode 100644 index 5044be391..000000000 --- a/app/src/main/res/layout-v23/photo_video_tmb.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/app/src/main/res/layout/activity_edit.xml b/app/src/main/res/layout/activity_edit.xml new file mode 100644 index 000000000..3dffbf1bf --- /dev/null +++ b/app/src/main/res/layout/activity_edit.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_excluded_folders.xml b/app/src/main/res/layout/activity_excluded_folders.xml deleted file mode 100644 index 64df37244..000000000 --- a/app/src/main/res/layout/activity_excluded_folders.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_included_folders.xml b/app/src/main/res/layout/activity_included_folders.xml deleted file mode 100644 index 527cdd94a..000000000 --- a/app/src/main/res/layout/activity_included_folders.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8390ab0bc..3756cbb82 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,5 @@ - - + + + + + + + app:layoutManager="com.simplemobiletools.commons.views.MyGridLayoutManager" + app:spanCount="@integer/directory_columns_vertical_scroll" /> + android:paddingStart="@dimen/normal_margin" + android:visibility="gone"> + + + + + + + + + - + diff --git a/app/src/main/res/layout/activity_manage_folders.xml b/app/src/main/res/layout/activity_manage_folders.xml new file mode 100644 index 000000000..e4526f204 --- /dev/null +++ b/app/src/main/res/layout/activity_manage_folders.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_media.xml b/app/src/main/res/layout/activity_media.xml index fc7539c71..c157f234b 100644 --- a/app/src/main/res/layout/activity_media.xml +++ b/app/src/main/res/layout/activity_media.xml @@ -1,6 +1,5 @@ - - + + + + + app:layoutManager="com.simplemobiletools.commons.views.MyGridLayoutManager" + app:spanCount="@integer/media_columns_vertical_scroll" /> + android:paddingStart="@dimen/normal_margin" + android:visibility="gone"> + + + + + + + + + - + diff --git a/app/src/main/res/layout/activity_medium.xml b/app/src/main/res/layout/activity_medium.xml index 1d8dc6c42..4d88429f9 100644 --- a/app/src/main/res/layout/activity_medium.xml +++ b/app/src/main/res/layout/activity_medium.xml @@ -10,4 +10,15 @@ android:layout_width="match_parent" android:layout_height="match_parent"/> + + + + diff --git a/app/src/main/res/layout/activity_new_photo_edit.xml b/app/src/main/res/layout/activity_new_photo_edit.xml new file mode 100644 index 000000000..aae3460c7 --- /dev/null +++ b/app/src/main/res/layout/activity_new_photo_edit.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/layout/activity_new_video_edit.xml b/app/src/main/res/layout/activity_new_video_edit.xml new file mode 100644 index 000000000..aae3460c7 --- /dev/null +++ b/app/src/main/res/layout/activity_new_video_edit.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/layout/activity_panorama_photo.xml b/app/src/main/res/layout/activity_panorama_photo.xml new file mode 100644 index 000000000..f4a92234d --- /dev/null +++ b/app/src/main/res/layout/activity_panorama_photo.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_panorama_video.xml b/app/src/main/res/layout/activity_panorama_video.xml new file mode 100644 index 000000000..337b4a375 --- /dev/null +++ b/app/src/main/res/layout/activity_panorama_video.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml new file mode 100644 index 000000000..6ab0974b7 --- /dev/null +++ b/app/src/main/res/layout/activity_search.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_set_wallpaper.xml b/app/src/main/res/layout/activity_set_wallpaper.xml new file mode 100644 index 000000000..7acb21c69 --- /dev/null +++ b/app/src/main/res/layout/activity_set_wallpaper.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index c1ec5d18a..ae582dca5 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -1,6 +1,7 @@ - @@ -17,35 +18,128 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:padding="@dimen/activity_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/customize_colors" /> + + + + + + + + + + + + + + + + + + + + + + + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/manage_included_folders" /> @@ -55,46 +149,85 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:padding="@dimen/activity_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/manage_excluded_folders" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + + + + + + + android:text="@string/show_hidden_items" + app:switchPadding="@dimen/medium_margin" /> + + + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/autoplay_videos" + app:switchPadding="@dimen/medium_margin" /> + + + + + + @@ -114,7 +270,10 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:padding="@dimen/activity_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/loop_videos" + app:switchPadding="@dimen/medium_margin" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/open_videos_on_separate_screen" + app:switchPadding="@dimen/medium_margin" /> - - - - - + android:layout_marginStart="@dimen/bigger_margin" + android:layout_marginTop="@dimen/section_margin" + android:text="@string/thumbnails" + android:textAllCaps="true" + android:textSize="@dimen/smaller_text_size" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/crop_thumbnails" + app:switchPadding="@dimen/medium_margin" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin"> + + + + + + + + + + + + + + + + + android:text="@string/scroll_thumbnails_horizontally" + app:switchPadding="@dimen/medium_margin" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -214,67 +607,613 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:paddingBottom="@dimen/bigger_margin" - android:paddingLeft="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" - android:paddingTop="@dimen/bigger_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin"> + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + android:text="@string/screen_rotation_by" /> + android:clickable="false" /> + + + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_toStartOf="@+id/settings_empty_recycle_bin_size" + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + android:text="@string/empty_recycle_bin" /> + android:clickable="false" /> + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml deleted file mode 100644 index 9a0069a92..000000000 --- a/app/src/main/res/layout/activity_splash.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/app/src/main/res/layout/activity_video_player.xml b/app/src/main/res/layout/activity_video_player.xml new file mode 100644 index 000000000..8c50ca199 --- /dev/null +++ b/app/src/main/res/layout/activity_video_player.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_widget_config.xml b/app/src/main/res/layout/activity_widget_config.xml new file mode 100644 index 000000000..2604a6a03 --- /dev/null +++ b/app/src/main/res/layout/activity_widget_config.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/bottom_actions.xml b/app/src/main/res/layout/bottom_actions.xml new file mode 100644 index 000000000..e03791630 --- /dev/null +++ b/app/src/main/res/layout/bottom_actions.xml @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/bottom_actions_aspect_ratio.xml b/app/src/main/res/layout/bottom_actions_aspect_ratio.xml new file mode 100644 index 000000000..e5ffd02df --- /dev/null +++ b/app/src/main/res/layout/bottom_actions_aspect_ratio.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/bottom_editor_actions_filter.xml b/app/src/main/res/layout/bottom_editor_actions_filter.xml new file mode 100644 index 000000000..734942343 --- /dev/null +++ b/app/src/main/res/layout/bottom_editor_actions_filter.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/layout/bottom_editor_crop_rotate_actions.xml b/app/src/main/res/layout/bottom_editor_crop_rotate_actions.xml new file mode 100644 index 000000000..93dcbb4e6 --- /dev/null +++ b/app/src/main/res/layout/bottom_editor_crop_rotate_actions.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/bottom_editor_draw_actions.xml b/app/src/main/res/layout/bottom_editor_draw_actions.xml new file mode 100644 index 000000000..07fe2fb2c --- /dev/null +++ b/app/src/main/res/layout/bottom_editor_draw_actions.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/bottom_editor_primary_actions.xml b/app/src/main/res/layout/bottom_editor_primary_actions.xml new file mode 100644 index 000000000..ed600ed0e --- /dev/null +++ b/app/src/main/res/layout/bottom_editor_primary_actions.xml @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/bottom_set_wallpaper_actions.xml b/app/src/main/res/layout/bottom_set_wallpaper_actions.xml new file mode 100644 index 000000000..3be00ceaa --- /dev/null +++ b/app/src/main/res/layout/bottom_set_wallpaper_actions.xml @@ -0,0 +1,38 @@ + + + + + + + + diff --git a/app/src/main/res/layout/bottom_video_time_holder.xml b/app/src/main/res/layout/bottom_video_time_holder.xml new file mode 100644 index 000000000..3a7b6a477 --- /dev/null +++ b/app/src/main/res/layout/bottom_video_time_holder.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_change_file_thumbnail_style.xml b/app/src/main/res/layout/dialog_change_file_thumbnail_style.xml new file mode 100644 index 000000000..5c0b8716d --- /dev/null +++ b/app/src/main/res/layout/dialog_change_file_thumbnail_style.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_change_folder_thumbnail_style.xml b/app/src/main/res/layout/dialog_change_folder_thumbnail_style.xml new file mode 100644 index 000000000..e29763f1a --- /dev/null +++ b/app/src/main/res/layout/dialog_change_folder_thumbnail_style.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_change_grouping.xml b/app/src/main/res/layout/dialog_change_grouping.xml new file mode 100644 index 000000000..c64aad625 --- /dev/null +++ b/app/src/main/res/layout/dialog_change_grouping.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_change_sorting.xml b/app/src/main/res/layout/dialog_change_sorting.xml index d0ab9b36f..96fb61ad5 100644 --- a/app/src/main/res/layout/dialog_change_sorting.xml +++ b/app/src/main/res/layout/dialog_change_sorting.xml @@ -11,8 +11,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingLeft="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" - android:paddingTop="@dimen/activity_margin"> + android:paddingTop="@dimen/activity_margin" + android:paddingRight="@dimen/activity_margin"> + + + + @@ -85,13 +101,30 @@ android:id="@+id/use_for_this_folder_divider" layout="@layout/divider"/> + + + + diff --git a/app/src/main/res/layout/dialog_change_view_type.xml b/app/src/main/res/layout/dialog_change_view_type.xml new file mode 100644 index 000000000..b6e1f342c --- /dev/null +++ b/app/src/main/res/layout/dialog_change_view_type.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_confirm_delete_folder.xml b/app/src/main/res/layout/dialog_confirm_delete_folder.xml new file mode 100644 index 000000000..220c36f09 --- /dev/null +++ b/app/src/main/res/layout/dialog_confirm_delete_folder.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/layout/dialog_custom_aspect_ratio.xml b/app/src/main/res/layout/dialog_custom_aspect_ratio.xml new file mode 100644 index 000000000..7650ee275 --- /dev/null +++ b/app/src/main/res/layout/dialog_custom_aspect_ratio.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_delete_with_remember.xml b/app/src/main/res/layout/dialog_delete_with_remember.xml new file mode 100644 index 000000000..2f9fc47d5 --- /dev/null +++ b/app/src/main/res/layout/dialog_delete_with_remember.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/layout/dialog_directory_picker.xml b/app/src/main/res/layout/dialog_directory_picker.xml index 0feb6bf5a..a3cc14860 100644 --- a/app/src/main/res/layout/dialog_directory_picker.xml +++ b/app/src/main/res/layout/dialog_directory_picker.xml @@ -1,10 +1,56 @@ - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_filter_media.xml b/app/src/main/res/layout/dialog_filter_media.xml new file mode 100644 index 000000000..f707967a2 --- /dev/null +++ b/app/src/main/res/layout/dialog_filter_media.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_manage_bottom_actions.xml b/app/src/main/res/layout/dialog_manage_bottom_actions.xml new file mode 100644 index 000000000..2ddeb0afc --- /dev/null +++ b/app/src/main/res/layout/dialog_manage_bottom_actions.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_manage_extended_details.xml b/app/src/main/res/layout/dialog_manage_extended_details.xml new file mode 100644 index 000000000..610b21304 --- /dev/null +++ b/app/src/main/res/layout/dialog_manage_extended_details.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_medium_picker.xml b/app/src/main/res/layout/dialog_medium_picker.xml index f2aebbd17..cc06575dc 100644 --- a/app/src/main/res/layout/dialog_medium_picker.xml +++ b/app/src/main/res/layout/dialog_medium_picker.xml @@ -1,10 +1,39 @@ - - + android:paddingTop="@dimen/activity_margin"> + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_other_aspect_ratio.xml b/app/src/main/res/layout/dialog_other_aspect_ratio.xml new file mode 100644 index 000000000..661245a51 --- /dev/null +++ b/app/src/main/res/layout/dialog_other_aspect_ratio.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/resize_image.xml b/app/src/main/res/layout/dialog_resize_image.xml similarity index 81% rename from app/src/main/res/layout/resize_image.xml rename to app/src/main/res/layout/dialog_resize_image.xml index 488c10135..97283e806 100644 --- a/app/src/main/res/layout/resize_image.xml +++ b/app/src/main/res/layout/dialog_resize_image.xml @@ -1,11 +1,13 @@ + android:paddingLeft="@dimen/activity_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingRight="@dimen/activity_margin"> + android:textCursorDrawable="@null" + android:textSize="@dimen/normal_text_size"/> + android:textCursorDrawable="@null" + android:textSize="@dimen/normal_text_size"/> diff --git a/app/src/main/res/layout/dialog_resize_image_with_path.xml b/app/src/main/res/layout/dialog_resize_image_with_path.xml new file mode 100644 index 000000000..c1defcff2 --- /dev/null +++ b/app/src/main/res/layout/dialog_resize_image_with_path.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_save_as.xml b/app/src/main/res/layout/dialog_save_as.xml index c40bd92ad..c490a44a1 100644 --- a/app/src/main/res/layout/dialog_save_as.xml +++ b/app/src/main/res/layout/dialog_save_as.xml @@ -18,10 +18,10 @@ android:id="@+id/save_as_path" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginStart="@dimen/activity_margin" android:layout_marginBottom="@dimen/activity_margin" - android:layout_marginLeft="@dimen/activity_margin" - android:paddingRight="@dimen/small_margin" - android:paddingTop="@dimen/small_margin"/> + android:paddingTop="@dimen/small_margin" + android:paddingEnd="@dimen/small_margin"/> + android:textCursorDrawable="@null" + android:textSize="@dimen/normal_text_size"/> + android:textCursorDrawable="@null" + android:textSize="@dimen/normal_text_size"/> diff --git a/app/src/main/res/layout/dialog_slideshow.xml b/app/src/main/res/layout/dialog_slideshow.xml new file mode 100644 index 000000000..86120da2c --- /dev/null +++ b/app/src/main/res/layout/dialog_slideshow.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/directory_item.xml b/app/src/main/res/layout/directory_item.xml deleted file mode 100644 index 9d4ef8a46..000000000 --- a/app/src/main/res/layout/directory_item.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/directory_item_grid_rounded_corners.xml b/app/src/main/res/layout/directory_item_grid_rounded_corners.xml new file mode 100644 index 000000000..0a6b516ff --- /dev/null +++ b/app/src/main/res/layout/directory_item_grid_rounded_corners.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/directory_item_grid_square.xml b/app/src/main/res/layout/directory_item_grid_square.xml new file mode 100644 index 000000000..f2a796539 --- /dev/null +++ b/app/src/main/res/layout/directory_item_grid_square.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/directory_item_list.xml b/app/src/main/res/layout/directory_item_list.xml new file mode 100644 index 000000000..ac3f617a3 --- /dev/null +++ b/app/src/main/res/layout/directory_item_list.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/directory_tmb.xml b/app/src/main/res/layout/directory_tmb.xml deleted file mode 100644 index f7bce5a6f..000000000 --- a/app/src/main/res/layout/directory_tmb.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/editor_filter_item.xml b/app/src/main/res/layout/editor_filter_item.xml new file mode 100644 index 000000000..dd3913336 --- /dev/null +++ b/app/src/main/res/layout/editor_filter_item.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/layout/fragment_holder.xml b/app/src/main/res/layout/fragment_holder.xml index 9a0863df0..6aea1be0a 100644 --- a/app/src/main/res/layout/fragment_holder.xml +++ b/app/src/main/res/layout/fragment_holder.xml @@ -1,6 +1,24 @@ - + android:layout_height="match_parent"> + + + + + + + + diff --git a/app/src/main/res/layout/item_manage_folder.xml b/app/src/main/res/layout/item_manage_folder.xml index ac706585a..fd3c8ca02 100644 --- a/app/src/main/res/layout/item_manage_folder.xml +++ b/app/src/main/res/layout/item_manage_folder.xml @@ -1,29 +1,20 @@ - - + android:layout_marginTop="@dimen/medium_margin"/> diff --git a/app/src/main/res/layout/pager_photo_item.xml b/app/src/main/res/layout/pager_photo_item.xml index 7d7cf1e82..2238ea26d 100644 --- a/app/src/main/res/layout/pager_photo_item.xml +++ b/app/src/main/res/layout/pager_photo_item.xml @@ -1,19 +1,110 @@ - - + android:layout_height="match_parent" /> + + + + + + + android:visibility="gone" /> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/pager_video_item.xml b/app/src/main/res/layout/pager_video_item.xml index 572e96c33..6e46132d8 100644 --- a/app/src/main/res/layout/pager_video_item.xml +++ b/app/src/main/res/layout/pager_video_item.xml @@ -1,71 +1,87 @@ - - + + + + + + + + + + + + android:padding="20dp" + android:src="@drawable/ic_play_outline_vector" /> - + + + + + + + android:layout_centerHorizontal="true" + android:layout_marginBottom="120dp" + android:alpha="0" + android:background="@drawable/black_rounded_background" + android:gravity="center" + android:paddingLeft="@dimen/activity_margin" + android:paddingTop="@dimen/medium_margin" + android:paddingRight="@dimen/activity_margin" + android:paddingBottom="@dimen/medium_margin" + android:textColor="@android:color/white" + android:textSize="@dimen/extra_big_text_size" /> - - - - - - - diff --git a/app/src/main/res/layout/photo_video_item.xml b/app/src/main/res/layout/photo_video_item.xml deleted file mode 100644 index f5a4bfea3..000000000 --- a/app/src/main/res/layout/photo_video_item.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/photo_video_item_grid.xml b/app/src/main/res/layout/photo_video_item_grid.xml new file mode 100644 index 000000000..cc3fc718d --- /dev/null +++ b/app/src/main/res/layout/photo_video_item_grid.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/photo_video_item_list.xml b/app/src/main/res/layout/photo_video_item_list.xml new file mode 100644 index 000000000..7346f892e --- /dev/null +++ b/app/src/main/res/layout/photo_video_item_list.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/photo_video_tmb.xml b/app/src/main/res/layout/photo_video_tmb.xml deleted file mode 100644 index 531a2533d..000000000 --- a/app/src/main/res/layout/photo_video_tmb.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/portrait_photo_item.xml b/app/src/main/res/layout/portrait_photo_item.xml new file mode 100644 index 000000000..21b42ab42 --- /dev/null +++ b/app/src/main/res/layout/portrait_photo_item.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/main/res/layout/thumbnail_section.xml b/app/src/main/res/layout/thumbnail_section.xml new file mode 100644 index 000000000..65d51f7df --- /dev/null +++ b/app/src/main/res/layout/thumbnail_section.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/view_crop_image.xml b/app/src/main/res/layout/view_crop_image.xml deleted file mode 100644 index a956b2213..000000000 --- a/app/src/main/res/layout/view_crop_image.xml +++ /dev/null @@ -1,9 +0,0 @@ - - diff --git a/app/src/main/res/layout/widget.xml b/app/src/main/res/layout/widget.xml new file mode 100644 index 000000000..87e79a942 --- /dev/null +++ b/app/src/main/res/layout/widget.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/app/src/main/res/menu/cab_directories.xml b/app/src/main/res/menu/cab_directories.xml index 2267e4c6e..3e9c55cbd 100644 --- a/app/src/main/res/menu/cab_directories.xml +++ b/app/src/main/res/menu/cab_directories.xml @@ -1,6 +1,11 @@ + - + android:id="@+id/cab_create_shortcut" + android:title="@string/create_shortcut" + app:showAsAction="never"/> + + + + + diff --git a/app/src/main/res/menu/cab_hidden_folders.xml b/app/src/main/res/menu/cab_hidden_folders.xml new file mode 100644 index 000000000..938f82f7f --- /dev/null +++ b/app/src/main/res/menu/cab_hidden_folders.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/menu/cab_media.xml b/app/src/main/res/menu/cab_media.xml index 4b47e6025..cb8dda1e9 100644 --- a/app/src/main/res/menu/cab_media.xml +++ b/app/src/main/res/menu/cab_media.xml @@ -1,16 +1,38 @@ + + + + + + + + - - + + + + + + + + + diff --git a/app/src/main/res/menu/menu_excluded_folders.xml b/app/src/main/res/menu/menu_add_folder.xml similarity index 86% rename from app/src/main/res/menu/menu_excluded_folders.xml rename to app/src/main/res/menu/menu_add_folder.xml index 4671dcd0f..3551a8a4d 100644 --- a/app/src/main/res/menu/menu_excluded_folders.xml +++ b/app/src/main/res/menu/menu_add_folder.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> diff --git a/app/src/main/res/menu/menu_editor.xml b/app/src/main/res/menu/menu_editor.xml index eeeb84d73..16a5f7806 100644 --- a/app/src/main/res/menu/menu_editor.xml +++ b/app/src/main/res/menu/menu_editor.xml @@ -3,31 +3,17 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> - - - - - - + android:id="@+id/share" + android:icon="@drawable/ic_share_vector" + android:title="@string/share" + app:showAsAction="ifRoom"/> diff --git a/app/src/main/res/menu/menu_included_folders.xml b/app/src/main/res/menu/menu_included_folders.xml index 4671dcd0f..3551a8a4d 100644 --- a/app/src/main/res/menu/menu_included_folders.xml +++ b/app/src/main/res/menu/menu_included_folders.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 01937ceb9..646b3f165 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -1,38 +1,73 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:id="@+id/search" + android:icon="@drawable/ic_search_vector" + android:title="@string/search" + app:actionViewClass="androidx.appcompat.widget.SearchView" + app:showAsAction="collapseActionView|always" /> + app:showAsAction="ifRoom" /> + android:id="@+id/sort" + android:icon="@drawable/ic_sort_vector" + android:title="@string/sort_by" + app:showAsAction="ifRoom" /> + + + app:showAsAction="never" /> + + + + + + + app:showAsAction="never" /> + app:showAsAction="never" /> + app:showAsAction="never" /> + app:showAsAction="never" /> diff --git a/app/src/main/res/menu/menu_main_intent.xml b/app/src/main/res/menu/menu_main_intent.xml index f35d3fdb8..30df345bd 100644 --- a/app/src/main/res/menu/menu_main_intent.xml +++ b/app/src/main/res/menu/menu_main_intent.xml @@ -5,4 +5,8 @@ android:id="@+id/temporarily_show_hidden" android:title="@string/temporarily_show_hidden" app:showAsAction="never"/> + diff --git a/app/src/main/res/menu/menu_media.xml b/app/src/main/res/menu/menu_media.xml index 8a0cc4b29..1c4c61146 100644 --- a/app/src/main/res/menu/menu_media.xml +++ b/app/src/main/res/menu/menu_media.xml @@ -1,55 +1,94 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:id="@+id/search" + android:icon="@drawable/ic_search_vector" + android:title="@string/search" + app:actionViewClass="androidx.appcompat.widget.SearchView" + app:showAsAction="collapseActionView|ifRoom" /> + app:showAsAction="ifRoom" /> + android:id="@+id/sort" + android:icon="@drawable/ic_sort_vector" + android:title="@string/sort_by" + app:showAsAction="ifRoom" /> + android:id="@+id/filter" + android:icon="@drawable/ic_filter_vector" + android:title="@string/filter_media" + app:showAsAction="ifRoom" /> - - + android:id="@+id/change_view_type" + android:title="@string/change_view_type" + app:showAsAction="never" /> + app:showAsAction="never" /> + + + + + + + + + + + app:showAsAction="never" /> + app:showAsAction="never" /> + + app:showAsAction="never" /> + app:showAsAction="never" /> diff --git a/app/src/main/res/menu/menu_search.xml b/app/src/main/res/menu/menu_search.xml new file mode 100644 index 000000000..c695b68da --- /dev/null +++ b/app/src/main/res/menu/menu_search.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/menu/menu_set_wallpaper.xml b/app/src/main/res/menu/menu_set_wallpaper.xml index 59328d99d..a10009346 100644 --- a/app/src/main/res/menu/menu_set_wallpaper.xml +++ b/app/src/main/res/menu/menu_set_wallpaper.xml @@ -3,22 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> - - - diff --git a/app/src/main/res/menu/menu_video_player.xml b/app/src/main/res/menu/menu_video_player.xml new file mode 100644 index 000000000..62d8c48c4 --- /dev/null +++ b/app/src/main/res/menu/menu_video_player.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/menu/menu_viewpager.xml b/app/src/main/res/menu/menu_viewpager.xml index 96dd6b3c1..d0913803e 100644 --- a/app/src/main/res/menu/menu_viewpager.xml +++ b/app/src/main/res/menu/menu_viewpager.xml @@ -1,39 +1,59 @@ - - - - - - - + + + + + + + + + + + - + android:id="@+id/menu_set_as" + android:icon="@drawable/ic_set_as_vector" + android:title="@string/set_as" + app:showAsAction="ifRoom"/> + + + + + + + + + + + diff --git a/app/src/main/res/menu/photo_video_menu.xml b/app/src/main/res/menu/photo_video_menu.xml index 4cfd9dfa1..eb7372b40 100644 --- a/app/src/main/res/menu/photo_video_menu.xml +++ b/app/src/main/res/menu/photo_video_menu.xml @@ -3,20 +3,30 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + + android:id="@+id/menu_set_as" + android:icon="@drawable/ic_set_as_vector" + android:title="@string/set_as" + app:showAsAction="ifRoom"/> + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..a77f72238 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml new file mode 100644 index 000000000..dab4c0c59 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml new file mode 100644 index 000000000..37bf057fa --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml new file mode 100644 index 000000000..3e4d0696b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml new file mode 100644 index 000000000..9786d7bf0 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml new file mode 100644 index 000000000..afb3d0d36 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml new file mode 100644 index 000000000..1846b81de --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml new file mode 100644 index 000000000..4152801a0 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml new file mode 100644 index 000000000..e55d10929 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml new file mode 100644 index 000000000..40d074503 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml new file mode 100644 index 000000000..601d81701 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml new file mode 100644 index 000000000..01f2fead9 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml new file mode 100644 index 000000000..d37b24c44 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml new file mode 100644 index 000000000..9fd7bc5b2 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml new file mode 100644 index 000000000..d2adf9a07 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml new file mode 100644 index 000000000..32c838cdd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml new file mode 100644 index 000000000..a6e935994 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml new file mode 100644 index 000000000..18492d282 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml new file mode 100644 index 000000000..854427e06 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index 26e7bfbd2..88e7928d9 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_amber.png b/app/src/main/res/mipmap-hdpi/ic_launcher_amber.png new file mode 100644 index 000000000..d6aa3f323 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_amber.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_blue.png b/app/src/main/res/mipmap-hdpi/ic_launcher_blue.png new file mode 100644 index 000000000..127e37d37 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_blue.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_blue_grey.png b/app/src/main/res/mipmap-hdpi/ic_launcher_blue_grey.png new file mode 100644 index 000000000..473400bc0 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_blue_grey.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_brown.png b/app/src/main/res/mipmap-hdpi/ic_launcher_brown.png new file mode 100644 index 000000000..c74a80c87 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_brown.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_cyan.png b/app/src/main/res/mipmap-hdpi/ic_launcher_cyan.png new file mode 100644 index 000000000..ec0979f15 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_cyan.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_deep_orange.png b/app/src/main/res/mipmap-hdpi/ic_launcher_deep_orange.png new file mode 100644 index 000000000..895d2fab7 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_deep_orange.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_deep_purple.png b/app/src/main/res/mipmap-hdpi/ic_launcher_deep_purple.png new file mode 100644 index 000000000..3b7f015e5 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_deep_purple.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..e5c2534d4 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_green.png b/app/src/main/res/mipmap-hdpi/ic_launcher_green.png new file mode 100644 index 000000000..433cde253 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_green.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_grey_black.png b/app/src/main/res/mipmap-hdpi/ic_launcher_grey_black.png new file mode 100644 index 000000000..a83fa0af3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_grey_black.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_indigo.png b/app/src/main/res/mipmap-hdpi/ic_launcher_indigo.png new file mode 100644 index 000000000..c52966808 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_indigo.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_light_blue.png b/app/src/main/res/mipmap-hdpi/ic_launcher_light_blue.png new file mode 100644 index 000000000..928e47ee1 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_light_blue.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_light_green.png b/app/src/main/res/mipmap-hdpi/ic_launcher_light_green.png new file mode 100644 index 000000000..00a1ea985 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_light_green.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_lime.png b/app/src/main/res/mipmap-hdpi/ic_launcher_lime.png new file mode 100644 index 000000000..3c5f0148b Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_lime.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_pink.png b/app/src/main/res/mipmap-hdpi/ic_launcher_pink.png new file mode 100644 index 000000000..e3d98241f Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_pink.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_purple.png b/app/src/main/res/mipmap-hdpi/ic_launcher_purple.png new file mode 100644 index 000000000..6eae4fb39 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_purple.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_red.png b/app/src/main/res/mipmap-hdpi/ic_launcher_red.png new file mode 100644 index 000000000..f3a0161fe Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_red.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_teal.png b/app/src/main/res/mipmap-hdpi/ic_launcher_teal.png new file mode 100644 index 000000000..9b392844d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_teal.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_yellow.png b/app/src/main/res/mipmap-hdpi/ic_launcher_yellow.png new file mode 100644 index 000000000..5475ffe4c Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_yellow.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 456d42d33..f645b6dae 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_amber.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_amber.png new file mode 100644 index 000000000..dfff1b939 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_amber.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_blue.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_blue.png new file mode 100644 index 000000000..501d29e21 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_blue.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_blue_grey.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_blue_grey.png new file mode 100644 index 000000000..10101e7e8 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_blue_grey.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_brown.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_brown.png new file mode 100644 index 000000000..0ad858fa5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_brown.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_cyan.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_cyan.png new file mode 100644 index 000000000..58c6ebd81 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_cyan.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_deep_orange.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_deep_orange.png new file mode 100644 index 000000000..d98d0aa96 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_deep_orange.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_deep_purple.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_deep_purple.png new file mode 100644 index 000000000..8ae82a7e3 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_deep_purple.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..751916395 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_green.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_green.png new file mode 100644 index 000000000..c4a247955 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_green.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_grey_black.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_grey_black.png new file mode 100644 index 000000000..2e95a75f5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_grey_black.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_indigo.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_indigo.png new file mode 100644 index 000000000..27e1bb8f0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_indigo.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_light_blue.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_light_blue.png new file mode 100644 index 000000000..a261aecd5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_light_blue.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_light_green.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_light_green.png new file mode 100644 index 000000000..1cb9abe2c Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_light_green.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_lime.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_lime.png new file mode 100644 index 000000000..9a90e56bb Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_lime.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_pink.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_pink.png new file mode 100644 index 000000000..9df1e5f2d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_pink.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_purple.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_purple.png new file mode 100644 index 000000000..810de58d8 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_purple.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_red.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_red.png new file mode 100644 index 000000000..8de0fb064 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_red.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_teal.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_teal.png new file mode 100644 index 000000000..22e6269b4 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_teal.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_yellow.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_yellow.png new file mode 100644 index 000000000..77d25aa22 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_yellow.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index c1aff4f42..3151de093 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_amber.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_amber.png new file mode 100644 index 000000000..7fdb61068 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_amber.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_blue.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_blue.png new file mode 100644 index 000000000..a0bf57559 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_blue.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_blue_grey.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_blue_grey.png new file mode 100644 index 000000000..3a0383642 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_blue_grey.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png new file mode 100644 index 000000000..0a27d625a Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_cyan.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_cyan.png new file mode 100644 index 000000000..fc247a08a Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_cyan.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_deep_orange.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_deep_orange.png new file mode 100644 index 000000000..bbcfa0235 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_deep_orange.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_deep_purple.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_deep_purple.png new file mode 100644 index 000000000..a30512892 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_deep_purple.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..0572eaa1a Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_green.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_green.png new file mode 100644 index 000000000..2ec7ce353 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_green.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_grey_black.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_grey_black.png new file mode 100644 index 000000000..6872df0a6 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_grey_black.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_indigo.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_indigo.png new file mode 100644 index 000000000..7463e54c0 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_indigo.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_light_blue.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_light_blue.png new file mode 100644 index 000000000..49122cd01 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_light_blue.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_light_green.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_light_green.png new file mode 100644 index 000000000..a7614e913 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_light_green.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_lime.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_lime.png new file mode 100644 index 000000000..3b3a3b564 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_lime.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_pink.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_pink.png new file mode 100644 index 000000000..b49c6822d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_pink.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png new file mode 100644 index 000000000..049d8f53e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_red.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_red.png new file mode 100644 index 000000000..50d16eb14 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_red.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_teal.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_teal.png new file mode 100644 index 000000000..9642c6098 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_teal.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_yellow.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_yellow.png new file mode 100644 index 000000000..75ca1c532 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_yellow.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index c8ab44455..516d12f56 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_amber.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_amber.png new file mode 100644 index 000000000..8b0388201 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_amber.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_blue.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_blue.png new file mode 100644 index 000000000..8454f5828 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_blue.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_blue_grey.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_blue_grey.png new file mode 100644 index 000000000..f3658ed18 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_blue_grey.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png new file mode 100644 index 000000000..c8c572de0 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_cyan.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_cyan.png new file mode 100644 index 000000000..666b77ddb Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_cyan.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_orange.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_orange.png new file mode 100644 index 000000000..b40a51218 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_orange.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_purple.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_purple.png new file mode 100644 index 000000000..fd5a836e4 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_purple.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..015dd1e4c Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_green.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_green.png new file mode 100644 index 000000000..64c77f639 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_green.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_grey_black.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_grey_black.png new file mode 100644 index 000000000..66a4f4198 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_grey_black.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_indigo.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_indigo.png new file mode 100644 index 000000000..d990c5c6d Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_indigo.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_light_blue.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_light_blue.png new file mode 100644 index 000000000..5a9155abd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_light_blue.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_light_green.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_light_green.png new file mode 100644 index 000000000..59020780a Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_light_green.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_lime.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_lime.png new file mode 100644 index 000000000..9057961da Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_lime.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pink.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pink.png new file mode 100644 index 000000000..b281365e6 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pink.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png new file mode 100644 index 000000000..bbfb8ba63 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png new file mode 100644 index 000000000..f1b0bf2af Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_teal.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_teal.png new file mode 100644 index 000000000..09fced1b9 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_teal.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_yellow.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_yellow.png new file mode 100644 index 000000000..382dc6271 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_yellow.png differ diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml new file mode 100644 index 000000000..a1ca5707b --- /dev/null +++ b/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,426 @@ + + + الاستديو البسيط + الاستديو + تعديل + فتح الكاميرا + (مخفي) + (مستبعد) + تثبيت المجلد + إلغاء تثبيت المجلد + تثبيت في الأعلى + إظهار كل محتويات المجلدات + كل المجلدات + التبديل إلى عرض المجلد + مجلد آخر + عرض علي الخريطة + موقع غير معروف + الصوت + السطوع + قفل الاتجاه + فتح الاتجاه + تغيير الاتجاه + فرض الإتجاه العمودي + فرض الإتجاه الأفقي + إستخدام الإتجاه الافتراضي + إصلاح تاريخ إلتقاط القيمة + جارٍ الإصلاح… + تم إصلاح التواريخ بنجاح + لم يتم العثور على تاريخ التقاط القيم + مشاركة الصورة الذي تم تغيير حجمها + مرحباً,\n\nيبدو أنك قمت بترقية التطبيق المجاني القديم. يمكنك الآن إلغاء تثبيت الإصدار القديم، الذي يحتوي على زر \'الترقية إلى Pro\' في أعلي إعدادات التطبيق.\n\nسيتم حذف عناصر سلة المحذوفات فقط، كما سيتم حذف العناصر المفضلة، كما سيتعين عليك إعادة ضبط إعدادات التطبيق .\n\nشكراً! + التبديل إلي البحث فى الملفات عبر كافة المجلدات المرئية + Set as default folder + Unset as default folder + + + فلترة الوسائط + الصور + الفيديوهات + الصور المتحركة (GIF) + صور الخام (RAW) + صور الرسومات المتجهة (SVG) + صور البورتريه + لم يتم العثور على ملفات وسائط مع الفلاتر المحددة. + تغيير الفلاتر + + + تقوم هذه الوظيفة باخفاء المجلد عن طريق إضافة ملف \'. nomedia \' إليه ، وستقوم باخفاء كل المجلدات الفرعية أيضًا. يمكنك رؤيتهم عن طريق تبديل خيار \'إظهار العناصر المخفية \' في الإعدادات. هل تريد المتابعة؟ + إستبعاد + المجلدات المستبعدة + إدارة المجلدات المستبعدة + سيؤدي ذلك الي إستبعاد التحديد مع المجلدات الفرعية من "الاستديو" فقط.. يمكنك إدارة المجلدات المستبعدة في الإعدادات. + إستبعاد المجلد الأصل بدلاً من ذلك(المحدد)؟ + سيؤدي إستبعاد المجلدات الي جعلها مع المجلدات الفرعية مخفية في "الاستديو" فقط, وستظل مرئية في تطبيقات أخرى. \n\nإذا كنت تريد إخفاءها من التطبيقات الأخرى أيضاً ، فاستخدم وظيفة الإخفاء. + إزالة الكل + هل تريد إزالة كافة المجلدات من قائمة المجلدات المستبعدة ؟ لن يؤدي هذا إلي حذف المجلدات. + المجلدات المخفية + إدارة المجلدات المخفية + يبدو أنه لا توجد لديك أي مجلدات مخفية بملف \".nomedia\". + + + المجلدات المضمنة + إدارة المجلدات المضمنة + إضافة مجلد + إذا كان لديك بعض المجلدات التي تحتوي على الوسائط ، ولكن لم يتم التعرف عليها من قبل التطبيق، يمكنك إضافتها يدوياً هنا.\n\nلن تؤدي إضافة بعض العناصر هنا إلى إستبعاد أي مجلد آخر. + لم يتم العثور علي أي ملفات وسائط.يمكنك حلها عن طريق إضافة المجلدات التي تحتوي على ملفات وسائط يدوياً. + + + تغيير الحجم + تغيير حجم التحديد والحفظ + العرض + الإرتفاع + الحفاظ على نسبة الأبعاد + يرجى إدخال دقة صالحة + + + تعديل + تدوير + مسار الصورة غير صحيح + Invalid video path + فشل تعديل الصورة + Video editing failed + تم إلغاء تعديل الصورة + Video editing cancelled + تم تعديل الملف بنجاح + Image edited successfully + Video edited successfully + تعديل الصورة باستخدام: + Edit video with: + لم يتم العثور على أي محرر للصور + No video editor found + موقع الملف غير معروف + لا يمكن إستبدال الملف الأصل + تدوير لليسار + تدوير لليمين + تدوير بزاوية 180º + انعكاس + انعكاس أفقياً + انعكاس عمودياً + حر + أخري + + + خلفية بسيطة + تعيين كخلفية + فشل التعيين كخلفية + تعيين كخلفية باستخدام: + جارٍ تعيين الخلفية… + تم تعيين الخلفية بنجاح + نسبة الأبعاد العمودية + نسبة الأبعاد الأفقية + الشاشة الرئيسية + شاشة القفل + الشاشة الرئيسية وشاشة القفل + + + عرض الشرائح + الفاصل الزمني (بالثواني): + تضمين الصور + تضمين مقاطع الفيديو + تضمين الصور المتحركة (Gif) + ترتيب عشوائي + تحريك للخلف + حلقة عرض الشرائح + الحركة + لا شيء + تلاشى + انزلاق + أنتهى عرض الشرائح + لم يتم العثور علي أي وسائط لعرض الشرائح + + + تجميع المجلدات الفرعية مباشرة + + + تجميع حسب + عدم تجميع الملفات + المجلد + آخر تعديل + آخر تعديل (يومياً) + آخر تعديل (شهرياً) + تاريخ الإلتقاط + تاريخ الإلتقاط (يومياً) + آخر تعديل (شهرياً) + نوع الملف + الإمتداد + يرجى ملاحظة أن التجميع والفرز هما حقلان مستقلان + + + المجلد الظاهر فى الويدجت: + إظهار اسم المجلد + + + تشغيل مقاطع الفيديو تلقائياً + تذكر آخر موضع لتشغيل الفيديو + تبديل رؤية إسم الملف + حلقة مقاطع الفيديو + تحريك الصور المتحركة (Gif) في الصور المصغرة + أقصى سطوع عند عرض وسائط ملء الشاشة + إقتصاص الصور المصغرة إلى مربعات + إظهار مدة الفيديوهات + تدوير وسائط ملء الشاشة باستخدام + إعدادات النظام + تدوير الجهاز + نسبة الأبعاد + جعل الخلفية باللون الأسود في وسائط ملء الشاشة + تمرير الصور المصغرة أفقياً + إخفاء واجهة مستخدم النظام تلقائياً في وضع ملء الشاشة + حذف المجلدات الفارغة بعد حذف محتواها + السماح بالتحكم في سطوع الصور باستخدام الإيماءات العمودية + السماح بالتحكم في مستوي صوت الفيديو والسطوع باستخدام الإيماءات العمودية + إظهار عدد وسائط المجلد في العرض الرئيسي + إظهار التفاصيل الموسعة عبر وسائط ملء الشاشة + إدارة التفاصيل الموسعة + السماح بتكبير الوسائط بأصبع واحد في وضع ملء الشاشة + السماح بتغيير الوسائط علي الفور من خلال النقر علي جوانب الشاشة + السماح بالتكبير العميق للصور + إخفاء التفاصيل الموسعة عند إخفاء شريط الحالة + إظهار بعض أزرار الإجراءات في أسفل الشاشة + إظهار "سلة المحذوفات" في شاشة المجلدات + الصور القابلة للتكبير العميق + عرض الصور بأعلى جودة ممكنة + إظهار "سلة المحذوفات" كعنصر آخر علي الشاشة الرئيسية + السماح بإغلاق عرض ملء الشاشة باستخدام إيماءه لأسفل + السماح بالتكبير 1:1 باستخدام النقر مزدوجاّ + فتح مقاطع الفيديو دائماً علي شاشة منفصلة مع إيماءات أفقية جديدة + عرض النوتش إذا كان متوفر + السماح بتدوير الصور باستخدام الإيماءات + أولوية تحميل الملفات + بسرعة + إختيار وسط + تجنب إظهار الملفات غير الصالحة + إظهار أنواع ملفات الصور + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + الصور المصغرة + وسائط ملء الشاشة + التفاصيل الموسعة + إجراءات الأزرار السفلية + + + إدارة إجراءات الأزرار المرئية + تبديل المفضل + تبديل رؤية الملف + + + مخصص + إعادة تعيين + مربع + تحويل + فلتر + لا شيء + ضبط + ظلال + التعرض + أكثر وضوحاً + السطوع + التباين + التشبع + الوضوح + جاما + دراجات الأسود + دراجات الأبيض + درجة الحرارة + الحدة + إعادة تعيين + تركيز + لا شيء + قطري + خطي + مرآة + جاوس + النص + خيارات النص + لون الخط + الخط + إضافة + تعديل + الصلابة + الخط + اللون + لون الخلفية + محاذاة + إلي الأمام + حذف + النص الخاص بك + الفرشاة + اللون + الحجم + الصلابة + إلي الأمام + حذف + لون الفرشاة + المحرر + هل تريد إغلاق المحرر؟ + Do you really want to discard the changes? + نعم + لا + إلغاء + موافق + حفظ + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + أنعكاس أفقي + أنعكاس رأسي + تراجع + إعادة + لاقط اللون + شفاف + أبيض + رمادي + أسود + أزرق فاتح + أزرق + بنفسجي + أرجواني + وردي + أحمر + برتقالي + ذهبي + أصفر + زيتوني + أخضر + زبرجد + Pipettable لون + Trim + + + كيف يمكنني جعل تطبيق الاستديو معرض الجهاز الإفتراضي ؟ + أولا، يجب عليك العثور على المعرض الإفتراضي الحالي في قسم "التطبيقات" من إعدادات الجهاز، أبحث عن زر يقول شيئاً مثل \"فتح كافتراضي/"، ثم أنقر عليه، ثم حدد \"مسح الإعدادات الإفتراضية\". في المرة القادمة التي ستحاول فيها فتح صورة أو مقطع فيديو يجب أن تري إختيار تطبيق، حيث يمكنك تحديد تطبيق "الاستديو" وجعله التطبيق الإفتراضي. + لقد أغلقت التطبيق بكلمة مرور، لكنني نسيت ذلك. ماذا يمكنني أن أفعل؟ + يمكنك حلها بطريقتين. يمكنك أما إعادة تثبيت التطبيق أو أبحث عن التطبيق في اعدادات جهازك وحدد \"مسح البيانات\". سيتم إعادة تعيين كل إعداداتك، ولن يقوم بإزالة أي ملفات وسائط. + كيف يمكنني جعل ألبوماً يظهر دائماً في الأعلى ؟ + يمكنك الضغط مطولاً علي الألبوم المطلوب وحدد أيقونة "التثبيت" في قائمة الإجراءات، التي ستقوم بتثبيته في الأعلي. يمكنك تثبيت مجلدات متعددة أيضا، سيتم فرز العناصر المثبتة حسب طريقة الفرز الإفتراضية. + كيف يمكنني تسريع مقاطع الفيديو؟ + يمكنك القيام بذلك عن طريق النقر المزدوج على جانب الشاشة, أو النقر على نصوص المدة الحالية أو القصوى بالقرب من شريط التمرير. إذا قمت بتمكين فتح مقاطع الفيديو على شاشة منفصلة في إعدادات التطبيق، يمكنك استخدام الإيماءات الأفقية أيضاً. + ما هو الفرق بين إخفاء المجلد وإستبعاده ؟ + لاحظ أن بعض الأجهزة لا تسمح بإخفاء المجلدات مثل الكاميرا ولقطات الشاشة والتنزيلات. الإستبعاد يمنع عرض المجلد فقط في الاستديو، بينما الإخفاء يعمل على مستوى النظام ويخفي المجلد من المعارض الأخرى أيضاً. يعمل عن طريق إنشاء ملف فارغ \ ".nomedia \" في المجلد المحدد ، والذي يمكنك إزالته بعد ذلك بواسطة أي مدير ملفات أيضًا. + لماذا تظهر المجلدات مع صورة غلاف الموسيقى أو الملصقات؟ + قد يحدث أن ترى بعض الألبومات غير العادية تظهر. يمكنك بسهولة إستبعادهم بالضغط مطولاً عليهم وأختر إستبعاد. في مربع الحوار التالي يمكنك بعد ذلك تحديد المجلد الاصل، من المحتمل ان يمنع ظهور الألبومات الأخري ذات الصلة أيضاً. + المجلد الذي به صور لا يظهر، أو لا يظهر كل العناصر. ماذا يمكنني أن أفعل؟ + قد يكون لهذا عدة أسباب، ولكن حل هذه المشكلة أمر سهل. فقط أذهب إلى الإعدادات -> إدارة المجلدات المضمنة، وأختر علامة زائد (+) وأنتقل إلى المجلد المطلوب. + ماذا أفعل لو كنت أريد بعض المجلدات المحددة مرئية فقط؟ + إضافة مجلد إلى المجلدات المضمنة لا يستبعد تلقائياً أي شيء. ما يمكنك القيام به هو الإنتقال إلى الإعدادات -> إدارة المجلدات المستبعدة، إستبعاد المجلد الأصل \"/\"، ثم إضافة المجلدات المطلوبة في الإعدادات -> إدارة المجلدات المضمنة. سيؤدي ذلك إلى جعل المجلدات المحددة مرئية فقط، حيث أن كل من الاستبعاد والتضمين منعكسين وإذا كان أحد المجلدات مستبعداً ومضمن معاً، فسوف يظهر. + هل يمكنني إقتصاص الصور باستخدام هذا التطبيق؟ + نعم ، يمكنك إقتصاص الصور في المحرر ، عن طريق سحب زوايا الصورة. يمكنك الوصول إلى المحرر إما بالضغط مطولاً على الصورة المصغرة وأختر تعديل ، أو أختر تعديل من عرض ملء الشاشة. + هل يمكنني بطريقة ما تجميع الصور المصغرة لملف الوسائط؟ + بالتأكيد، فقط تستخدم عنصر القائمة \"تجميع حسب\" أثناء عرض الصور المصغرة.يمكنك تجميع الملفات حسب معايير متعددة، بما في ذلك تاريخ الإلتقاط. إذا كنت تستخدم وظيفة \"إظهار كل محتويات المجلدات\" فيمكنك تجميعها حسب المجلدات أيضا. + يبدو ان الفرز حسب تاريخ الإلتقاط لا يعمل بشكل صحيح، كيف يمكنني إصلاحه؟ + غالباً ما يكون ذلك بسبب الملفات التي يتم نسخها من مكان ما. يمكنك اصلاحه عن طريق تحديد الصور المصغرة للملف وأختر \"إصلاح تاريخ إلتقاط القيمة\". + أرى بعض التباين اللونى على الصور. كيف يمكنني تحسين الجودة؟ + الحل الحالي لعرض الصور يعمل بشكل جيد في الأغلبية العظمي للحالات، ولكن إذا كنت تريد تحسين جودة الصور، يمكنك تمكين خيار \"عرض الصور بأعلي جودة ممكنة\" من اعدادات التطبيق، من قسم \"الصور العميقة القابلة للتكبير\". + لقد قمت بإخفاء ملف/مجلد. كيف يمكنني إظهاره؟ + يمكنك أما أن تضغط علي عنصر القائمة "إظهار العناصر المخفية مؤقتاً" علي الشاشه الرئيسية أو تفعيل خيار \"إظهار العناصر المخفية" في إعدادات التطبيق لرؤية العنصر المخفي. إذا كنت تريد إظهاره ، فقط أضغط مطولاً عليه وأختر \" إظهار\". يتم إخفاء المجلدات عن طريق إضافة ملف مخفي ". nomedia/" إليهم، يمكنك حذف الملف بواسطة أي مدير ملفات أيضاً. + لماذا يشغل التطبيق مساحة كبيرة؟ + يمكن لذاكرة التخزين المؤقت للتطبيق أن تصل سعتها إلى 250 ميجابايت، ، مما يضمن سرعة تحميل الصور. إذا كان التطبيق يشغل مساحة أكبر ، فمن المحتمل أن يكون ذلك بسبب وجود عناصر في "سلة المحذوفات". يتم حساب هذه الملفات إلى حجم التطبيق. يمكنك مسح "سلة المحذوفات" عن طريق فتحها وحذف جميع الملفات ، أو من إعدادات التطبيق. يتم حذف كل ملف في "سلة المحذوفات" تلقائياً بعد 30 يوماً. + + + + المعرض البسيط (Pro) - محرر ومدير الصور + + أستعرض ذكرياتك دون أي انقطاع مع معرض الصور والفيديو هذا + + (تطبيق المعرض البسيط الـ Pro) هو معرض بدون إنترنت قابل للتخصيص بدرجة كبيرة يتمتع بتنظيم الصور وتحريرها وإستعادة الملفات المحذوفة من خلال "سلة المحذوفات" وحماية الملفات وإخفاؤها وعرض مجموعة كبيرة من تنسيقات الصور والفيديو المختلفة بما في ذلك تنسيقات RAW و SVG وغير ذلك الكثير. + + لا يحتوي التطبيق على إعلانات وأذونات غير ضرورية. نظراً لأن التطبيق لا يتطلب الوصول إلى الإنترنت أيضاً ، فإن خصوصيتك محمية. + + ------------------------------------------------- + المعرض البسيط الـ Pro - الميزات + ------------------------------------------------- + + معرض بدون إنترنت بدون إعلانات أو نوافذ منبثقة + • محرر صور (معرض البسيط) - قص، تدوير، تغيير حجم، رسم، فلاتر وغير ذلك + • لا حاجة للوصول إلى الإنترنت، مما يمنحك المزيد من الخصوصية والأمان + + • لا توجد أذونات غير ضرورية مطلوبة + • البحث بسرعة عن الصور ومقاطع الفيديو والملفات + • فتح وعرض العديد من أنواع مختلفة من الصور والفيديو (RAW ، SVG ، بانورامية الخ) + • مجموعة متنوعة من الإيماءات البسيطة لتحرير الملفات وتنظيمها بسهولة + • الكثير من الطرق لفلترة الملفات وتجميعها وفرزها + • تخصيص مظهر للتطبيق + • متوفر بـ ٣٢ لغة + • وضع علامة علي الملفات كمفضلة للوصول السريع + • تمتع بحماية صورك ومقاطع الفيديو باستخدام نمط أو رمز PIN أو بصمة إصبع + • أستخدم رمز الـ PIN والنقش وبصمة الاصبع لحماية تشغيل التطبيق أو وظائف معينة أيضاً + • إستعادة الصور ومقاطع الفيديو المحذوفة من "سلة المحذوفات" + • تبديل رؤية الملفات لإخفاء الصور ومقاطع الفيديو + • إنشاء عرض شرائح قابل للتخصيص لملفاتك + • عرض معلومات تفصيلية لملفاتك (دقة الوضوح، قيم EXIF، إلخ) + • تطبيق المعرض هو مصدر مفتوح + … والكثير الكثير! + محرر معرض الصور + يساعد (تطبيق المعرض البسيط الـ Pro ) على تسهيل تحرير الصور بسرعة فائقة. قص الصور وقلبها وتدويرها وتغيير حجمها. إذا كنت تشعر بقدر أكبر من الإبداع، يمكنك إضافة الفلاتر والرسم على صورك! + + + يدعم العديد من انواع الملفات + على عكس بعض تطبيقات معرض الصور الأخري ومنظمي الصور ، يدعم (تطبيق المعرض البسيط الـ Pro )مجموعة كبيرة من أنواع الملفات المختلفة بما في ذلك JPEG و PNG و MP4 و MKV و RAW و SVG والصور البانورامية ومقاطع الفيديو البانورامية وغيرها الكثير. + + مدير معرض الصور القابل للتخصيص بدرجة كبيرة + بدءاً من واجهة المستخدم وحتى أزرار الوظائف الموجودة على شريط الأدوات السفلي، يتسم (تطبيق المعرض البسيط الـ Pro ) بأنه قابل للتخصيص بدرجة كبيرة ويعمل بالطريقة التي تريدها في الإستخدام. لا يوجد أي مدير معرض آخر يتمتع بهذا النوع من المرونة! بفضل كونه مفتوح المصدر ، نحن متاحون أيضاً بـ 32 لغة! + + إسترداد الصور ومقاطع الفيديو المحذوفة + حذف صورة أو فيديو ثمين عن طريق الخطأ لا تقلق نظراً لأن(تطبيق المعرض البسيط الـ Pro) يتميز بسلة محذوفات سهلة الاستخدام يمكنك من خلالها إسترداد الصور ومقاطع الفيديو المحذوفة بسهولة. + + حماية وإخفاء الصور والفيديوهات والملفات + باستخدام رمز الـ PIN أو النمط أو الماسح الضوئي لبصمة الاصبع لجهازك يمكنك حماية الصور ومقاطع الفيديو والألبومات بأكملها وإخفائها. يمكنك حماية التطبيق نفسه أو وضع قفل الأمان علي وظائف معينة للتطبيق علي سبيل المثال، لا يمكنك حذف ملف دون إجراء مسح ضوئي لبصمة الاصبع، مما يساعد علي حماية ملفاتك من الحذف غير المقصود. + + أطلع على المجموعة الكاملة من تطبيقات البسيط هنا: + https://www.simplemobiletools.com + + موقع مستقل لتطبيق المعرض البسيط الـ Pro : + https://www.simplemobiletools.com/gallery + + صفحة الفيسبوك: + https://www.facebook.com/simplemobiletools + + موقع رديت: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml new file mode 100644 index 000000000..0333af35f --- /dev/null +++ b/app/src/main/res/values-az/strings.xml @@ -0,0 +1,427 @@ + + + Sadə Qalereya + Qalereya + Redaktə + Kameranı aç + (gizli) + (xaric edilmiş) + Pin qovluğu + Unpin qovluğu + Pin to the top + Show all folders content + All folders + Switch to folder view + Other folder + Show on map + Unknown location + Volume + Brightness + Lock orientation + Unlock orientation + Change orientation + Force portrait + Force landscape + Use default orientation + Fix Date Taken value + Fixing… + Dates fixed successfully + No Date Taken values have been found + Share a resized version + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Switch to file search across all visible folders + Set as default folder + Unset as default folder + + + Filter media + Images + Videos + GIFs + RAW images + SVGs + Portraits + No media files have been found with the selected filters. + Change filters + + + This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden items\' option in Settings. Continue? + Exclude + Excluded folders + Manage excluded folders + This will exclude the selection together with its subfolders from Simple Gallery only. You can manage excluded folders in Settings. + Exclude a parent instead? + Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\n\nIf you want to hide them from other apps too, use the Hide function. + Remove all + Remove all folders from the list of excluded? This will not delete the folders. + Hidden folders + Manage hidden folders + Seems like you don\'t have any folders hidden with a \".nomedia\" file. + + + Included folders + Manage included folders + Add folder + If you have some folders which contain media, but were not recognized by the app, you can add them manually here.\n\nAdding some items here will not exclude any other folder. + No media files have been found. You can solve it by adding the folders containing media files manually. + + + Resize + Resize selection and save + Width + Height + Keep aspect ratio + Please enter a valid resolution + + + Editor + Rotate + Invalid image path + Invalid video path + Image editing failed + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + Edit image with: + Edit video with: + No image editor found + No video editor found + Unknown file location + Could not overwrite the source file + Rotate left + Rotate right + Rotate by 180º + Flip + Flip horizontally + Flip vertically + Free + Other + + + Simple Wallpaper + Set as Wallpaper + Setting as Wallpaper failed + Set as wallpaper with: + Setting wallpaper… + Wallpaper set successfully + Portrait aspect ratio + Landscape aspect ratio + Home screen + Lock screen + Home and lock screen + + + Slideshow + Interval (seconds): + Include photos + Include videos + Include GIFs + Random order + Move backwards + Loop slideshow + Animation + None + Fade + Slide + The slideshow ended + No media for the slideshow have been found + + + Group direct subfolders + + + Group by + Do not group files + Folder + Last modified + Last modified (daily) + Last modified (monthly) + Date taken + Date taken (daily) + Date taken (monthly) + File type + Extension + Please note that grouping and sorting are 2 independent fields + + + Folder shown on the widget: + Show folder name + + + Play videos automatically + Remember last video playback position + Toggle filename visibility + Loop videos + Animate GIFs at thumbnails + Max brightness when viewing fullscreen media + Crop thumbnails into squares + Show video durations + Rotate fullscreen media by + System setting + Device rotation + Aspect ratio + Black background at fullscreen media + Scroll thumbnails horizontally + Automatically hide system UI at fullscreen media + Delete empty folders after deleting their content + Allow controlling photo brightness with vertical gestures + Allow controlling video volume and brightness with vertical gestures + Show folder media count on the main view + Show extended details over fullscreen media + Manage extended details + Allow one finger zoom at fullscreen media + Allow instantly changing media by clicking on screen sides + Allow deep zooming images + Hide extended details when status bar is hidden + Show some action buttons at the bottom of the screen + Show the Recycle Bin at the folders screen + Deep zoomable images + Show images in the highest possible quality + Show the Recycle Bin as the last item on the main screen + Allow closing the fullscreen view with a down gesture + Allow 1:1 zooming in with two double taps + Always open videos on a separate screen with new horizontal gestures + Show a notch if available + Allow rotating images with gestures + File loading priority + Speed + Compromise + Avoid showing invalid files + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Thumbnails + Fullscreen media + Extended details + Bottom actions + + + Manage visible bottom actions + Toggle favorite + Toggle file visibility + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + How can I make Simple Gallery the default device gallery? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + Simple Gallery Pro - Photo Manager & Editor + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. + + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml new file mode 100644 index 000000000..0dcc3984e --- /dev/null +++ b/app/src/main/res/values-bn/strings.xml @@ -0,0 +1,414 @@ + + + সিম্পল গ্যালারি + গ্যালারি + সম্পাদনা + ক্যামেরা খুলুন + (লুকানো) + (বাদ) + ফোল্ডার পিন করুন + ফোল্ডার আনপিন করুন + একদম উপরে পিন করুন + সব ফোল্ডারের ভেতরে কি আছে দেখান + সব ফোল্ডার + ফোল্ডার ভিউতে সুইচ করুন + অন্যান্য ফোল্ডার + মানচিত্রে দেখান + অজানা স্থান + শব্দ + উজ্জ্বলতা + ওরিয়েন্টেশন বন্ধ + ওরিয়েন্টেশন খুলুন + ওরিয়েন্টেশন পরিবর্তন + পোট্রেট ওরিয়েন্টেশন + অবতল ওরিয়েন্টেশন + ডিফল্ট ওরিয়েন্টেশন + তারিখ ঠিক করুন + ঠিক করা হচ্ছে… + তারিখ সফলভাবে ঠিক করা হয়েছে + তোলার তারিখ নেই + পরিবর্তিত একা সংস্করণ নিন + অই,\n\n মনে হচ্ছে আপনি অ্যাপটি পুরোনো মুক্ত সংস্করণ থেকে হালনাগাদ করেছেন, যেটার সেটিংসে \'প্রোতে হালনাগাদ করুন\' বাটন রয়েছে। \n\nধন্যবাদ! + সব দেখা যাওয়া ফোল্ডারে ফাইল সার্চে চলে যান + ডিফল্ট ফোল্ডার হিশেবে সেট করুন + ডিফল্ট ফোল্ডার হিশেবে আর রাখবেন না + + + মিডিয়া ফিল্টার করুন + ছবিসমূহ + ভিডিওসমূহ + গিফসমূহ + র ছবিসমূহ + এসভিজি সমূহ + পোট্রেইট + এ নির্দিষ্ট ফিল্টারে কোন মিডিয়া ফাইল পাওয়া যায়নি। + ফিল্টার পরিবর্তন করুন + + + This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden items\' option in Settings. Continue? + বাদ দিন + বাদ দেওয়া ফোল্ডারসমূহ + বাদ দেওয়া ফোল্ডারগুলোর ব্যবস্থাপনা + This will exclude the selection together with its subfolders from Simple Gallery only. You can manage excluded folders in Settings. + Exclude a parent instead? + Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\n\nIf you want to hide them from other apps too, use the Hide function. + সবগুলো সরান + বাদ দেওয়া তালিকা থেকে সমস্ত ফোল্ডার সরাবেন? এটি ফোল্ডারগুলি মুছবে না। + লুকানো ফোল্ডারগুলো + লুকানো ফোল্ডারগুলি পরিচালনা করুন + দেখে মনে হচ্ছে আপনার সাথে কোনও ফোল্ডার লুকানো নেই \".nomedia\" ফাইল। + + + অন্তর্ভুক্ত ফোল্ডার + অন্তর্ভুক্ত ফোল্ডার ব্যবস্থাপনা করুন + ফোল্ডার যোগ করুন + If you have some folders which contain media, but were not recognized by the app, you can add them manually here.\n\nAdding some items here will not exclude any other folder. + No media files have been found. You can solve it by adding the folders containing media files manually. + + + আকার পরিবর্তন + নির্বাচিতগুলোর আকার পরিবর্তন ও সংরক্ষণ করুন। + প্রস্থ + দৈর্ঘ্য + অনুপাত রাখুন + দয়া করে একটি বৈধ রেজোলিউশন দিন। + + + সম্পাদক + ঘোরান + ভুল ছবির পথ + ভুল ভিডিওর পথ + ছবি সম্পাদনা ব্যর্থ + ভিডিও সম্পাদনা ব্যর্থ + ছবি সম্পাদনা বাতিল + ভিডিও সম্পাদনা বাতিল + ফাইল সফলভাবি সম্পাদিত হয়েছে + ছবি সফলভাবি সম্পাদিত হয়েছে + ভিডিও সফলভাবি সম্পাদিত হয়েছে + ছবি সম্পাদনা করুন: + ভিডিও সম্পাদনা করুন: + কোন ছবি সম্পাদক পাওয়া যায়নি + কোন ভিডিও সম্পাদক পাওয়া যায়নি + অজানা অবস্থান ফাইল + উৎস ফাইল প্রতিস্থাপন করা যায়নি + বামে ঘোরান + ডানে ঘোরান + ১৮০º ঘোরান + ঘোরান + অবতলভাবে ঘোরান + খাড়াখাড়িভাবে ঘোরান + মুক্ত + অন্যান্য + + + সিম্পল ওয়ালপেপার + ওয়ালপেপার হিশেবে সেট করুন + ওয়ালপেপার সেট করা ব্যর্থ হয়েছে + ওয়ালপেপার সেট করুন: + ওয়ালপেপার সেট করা হচ্ছে… + সফলভাবে ওয়ালপেপার সেট হয়েছে + খাড়াখারি আকৃতিগত অনুপাত + অবতল আকৃতিগত অনুপাত + হোম স্ক্রিন + লক স্ক্রিন + হোম ও লক স্ক্রিন + + + স্লাইডশো + বিরতি (সেকেন্ডে): + ছবিগুলো যোগ করুন + ভিডিওগুলো যোগ করুন + গিফগুলো যোগ করুন + বিক্ষিপ্তভাবে + পেছনে নিয়ে যান + স্লাইডশো লুপ করুন + অ্যানিমেশন + কিছু না + বিবর্ণ + স্লাইড + স্লাইডশো শেষ হয়েছে + স্লাইডশোর জন্য কোন মিডিয়া পাওয়া যায়নি + + + Group direct subfolders + + + একত্রিত করুন + Do not group files + ফোল্ডার + শেষ পরিবর্তন করা হয়েছে + শেষ পরিবর্তন (দৈনিক) + শেষ পরিবর্তন (মাসিক) + তোলার তারিখ + তোলার তারিখ (দৈনিক) + তোলার তারিখ (মাসিক) + ফাইল টাইপ + এক্সটেনশন + Please note that grouping and sorting are 2 independent fields + + + উইজেটে দেখানো ফোল্ডার: + ফোল্ডারের নাম দেখান + + + স্বয়ংক্রিয়ভাবে ভিডিও চালান + Remember last video playback position + Toggle filename visibility + Loop videos + Animate GIFs at thumbnails + Max brightness when viewing fullscreen media + Crop thumbnails into squares + Show video durations + Rotate fullscreen media by + সিস্টেম সেটিংস + Device rotation + অ্যাস্পেক্ট রেশিও + Black background at fullscreen media + Scroll thumbnails horizontally + Automatically hide system UI at fullscreen media + Delete empty folders after deleting their content + Allow controlling photo brightness with vertical gestures + Allow controlling video volume and brightness with vertical gestures + Show folder media count on the main view + Show extended details over fullscreen media + Manage extended details + Allow one finger zoom at fullscreen media + Allow instantly changing media by clicking on screen sides + Allow deep zooming images + Hide extended details when status bar is hidden + Show some action buttons at the bottom of the screen + Show the Recycle Bin at the folders screen + Deep zoomable images + Show images in the highest possible quality + Show the Recycle Bin as the last item on the main screen + Allow closing the fullscreen view with a down gesture + Allow 1:1 zooming in with two double taps + Always open videos on a separate screen with new horizontal gestures + Show a notch if available + Allow rotating images with gestures + File loading priority + গতি + সমঝোতা + Avoid showing invalid files + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + বর্গ + Rounded corners + + + থাম্বনেল + পুরো স্ক্রিনের মিডিয়া + বর্ধিত বিশদ + নিচের অংশের ক্রিয়াগুলো + + + Manage visible bottom actions + Toggle favorite + Toggle file visibility + + + কাস্টম + রিসেট + বর্গ + রূপান্তর + ফিল্টার + কিছু না + সামঞ্জস্য করুন + ছায়া + অনাবৃত করুন + হাইলাইটসমূহ + উজ্জ্বলতা + Contrast + স্যাচুরেশন + নির্মলতা + গামা + কালো + শাদা + উষ্ণতা + তীক্ষ্ণতা + রিসেট + ফোকাস + কিছু না + রশ্মীবৎ + লিনিয়ার + আয়না + গাউসিয়ান + লেখা + লেখার অপশন + লেখার রঙ + ফন্ট + যোগ করুন + সম্পাদনা করুন + সোজা করা + ফন্ট + রঙ + পেছনের রঙ + প্রান্তিককরণ + সামনে আনুন + মুছে ফেলুন + আপনার লেখা + ব্রাশ + রঙ + আয়তন + কঠোরতা + সামনে আনুন + মুছে ফেলুন + ব্রাশের রঙ + সম্পাদক + সম্পাদক বন্ধ করুন? + আপনি কি সত্যিই পরিবর্তনগুলো বাদ দিতে চান? + হ্যাঁ + না + বাতিল + গ্রহণ + সংরক্ষণ + এক্সপোর্ট হচ্ছে… + এক্সপোর্ট হচ্ছে %s. + স্টিকার + স্টিকারের রঙ + Sticker Options + যোগ + রঙ + মুছে ফেলা + সামনে + সোজা করুন + প্রতিস্থাপন + অস্বচ্ছতা + বৈপরীত্য + সম্পৃক্তি + উজ্জ্বলতা + আপলোডসমূহ + ওভারলে + সাধারণ + অন্ধকারাচ্ছন্ন করা + স্ক্রিন + ওভারলে + হালকা করা + গুণ + পোড়া রঙ + নরম আলো + কড়া আলো + কিছু না + সোনালী + লাইটলিক ১ + মোজাইক + কাগন + বৃষ্টি + ভিন্টেজ + পাশাপাশি ঘুরান + খাড়াখাড়ি ঘুরান + পূর্বাবস্থায় ফেরত যান + পুনরায় আনুন + রঙ নির্বাচক + স্বচ্ছ + শাদা + ধুসর + কালো + হালকা নীল + নীল + বেগুনী + অর্কিড + গোলাপী + লাল + কমলা + সোনালী + হলুদ + জলপাই রঙ + নীল + সামুদ্রিক নীল + পাইপেটেবল রঙ + কাটুন + + + How can I make Simple Gallery the default device gallery? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + সিম্পল গ্যালারি প্রো - ছবি ম্যানেজার ও সম্পাদক + + কোন বিজ্ঞাপন ছাড়া ছবি, ভিডিও ও গিফ সম্পাদনা, ব্যবস্থাপনার একটি প্রিমিয়াম অ্যাপ + + সিম্পল গ্যালারি প্রো একটি অত্যন্ত সম্পাদনাযোগ্য অফলাইন গ্যালারি। + + অ্যাপটিতে কোনও বিজ্ঞাপন এবং অপ্রয়োজনীয় অনুমতি নেই। অ্যাপ্লিকেশনটিকে যেহেতু ইন্টারনেট সংযোগের প্রয়োজন নেই, তাই আপনার গোপনীয়তা সুরক্ষিত। + + ------------------------------------------------- + সহজ গ্যালারি প্রো - বৈশিষ্ট্যসমূহ + ------------------------------------------------- + + • কোন বিজ্ঞাপন বা পপআপ ছাড়া অফলাইন গ্যালারী + • সাধারণ গ্যলারি ফটো সম্পাদক - ক্রপ করুন, ঘোরান, পুনরায় আকার দিন, অঙ্কন করুন, ফিল্টার প্রয়োগ করুন এবং আরও অনেক কিছু। + • কোন ইন্টারনেট সংযোগ প্রয়োজন নেই + • কোন অপ্রয়োজনীয় অনুমতি চায় না + • ছবি, ভিডিও ও অন্যান্য ফাইলসমূহ দ্রুত অনুসন্ধান করুন + • সিম্পল গ্যালারি প্রো এর ধরন পরিবর্তন করুন + • ৩২টি ভাষায় রয়েছে + • সিম্পল গ্যালারি প্রো উন্মুক্ত উৎসের + … এবং আরও অনেক অনেক বেশী! + + ছবি গ্যালারি সম্পাদক + সিম্পল গ্যালারি প্রো আপনার ছবি যেকোন সময় সম্পাদনা সহজ করে দেয়। আপনার ছবি কাটুন, ফ্লিপ করুন বা আকার পরিবর্তন করুন। আপনি আরেকটু সৃজনশীল হলে, নতুন ফিল্টার যুক্ত করতে পারবেন এবং আপনার ছবির উপর আঁকতেও পারবেন। + + অনেকধরণের ফাইল ঘরানার জন্য সমর্থন রয়েছে + জেপিইজি, পিএনজি, এমপি৪, এমকেভি, আরএডাব্লু, এসভিজি, প্যানোরামিক ফটো, প্যানোরামিক ভিডিও এবং আরও অনেক ফাইল টাইপ সিম্পল গ্যালারি প্রো সমর্থন করে। + + মুছে ফেলা ছবি ও ভিডিও পুনরুদ্ধার করুন + ভুল করে গুরুত্বপূর্ণ ছবি বা ভিডিও মুছে ফেললেন? চিন্তার কারণ নেই। সিম্পল গ্যালারি প্রোর নিজস্ব রিসাইকেল বিন আপনাকে এ বিপদ থেকে বাঁচাবে। + + ছবি, ভিডিও এবং ফাইল লুকান ও নিরাপদ রাখুন: + পিন, প্যাটার্ন, অথবা ফিঙ্গারপ্রিন্ট ব্যবহার করে আপনি আপনার ছবি, ভিডিও এবং পুরো অ্যালবামই নিরাপদে রাখতে পারবেন। আপনি পুরো অ্যাপটাই নিরাপদে রাখতে পারবে, অথবা নির্দিষ্ট কিছু ফাংশনে লক দিতে পারবেন। যেমন ধরুন, ফিঙ্গারপ্রিন্ট ছাড়া কোন ফাইল মুছতে না পারাটা ভুল করে ফাইল হারানো থেকে আপনাকে বাঁচাবে। + + সিম্পল টুলসমূহের পরিপূর্ণ স্যুট: + https://www.simplemobiletools.com + + সিম্পল গ্যালারি প্রোর আলাদা ওয়েবসাইট + https://www.simplemobiletools.com/gallery + + ফেসবুক: + https://www.facebook.com/simplemobiletools + + রেডিট: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml new file mode 100644 index 000000000..58e0d5c34 --- /dev/null +++ b/app/src/main/res/values-ca/strings.xml @@ -0,0 +1,427 @@ + + + Simple Gallery + Galeria + Editar + Obrir càmera + (ocult) + (exclòs) + Fixar carpeta + No fixar carpeta + Ancorar a l’inici + Mostrar el contingut de totes les carpetes + Tots els mitjans + Canviar a vista de carpeta + Un altre carpeta + Mostrar al mapa + Ubicació desconeguda + Volum + Brillantor + Bloquejar orientació + Desbloquejar orientació + Canviar orientació + Forçar vertical + Forçar horitzontal + Fer servir la orientació per defecte + Fixar la data de presa + Fixant… + Data fixada correctament + No s\’han trobat valors presos per data + Comparteix una versió redimensionada + Hola,\n\nsembla que heu actualitzat des de l\'antiga aplicació gratuïta. Ara podeu desinstal·lar la versió antiga, que té un botó "Actualitza a Pro" a la part superior de la configuració de l’aplicació.\nNomés s’eliminaran els elements de la paperera, els elements preferits sense marcar i també caldrà restablirla configuració de la vostra aplicació.\n\nGràcies! + Canvia a la cerca de fitxers a totes les carpetes visibles + Set as default folder + Unset as default folder + + + Filtre d’arxius + Imatges + Vídeos + GIFs + Imatges RAW + SVGs + Retrats + No s’han tronat arxius amb els filtres seleccionats. + Canviar filtres + + + Aquesta funció oculta les carpetes agregant un arxiu ’.nomedia’ dins d’ella. També ocultarà les subcarpetes. Pots mostrar-les canviant la opció ’Mostrar carpetes ocultes’ als ajustaments. Continuar? + Excloure + Carpetes excloses + Gestionar carpetes excloses + Això exclou la selecció juntament amb les carpetes, només de Simple Gallery. Pots gestionar les carpetes excloses en els Ajustaments. + Excloure millor la carpeta superior? + Excloure les carpetes les ocultarà junt amb les seves subcarpetes, però només a Simple Gallery. Seguirant sent visibles a altres aplicacions.\n\nSi vols ocultar-les d’altres aplicacions fes servir la opció Ocultar. + Eliminar tot + Eliminar totes les carpetes de la llista d’excloses? Això no eliminarà les carpetes. + Carpetes ocultes + Gestionar carpetes ocultes + Sembla que no tens cap carpeta amb l’arxiu \".nomedia\". + + + Carpetes incloses + Gestionar carpetes incloses + Agregar carpeta + Si tens alguna carpeta que contingui multimèdia però no ha estat reconeguda per la aplicació, pots agregar-les manualment aquí. + No s’ha trobat cap fitxer multimèdia. Podeu resoldre-ho afegint manualment les carpetes que contenen fitxers multimèdia. + + + Redimensionar + Redimensionar la selecció i desar + Ample + Alt + Mantenir proporcions + Si us plau, introdueix una resolució vàlida + + + Editor + Rotar + Ruta de imatge no vàlida + Invalid video path + Ha fallat la edició de la imatge + Video editing failed + S\'ha cancel·lat l\'edició de la imatge + Video editing cancelled + Fitxer editar satisfactoriament + Image edited successfully + Video edited successfully + Editar imatge utilitzant: + Edit video with: + No s’ha trobat cap editor d’imatges + No video editor found + Ubicació de l’arxiu desconeguda + No s’ha pogut sobreescriure l’arxiu d’origen + Rotar a l’esquerra + Rotar a la dreta + Rotar 180º + Girar + Horizontalment + Verticalment + Lliure + Altres + + + Fons de pantalla de Simple Gallery + Establir com a fons de pantalla + Error a l’establir com fons de pantalla + Establir com fons de pantalla amb: + Establint fons de pantalla… + Fons de pantalla establert correctament + Relació d’aspecte tipus retrat + Relació d’aspecte tipus paisatge + Pantalla principal + Pantalla de bloqueig + Pantalla principal i de bloqueig + + + Presentació de diapositives + Interval (segons): + Inclou imatges + Inclou vídeos + Inclou GIFs + Ordre aleatori + Moure cap enrere + Presentació de diapositives + Animació + Cap + Esvair + Lliscar + S’ha acabat la presentació de diapositives + No s’han trobat mitjans per a la presentació de diapositives + + + Agrupar carpetes directes + + + Agrupar per + No agrupar fitxers + Carpeta + Darrer modificat + Darrer modificat (diari) + Darrer modificat (mensual) + Data de presa + Data de presa (diari) + Data de presa (mensual) + Tipus de fitxer + Extensió + Tingueu en compte que l’agrupació i la classificació són 2 camps independents + + + Carpeta que es mostra a l’estri: + Mostra el nom de la carpeta + + + Reproduir vídeos automàticament + Recordeu la posició de la darrera reproducció de vídeo + Canviar la visibilitat del nom d’arxiu + Reproducció continua de vídeos + Animar les miniatures dels GIFs + Brillantor màxima quan es mostra multimèdia + Retallar miniatures en quadrats + Mostra les durades del vídeo + Gira els mitjans a pantalla completa segons + Configuració del sistema + Rotació del dispositiu + Relació d’aspecte + Fons i barra d’estat negre als mitjans de pantalla completa + Desplaçar miniatures horizontalment + Ocultar automàticament la interficie de usuari del sistema a pantalla complerta + Eliminar carpetes buides després d’esborrar el seu contingut + Permet controlar la brillantor amb gestos verticals + Permet controlar el volum i la brillantor del vídeo amb gestos verticals + Mostrar el número de mitjans de les carpetes a la vista principal + Mostrar detalls estesos sobre mitjans a pantalla complerta + Gestioneu els detalls ampliats + Permet fer zoom amb un sol dit a pantalla complerta + Permet canviar els mitjans de manera instantània fent clic als costats de la pantalla + Permet imatges de zoom profund + Amaga els detalls estesos quan la barra d’estat està amagada + Mostra alguns botons d’acció a la part inferior de la pantalla + Mostra la paperera de reciclatge a la pantalla de carpetes + Imatges ampliades a mida + Mostra imatges amb la màxima qualitat possible + Mostra la paperera de reciclatge com a últim element a la pantalla principal + Permet tancar la vista de pantalla completa amb un gest avall + Permet 1:1 zoom amb dos tocs dobles + Obriu sempre vídeos en una pantalla independent amb nous gestos horitzontals + Mostra una osca si està disponible + Permet girar imatges amb gestos + Prioritat de càrrega d’arxius + Velocitat + Compromès + Eviteu mostrar fitxers no vàlids + Mostra els tipus de fitxers d\’imatges + Permet fer zoom als vídeos amb dos cops + Estil de miniatura de carpeta + Estil de miniatura del fitxer + Espai entre miniatures + Mostra el recompte de fitxers en una línia diferent + Mostra el recompte de fitxers entre claudàtors + No mostreu el recompte de fitxers + Limiteu els títols de carpeta llarga a 1 línia + Quadrat + Cantonades arrodonides + + + Miniatures + Mitjans a pantalla completa + Detalls estesos + Accions de la part inferior + + + Administra les accions de la part inferior + Activa favorits + Activa la visibilitat del fitxer + + + Personalitzat + Reiniciar + Quadrat + Transformar + Filtrar + No + Ajustar + Ombres + Exposició + Destacat + Brillantor + Contrast + Saturació + Claredat + Gamma + Negres + Blancs + Temperatura + Nitidessa + Restablir + Focus + No + Radial + Lineal + Emmirallat + Gaussià + Text + Opcions de Text + Color de Text + Font + Afegir + Editar + Allisar + Lletra + Color + Color de fons + Alineació + Al davant + Eliminar + El teu text + Pinzell + Color + Mida + Duresa + Al davant + Eliminar + de pinzellColor + Editor + Tancar Editor? + Do you really want to discard the changes? + Si + No + Cancel·lar + Acceptar + Desar + Exportar… + Exportant %s. + Adhesiu + Color d\'adhesiu + Opcions d’adhesius + Afegeix + Color + Suprimeix + Al capdavant + Estirar + Substitueix + Opacitat + Contrast + Saturació + Brillantor + Brillantor + Superposició + Normal + Enfosquir + Pantalla + Superposició + Alleugerir + Multiplicar + Color cremat + Llum suau + Llum dura + Cap + Daurat + Fuga de llum 1 + Mosaic + Paper + Pluja + Vintage + Voltejar H + Voltejar V + Desfer + Refer + Selector de colors + Transparent + Blanc + Gris + Negre + Blau clar + Blau + Porpre + Orquídia + Rosa + Vermell + Taronja + Or + Groc + Oliva + Verd + Aiguamarina + Pipetejat de color + Trim + + + Com puc fer que Simple Gallery sigui la galeria de dispositius predeterminada? + Primer heu de trobar la galeria actualment predeterminada a la secció Aplicacions de la configuració del vostre dispositiu, cerqueu un botó que digui alguna cosa semblant a \"Obrir per defecte \", feu-hi clic i seleccioneu \"Esborra valors predeterminats \". + La propera vegada que proveu d’obrir una imatge o un vídeo, haureu de veure un selector d’aplicació, on podeu seleccionar Simple Galeria i convertir-la en la aplicació predeterminada. + Vaig bloquejar l’aplicació amb una contrasenya, però l’he oblidat. Què puc fer? + Es pot resoldre de dues maneres. Podeu tornar a instal·lar l’aplicació o trobar l’aplicació a la configuració del dispositiu i seleccionar \"Esborrar dades \". Això reiniciarà totes les configuracions, no eliminarà cap fitxer multimèdia. + Com puc fer que un àlbum sempre aparegui a la part superior? + Podeu prémer l’àlbum desitjat i seleccionar la icona de la xinxeta al menú d’acció i el fixarà a la part superior. També podeu enganxar diverses carpetes, els elements fixats s’ordenaran pel mètode de classificació predeterminat. + Com puc fer avançar els vídeos? + Podeu fer-ho tocant dues vegades el costat de la pantalla o tocant els textos de durada actual o màxima a prop de la barra de cerca. Si activeu l\’obertura de vídeos en una pantalla diferent a la configuració de l\’aplicació, també podeu fer servir gestos horitzontals. + Quina és la diferència entre ocultar i excloure una carpeta? + Excloure impedeix mostrar la carpeta només a Simple Galery, mentre que Ocultar també amaga la carpeta a altres galeries. Funciona creant un fitxer \". Nomedia \" buit a la carpeta donada, que podeu eliminar amb qualsevol gestor de fitxers. Tingueu en compte que alguns dispositius no permeten amagar carpetes com Càmera, Captures de pantalla i Descàrregues. + Per què apareixen les carpetes amb les portades de la música o adhesius? + Pot passar que veuràs que apareixen alguns àlbums inusuals. Podeu excloure’ls fàcilment prement-los i seleccionant Excloure. Al següent diàleg, podeu seleccionar la carpeta principal, és probable que impedeixi que apareguin altres àlbums relacionats. + Una carpeta amb imatges no apareix, què puc fer? + Això pot tenir diversos motius, però resoldre-ho és fàcil. Accediu a Configuració -> Gestiona les carpetes incloses, seleccioneu Més i navegueu fins a la carpeta necessària. + Què passa si vull veure només algunes carpetes concretes? + L’addició d’una carpeta a les carpetes incloses no exclou automàticament res. El que podeu fer és anar a Configuració -> Gestionar carpetes excloses, excloure la carpeta arrel \"/\" i, a continuació, afegir les carpetes desitjades a Configuració -> Gestionar carpetes incloses. + Això farà que només les carpetes seleccionades siguin visibles, ja que tant excloure com incloure són recursius i si una carpeta està exclosa i inclosa, es mostrarà. + Puc retallar imatges amb aquesta aplicació? + Sí, pots retallar imatges a l’editor, arrossegant les cantonades de la imatge. Pots accedir a l’editor prement una miniatura d’imatge i seleccionant Edita o seleccionant Edita des de la visualització de pantalla completa. + Puc agrupar d’alguna manera les miniatures del fitxer multimèdia? + Si, només heu d’utilitzar l’ítem del menú \"Agrupar per\" mentre es troba a la vista en miniatura. Podeu agrupar fitxers amb diversos criteris, inclòs data de presa. Si utilitzeu la funció \"Mostra el contingut de totes les carpetes\", també podeu agrupar-les per carpetes. + L’ordenació per data que de presa no sembla funcionar correctament, com puc solucionar-ho? + Probablement, els fitxers es copiïn en un lloc incorrecte. Podeu arreglar-ho si seleccioneu les miniatures del fitxer i seleccioneu \"Fixar data de presa\". + Veig algunes bandes de colors a les imatges. Com puc millorar la qualitat? + La solució actual per mostrar imatges funciona bé en la gran majoria dels casos, però si voleu obtenir una millor qualitat d\'imatge, podeu activar \"Mostra les imatges amb la màxima qualitat possible\" a la configuració de l’aplicació, a la secció \"Imatges ampliades a mida\". + He amagat un fitxer / carpeta. Com puc mostrar-la? + Podeu prémer l’element de menú \"Mostra temporalment elements ocults\" a la pantalla principal o canviar \"Mostra els elements ocults\" a la configuració de l’aplicació per veure l’element amagat. Si voleu mostrar-la sempre, premeu-la i seleccioneu \"Mostra\". Les carpetes s\'amaguen afegint un fitxer \".nomedia\" ocult en elles, també podeu eliminar el fitxer amb qualsevol gestor de fitxers. + Per què l’aplicació ocupa molt d’espai? + La memòria cau d’aplicacions pot ocupar fins a 250 MB, garanteix una càrrega de les imatges més ràpida. Si l’aplicació ocupa més espai, probablement sigui per tenir elements a la Paperera de reciclatge. Aquests fitxers compten a la mida de l’aplicació. Podeu esborrar la paperera de reciclatge obrint-la i suprimint tots els fitxers o des de la configuració de l’aplicació. Els fitxers de la paperera s’eliminen automàticament després de 30 dies. + + + + Simple Gallery Pro - Photo Manager & Editor + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro és una galeria fora de línia altament personalitzable. Organitza i edita les teves fotos, recupera fitxers suprimits amb la paperera de reciclatge, protegeix i amaga fitxers, mostra una gran varietat de formats de foto i vídeo incloent RAW, SVG i molt més. + + L’aplicació no conté anuncis ni permisos innecessaris. Com que l’aplicació tampoc no requereix accés a Internet, la vostra privadesa està protegida. + + ------------------------------------------------- + SIMPLE GALLERY PRO - CARACTERÍSTIQUES + ------------------------------------------------- + + • Galeria sense connexió sense anuncis ni finestres emergents + • Editor de fotos senzilles de la galeria: retalla, gira, canvia de mida, dibuixa, filtra i més + • No es necessita accés a Internet, donant-li més privacitat i seguretat + • No es necessiten permisos innecessaris + • Cerca ràpidament imatges, vídeos i fitxers + • Obre i visualitza molts tipus de fotos i de vídeo diferents (RAW, SVG, panoràmiques, etc.) + • Una varietat de gestos intuïtius per editar fàcilment i organitzar els fitxers + • Moltes maneres de filtrar, agrupar, amplificar i ordenar fitxers + • Personalitza l’aparença de Simple Gallery Pro + • Disponible en 32 idiomes + • Marca els fitxers com a preferits per accedir ràpidament + • Protegeix les teves fotos i vídeos amb un patró, pin o empremta digital + • Utilitza un pin, patró i empremta digital per protegir també el llançament de l’aplicació o funcions específiques + • Recupera fotos i vídeos esborrats de la paperera de reciclatge + • Canvia la visibilitat dels fitxers per ocultar fotografies i vídeos + • Crea una presentació de diapositives personalitzable dels teus fitxers + • Mostra informació detallada dels teus fitxers (resolució, valors EXIF, etc.) + • Simple Gallery Pro és de codi obert + ... i molt més! + + EDITOR DE GALERIA DE FOTOS + Simple Gallery Pro et permet editar fàcilment les vostres imatges. Retalla, gira, gira i canvia la mida de les teves fotos. Si et sents una mica més creatiu, pots afegir filtres i dibuixar-ne les imatges. + + SUPORT PER ALTRES TIPUS DE ARXIU + A diferència d’altres aplicacions de galeria, Simple Gallery Pro suporta una gran varietat de tipus de fitxers diferents, inclosos JPEG, PNG, MP4, MKV, RAW, SVG, fotos panoràmiques, vídeos panoràmics i molts més. + + GESTOR DE GALERIA ALTAMENT PERSONALITZABLE + Des de la interfície d’usuari fins als botons de funció de la barra d’eines inferior, Simple Gallery Pro és altament personalitzable i funciona de la manera que desitgis. Cap altre aplicació de galeria té aquest tipus de flexibilitat. Gràcies a ser de codi obert, també estem disponibles en 32 idiomes. + + RECUPERES FOTOS I VÍDEOS + Has suprimit accidentalment una foto o vídeo precios? No et preocupis! Simple Gallery Pro disposa d’una paperera de reciclatge on podeu recuperar fotos i vídeos eliminats fàcilment. + + PROTEGEIX, OCULTA FOTOS, VÍDEOS I ARXIUS + Amb el pin, el patró o l’escàner d’emprenta digital del teu dispositiu, pots protegir i amagar fotografies, vídeos i àlbums sencers. Pots protegir la pròpia aplicació o col·locar panys sobre funcions específiques de l’aplicació. Per exemple, no poder esborrar un fitxer sense una anàlisi d’empremtes digitals, ajudant-vos a protegir els teus fitxers de l’eliminació accidental. + + Consulteu el conjunt complet d’eines senzilles aquí: + https://www.simplemobiletools.com + + Lloc web de Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index d6b42eee7..aa9d59296 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -4,42 +4,66 @@ Galerie Upravit Spustit fotoaparát - Otevřít pomocí - Nebyla nalezena žádná vhodná aplikace - (skryté) + (skryto) + (vyloučeno) Připnout složku Odepnout složku + Připnout nahoru Zobrazit obsah všech složek Všechny složky Přepnout na zobrazení složek Jiná složka Zobrazit na mapě Neznámá poloha - Nebyla nalezena žádná mapová aplikace - Nebyla nalezena žádná fotografická aplikace - Zvýšit počet sloupců - Snížit počet sloupců - Dočasně zobrazit skryté - Change cover image - Select photo - Use default + Hlasitost + Jas + Uzamknout orientaci + Odemknout orientaci + Změnit orientaci + Vynutit orientaci na výšku + Vynutit orientaci na šířku + Použít výchozí orientaci + Opravit datum vytvoření + Opravuji… + Datumy byly úspěšně opraveny + No Date Taken values have been found + Sdílet verzi se změněnou velikostí + Zdravím,\n\nzdá se, že jste přešli ze staré bezplatné aplikace. Starou aplikaci, která má nahoře v nastavení tlačítko \'Stáhnout Pro verzi\', můžete již odinstalovat.\n\nZtratíte tím pouze soubory v odpadkovém koši, označení oblíbených souborů a také budete muset znovu nastavit položky v nastavení aplikace.\n\nDěkuji! + Přepnout na vyhledávání souborů ve všech viditelných složkách + Set as default folder + Unset as default folder + + + Filtr médií + Obrázky + Videa + GIFy + RAW obrázky + SVGčka + Portréty + Se zvolenými filtry nebyly nalezeny žádné mediální soubory. + Změnit filtry - Tato funkce skryje složku, včetně podsložek, přidáním souboru \'.nomedia\'. Zobrazíte je zvolením možnosti \'Zobrazit skryté složky\' v nastavení. Pokračovat? + Tato funkce skryje složku včetně podsložek přidáním souboru \'.nomedia\'. Zobrazíte je zvolením možnosti \'Zobrazit skryté položky\' v nastavení. Pokračovat? Vyloučit Vyloučené složky Spravovat vyloučené složky Tato funkce vyloučí výběr včetně podsložek jen z Jednoduché galerie. V nastavení můžete spravovat vyloučené složky. - Chcete vyloučit rodičovskou složku? - Vyloučené složky budou spolu s podsložkami vyloučeny jen z Jednoduché Galerie, ostatní aplikace je nadále uvidí.\n\nPokud je chcete skrýt i před ostatními aplikacemi, použijte funkci Skrýt. + Chcete vyloučit nadřazenou složku? + Vyloučené složky budou spolu s podsložkami vyloučeny jen z Jednoduché galerie, ostatní aplikace je nadále uvidí.\n\nPokud je chcete skrýt i před ostatními aplikacemi, použijte funkci Skrýt. Odstranit všechny Odstranit všechny složky ze seznamu vyloučených? Tato operace neodstraní obsah složek. + Skryté složky + Spravovat skryté složky + Zdá se, že nemáte žádné složky skryté pomocí souboru \".nomedia\". Přidané složky Spravovat přidané složky Přidat složku Pokud máte nějaké složky obsahující média, ale nebyly aplikací nalezeny, můžete je zde přidat ručně. + Nebyly nalezeny žádné mediální soubory. Můžete to napravit ručním přidáním složek, které obsahují média. Změnit velikost @@ -51,13 +75,20 @@ Editor - Uložit Otočit - Cesta Neplatná cesta + Invalid video path Úprava souboru selhala + Video editing failed + Image editing cancelled + Video editing cancelled + Soubor byl úspěšně upraven + Image edited successfully + Video edited successfully Upravit soubor pomocí: - Nebyl nalezen žádný editor + Edit video with: + Nebyl nalezen žádný editor + No video editor found Neznámé umístění souboru Nepodařilo se přepsat zdrojový soubor Otočit doleva @@ -66,51 +97,327 @@ Překlopit Překlopit vodorovně Překlopit svisle - Zařízení nemá dostatek paměti + Volný + Jiný Jednoduchá tapeta Nastavit jako tapetu Nastavení tapety selhalo Nastavit jako tapetu pomocí: - Nebyla nalezena žádná vhodná aplikace Nastavuje se tapeta… Tapeta byla úspěšně změněna - Portrait aspect ratio - Landscape aspect ratio + Poměr stran na výšku + Poměr stran na šířku + Domovská obrazovka + Zamykací obrazovka + Domovská a zamykací obrazovka + + + Prezentace + Interval (sekund): + Zahrnout fotografie + Zahrnout videa + Zahrnout GIFy + Náhodné pořadí + Jít opačným směrem + Opakovat prezentaci ve smyčce + Animace + Žádná + Prolnutí + Posun + Prezentace skončila + Nebyla nalezena žádná média pro prezentaci + + + Sloučit přímé podsložky + + + Seskupit podle + Neseskupovat soubory + Složky + Data poslední úpravy + Data poslední úpravy (denně) + Data poslední úpravy (měsíčně) + Data pořízení + Data pořízení (denně) + Data pořízení (měsíčně) + Typu souboru + Přípony + Mějte prosím na paměti, že seskupování a řazení jsou 2 nezávislé hodnoty + + + Složka zobrazená na widgetu: + Zobrazit název složky - Zobrazit skryté média - Automaticky přehrávat videa + Přehrávat videa automaticky + Zapamatovat pozici posledního přehraného videa Přepnout viditelnost názvů souborů - Zobrazit média - Jen obrázky - Jen videa - Gifs only - Images, videos, gifs - Obrázky i videa Přehrávat videa ve smyčce Animovat náhledy souborů GIF - Nastavit jas obrazovky na max při zobrazení médií - Crop thumbnails into squares - Rotate fullscreen media by - System setting - Device rotation - Aspect ratio - Dark background at fullscreen media - Scroll thumbnails horizontally + Maximální jas obrazovky při zobrazení médií + Oříznout náhledy na čtverce + Zobrazit dobu trvání videí + Otáčet média na celé obrazovce podle + Systémového nastavení + Otočení zařízení + Poměru stran + Černé pozadí při médiích na celou obrazovku + Prohlížet miniatury vodorovně + Automaticky skrývat systémové lišty při celoobrazovkových médiích + Odstranit prázdné složky po smazání jejich obsahu + Povolit ovládání jasu vertikálními tahy + Povolit ovládání hlasitosti a jasu videí vertikálními tahy + Zobrazit počet médií ve složce na hlavní obrazovce + Zobrazit rozšířené vlastnosti přes celoobrazovková média + Spravovat rozšířené vlastnosti + Povolit přibližování jedním prstem v celoobrazovkovém režimu + Povolit okamžité přepínání médií klepnutím na okraj obrazovky + Povolit hluboké přibližování obrázků + Skrýt rozšířené vlastnosti pokud je skrytá stavová lišta + Zobrazit některá akční tlačítka ve spodní části obrazovky + Zobrazit odpadkový koš na obrazovce se složkami + Hluboce priblížitelné obrázky + Zobrazit obrázky v nejlepší možné kvalitě + Zobrazit odpadkový koš jako poslední položku na hlavní obrazovce + Povolit zavírání celoobrazovkového režimu tažením prstu dolů + Povolit přiblížení 1:1 dvojitým poklepáním + Vždy otevírat videa na vlastní obrazovce s novými vodorovnými gesty + Zobrazit výřez obrazovky pokud je dostupný + Povolit otáčení obrázků gesty + Priorita načítání obrázků + Rychlost + Kompromis + Nezobrazovat neplatné soubory + Zobrazit typ obrázkových souborů + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Náhledy + Celoobrazovkový režim + Rozšířené vlastnosti + Spodní akční tlačítka + + + Upravit viditelná spodní akční tlačítka + Přepnutí oblíbenosti + Přepnutí viditelnosti souboru + + + Vlastní + Obnovit + Čtverec + Transformovat + Filtr + Žádný + Doladění + Stíny + Expozice + Světla + Jas + Kontrast + Sytost + Jasnost + Gamma + Černé + Bílé + Teplota + Ostrost + Obnovit + Zaostření + Žádné + Radiální + Lineární + Zrcadlové + Gaussian + Text + Nastavení textu + Barva textu + Písmo + Přidat + Upravit + Narovnat + Písmo + Barva + Pozadí + Zarovnání + Do popředí + Vymazat + Váš text + Štětec + Barva + Velikost + Tvrdost + Do popředí + Vymazat + Barva štětce + Editor + Zavřít editor? + Opravdu chcete zahodit úpravy? + Ano + Ne + Zrušit + Potvrdit + Uložit + Exportuje se… + Export %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Překlopit H + Překlopit V + Vrátit + Zopakovat vrácené + Výběr barvy + Průsvitná + Bílá + Šedá + Černá + Světle modrá + Modrá + Fialová + Orchidej + Růžová + Červená + Oranžová + Zlatá + Žlutá + Olivová + Zelená + Akvamarín + Pipetovatelná barva + Trim + + + Jak nastavím Jednoduchou galerii jako výchozí galerii? + Nejdříve musíte najít v nastavení zařízení, v sekci Aplikace, současnou výchozí galerii, zvolit tlačítko s textem ve smyslu \"Nastavení výchozího otevírání\" a následně \"Vymazat výchozí nastavení\". + Pokud poté zkusíte otevřít obrázek nebo video, zobrazí se seznam aplikací, kde můžete zvolit Jednoduchou galerii a nastavit ji jako výchozí. + Uzamknul jsem aplikaci heslem, ale zapomněl jsem ho. Co můžu udělat? + Můžete to vyriešit 2 způsoby. Můžete aplikaci buď přeinstalovat nebo ji najít v nastavení zařízení a zvolit \"Vymazat data\". Vymažou se pouze nastavení aplikace, nikoliv soubory. + Jak mohu dosáhnout, aby bylo dané album stále zobrazeno jako první? + Můžete označit danou složku dlouhým podržením a zvolit tlačítko s obrázkem připínáčku, to ji připne nahoru. Můžete připnout i více složek, budou seřazeny podle zvoleného řazení. + Jak mohu video posunout vpřed? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Jaký je rozdíl mezi Skrytím a Vyloučením složky? + Zatímco vyloučení zamezí zobrazení složky pouze vrámci Jednoduché galerie, skrytí ji ukryje v celém systému, tedy to ovlivní i ostatní galerie. Skrytí funguje pomocí vytvoření prázdného souboru \".nomedia\" v daném adresáři, který můžete vymazat i libovolným správcem souborů. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Proč se mi zobrazují složky s obaly hudebních alb, nebo nálepkami? + Může se stát, že se vám zobrazí také neobvyklé složky. Můžete je ale jednoduše skrýt jejich vybráním pomocí dlouhého podržení a zvolením Vyloučit. Pokud na následujícím dialogu zvolíte vyloučení nadřazené složky, pravděpodobně budou vyloučeny i ostatní podobné složky. + Složka s fotografiemi se mi nezobrazuje nebo ve složce nevidím všechny soubory. Co s tím? + Může to mít více důvodů, řešení je ale jednoduché. Jděte do Nastavení -> Spravovat přidané složky, zvolte Plus a zvolte požadovanou složku. + Co v případě, že chci mít zobrazeno pouze několik složek? + Přidání složky mezi Přidané složky automaticky nevyloučí ostatní. Můžete ale jít do Nastavení -> Spravovat vyloučené složky a zvolit Kořenovou složku \"/\", následně přidat požadované složky v Nastavení -> Spravovat přidané složky. + To způsobí, že budou zobrazeny pouze vyžádané složky, protože vyloučení i přidání fungují rekurzivně a pokud je složka vyloučena i přidaná, bude viditelná. + Dá se s touto aplikací oříznout obrázek? + Ano, oříznutí je možné v editoru, potažením rohů obrázku. Do editoru se můžete dostat buď dlouhým podržením náhledu obrázku a zvolením menu položky Upravit nebo zvolením Upravit při celoobrazovkovém režimu. + Mohu nějakým způsobem seskupit náhledy souborů? + Ano, použitím funkce \"Seskupit podle\" v menu obrazovky s náhledy. Seskupení je možné na základě různých kritérií včetně data vytvoření. Pokud použijete funkci \"Zobrazit obsah všech složek\", můžete je seskupit také podle složek. + Řazení podle data vytvoření nefunguje správně, jak to mohu opravit? + Je to pravděpodobně způsobeno kopírováním souborů. Můžete to opravit označením jednotlivých náhledů souborů a zvolit \"Opravit datum vytvoření\". + Na obrázcích vidím nějaké barevné pásy. Jak mohu zlepšit kvalitu obrázků? + Současné řešení funguje správně v drtivé většině případů, pokud ale chcete zobrazit obrázky v lepší kvalitě, můžete povolit možnost \"Zobrazit obrázky v nejlepší možné kvalitě\" v nastavení aplikace v sekcí \"Hluboko priblížitelné obrázky\". + Skryl jsem soubor/složku, jak jej mohu odkrýt? + Můžete buď použít menu tlačítko \"Dočasně zobrazit skryté položky\" na hlavní obrazovce, nebo v nastavení aplikace zapnout možnost \"Zobrazit skryté položky\", tím se skryté položky zobrazí. Pokud je chcete odkrýt trvale, stačí je dlouho podržet a zvolit možnost \"Odkrýt\". Složky jsou skrývané přidáním souboru \".nomedia\", ten můžete vymazat i libovolným správcem souborů. + Proč aplikace zabírá tolik místa? + Vyrovnávací paměť aplikace může zabírat až 250MB, zabezpečuje to rychlejší nahrávání obrázků. Pokud aplikace zabírá místa více, bude to pravděpodobně způsobeno soubory v odpadkovém koši. Dané soubory se započítávajé do velikosti aplikace. Koš můžete vyprázdnit buď jeho otevřením a smazáním všech souborů, nebo z nastavení aplikace. Položky v koši jsou automaticky mazány po 30 dnech. + + Jednoduchá galerie Pro - Organizér fotografií - Galerie na prohlížení obrázků a videí bez reklam. + Prohlížejte svoje vzpomínky bez prerušení s touto foto a video galerií. - Jednoduchý nástroj použitelný na prohlížení obrázků a videí. Položky mohou být seřazeny podle data, velikosti či názvu vzestupně i sestupně. Obrázky lze přiblížit. Položky jsou zobrazeny ve více sloupcích v závislosti na velikosti displeje, počet sloupců je možné měnit gestem. Soubory můžete přejmenovávat, sdílet, mazat, kopírovat i přesouvat. Obrázky lze ořezávat, otáčen, nebo nastavit jako tapetu přímo v aplikaci. + Jednoduchá galerie Pro je vysoce přizpůsobitelná offline galerie. Organizujte a upravujte své fotografie, obnovujte smazané fotografie s funkcí odpadkového koše, chraňte je a skrývejte. Prohlížejte množství různých foto a video formátů včetně RAW, SVG a mnoho dalších. - Galerie je též k disposici ostatním aplikacím za účelem zobrazení fotografií a videí a přidávání příloh v e-mailových klientech. Je vhodná ke každodennímu použití. + Aplikace neobsahuje žádné reklamy ani nepotřebná oprávnění. Tím, že ani nevyžaduje připojení k internetu, je vaše soukromí maximálně chráněno. - Neobsahuje žádné reklamy ani nepotřebná oprávnění a má otevřený zdrojový kód. Poskytuje možnost změny barev rozhraní. + ------------------------------------------------- + JEDNODUCHÁ GALERIE PRO – FUNKCE + ------------------------------------------------- - Táto aplikace je jen jednou ze skupiny aplikací. Všechny tyto aplikace naleznete na http://www.simplemobiletools.com + • Offline galerie bez reklam a vyskakujících oken + • Foto editor – ořezávejte, otáčejte, měňte velikost, kreslete, používejte filtry a více + • Nevyžaduje přístup k internetu - více soukromí a bezpečí + • Nepožaduje žádná nepotřebná oprávnění navíc + • Rychle prohledávejte obrázky a videa + • Otevírejte mnoho rozličných formátů fotografií a videí (RAW, SVG, GIF, panorama, atd) + • Množství intuitivních gest pro jednoduchou úpravu a organizaci souborů + • Mnoho různých způsobů filtrování, seskupování a řazení souborů + • Změňte si vzhled Jednoduché galerie Pro + • Dostupná ve 32 jazycích + • Označte si oblíbené soubory pro rychlý přístup + • Chraňte své fotografie a videa pomocí pinu, vzoru nebo otiskem prstu + • Použijte pin, vzor a otisk prstu pro zabezpečení spuštění aplikace, nebo i některých funkcí + • Obnovte smazané fotografie a videa z odpadkového koše + • Přepněte viditelnost souborů pro skrytí obrázků a videí + • Vytvořte si ze svých souborů prezentaci + • Zobrazte si detailní informace o svých souborech (rozlišení, hodnoty EXIF, atd) + • Jednoduchá galerie Pro má otevřený zdrojový kód + … a mnoho dalších! + + EDITOR OBRÁZKŮ + Jednoduchá galerie Pro umožňuje snadnou úpravu obrázků. Ořezávejte, překlápějte, otáčejte či měňte jejich velikost. Pokud se cítíte kreativně, můžete také aplikovat filtry nebo do obrázku kreslit! + + PODPORA MNOHA TYPŮ SOUBORŮ + Na rozdíl od některých galerií podporuje Jednoduchá galerie Pro velké množství různých druhů souborů včetně JPEG, PNG, MP4, MKV, RAW, SVG, panoramatických fotografií a videí. + + Vysoce upravitelný správce galerie + Od vzhledu až po funkční tlačítka na spodní liště, Jednoduchá galerie Pro je plně nastavitelná a bude fungovat přesně jak si budete přát. Žádná jiná galerie nenabízí takovou flexibilitu! A díky otevřenému kódu je naše aplikace dostupná ve 32 jazycích! + + OBNOVTE SMAZANÉ FOTOGRAFIE A VIDEA + Smazali jste nechtěně důležitou fotografii nebo video? Žádný strach! Jednoduchá galerie Pro pro podobné případy nabízí funkci odpadkového koše, odkud smazané fotografie a videa snadno obnovíte. + + CHRAŇTE A SKRÝVEJTE SVÉ FOTOGRAFIE A VIDEA + Použitím pinu, vzoru nebo otisku prstu snadno své fotografie, videa či celá alba ochráníte nebo skryjete. Můžete ochránit i spuštění samotné aplikace, nebo některé její funkce. Například můžete zabránit náhodnému smazání souboru bez potvrzení otiskem prstu. + + Prohlédněte si celou sadu Jednoduchých aplikací na: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + Filtrer medier + Billeder + Videoer + GIF\'er + RAW-billeder + SVG\'er + Portrætter + Der blev ikke fundet nogen filer med det valgte filter. + Skift filter + + + Denne funktion skjuler mappen og dens eventuelle undermapper ved at oprette en \".nomedia\"-fil i den. Du kan se dem ved at klikke på \"Vis skjulte\" i indstillingerne. Fortsæt? + Ekskluder + Ekskluderede mapper + Administrer ekskluderede mapper + Dette vil kun ekskludere de valgte mapper (og deres undermapper) fra Simple Gallery. Du kan administrere ekskluderede mapper i indstillingerne. + Ekskluder en overliggende mappe i stedet? + Ekskludering af mapper vil skjule dem og eventuelle undermapper for Simple Gallery, de vil stadig være synlige for andre apps.\n\nHvis du også vil skjule dem for andre apps, skal du bruge funktionen Skjul. + Fjern alle + Fjern alle fra listen med ekskluderede mapper? Det vil ikke slette mapperne. + Skjulte mapper + Administrer skjulte mapper + Det ser ikke ud til at du har nogen skjulte mapper med en \".nomedia\"-fil. + + + Inkluderede mapper + Administrer inkluderede mapper + Tilføj mappe + Hvis du har mapper med mediefiler som appen ikke har fundet, kan du manuelt tilføje dem her.\n\nDet vil ikke ekskludere andre mapper. + Ingen mediefiler er fundet. Dette kan løses ved manuelt at tilføje mapper som indeholder mediefiler. + + + Skaler + Skaler valgte elementer og gem + Bredde + Højde + Bevar billedforhold + Indtast en gyldig opløsning + + + Editor + Roter + Ugyldig sti til billede + Invalid video path + Redigering af billede mislykkedes + Video editing failed + Billede redigering annulleret + Video editing cancelled + Fil redigeret med succes + Image edited successfully + Video edited successfully + Rediger billede med: + Edit video with: + Der blev ikke fundet en editor til billedbehandling + No video editor found + Ukendt filplacering + Kunne ikke overskrive kildefilen + Roter mod venstre + Roter mod højre + Roter 180º + Spejlvend + Spejlvend vandret + Spejlvend lodret + Fri + Andet + + + Simple Wallpaper + Sæt som baggrundsbillede + Det mislykkedes at sætte billedet som baggrund + Sæt som baggrundsbillede med: + Sætter baggrundsbillede… + Sat som baggrundsbillede + Stående billedformat + Liggende billedformat + Hjemmeskærm + Låseskærm + Hjemme- og låseskærm + + + Slideshow + Frekvens (sekunder): + Inkluder billeder + Inkluder videoer + Inkluder GIF\'er + Tilfældig rækkefølge + Kør baglæns + Endeløs kørsel + Animation + Ingen + Udton + Glid + Slideshowet endte + Der blev ikke funket nogen mediefiler til slideshowet + + + Gruppér direkte undermapper + + + Gruppér efter + Gruppér ikke filer + Mappe + Sidst ændret + Sidst ændret (daglig) + Sidst ændret (månedlig) + Eksponeringsdato + Eksponeringsdato (daglig) + Eksponeringsdato (månedlig) + Filtype + Filendelse + Vær opmærksom på at gruppering og sortering er to individuelle felter + + + Mappe vist på widget: + Vis mappenavn + + + Afspil automatisk videoer + Husk sidste position ved videoafspilning + Skift filnavnets synlighed + Kør videoer i sløjfe + Animer GIF\'er i miniaturer + Maksimal lysstyrke ved fuldskærmsvisning af medier + Beskær miniaturer til kvadrater + Vis varighed på video + Roter fuldskærmsmedier efter + Systemindstilling + Enhedens orientering + Billedformat + Sort baggrund og statuslinje ved medievisning i fuldskærm + Scroll miniaturer vandret + Skjul automatisk systemets brugerflade ved fuldskærmsvisning af medier + Slet tomme mapper efter sletning af deres indhold + Tillad kontrol af lysstyrke på billeder med lodrette bevægelser + Tillad kontrol af lyd- og lysstyrke på videoer med lodrette bevægelser + Vis antal filer i hver mappe i oversigten + Vis udvidede oplysninger over medier i fuldskærm + Administrer udvidede oplysninger + Tillad zoom med en finger når medier er i fuldskærm + Tillad skift af medie ved klik på skærmens sider + Tillad dyb zooming af billeder + Skjul udvidede oplysninger når statuslinjen er skjult + Vis handlingsknapper i bunden af skærmen + Vis papirkurven ved mappevisning + Dybt zoombare billeder + Vis billeder i den højst mulige kvalitet + Vis papirkurven som sidste element på hovedskærmen + Luk fuldskærmsvisning ved at swipe ned + Tillad 1:1 zooming med to dobbelttryk + Åbn altid videoer på en separat skærm med nye vandrette bevægelser + Vis \"notch\" hvis tilgængelig + Tillad rotering af billeder med bevægelser + Prioritering af filindlæsning + Hastighed + Kompromis + Undgå at vise ugyldige filer + Vis billeders filtyper + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Miniaturer + Fuldskærmsmedier + Flere oplysninger + Handlingsknapper + + + Administrer synlige handlingsknapper + Favorit + Synlighed + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + Hvordan kan jeg gøre Simple Gallery til min enheds standardgalleri? + Først skal du finde det nuværende standardgalleri, i Apps-sektionen af din enheds indstillinger. Kig efter en knap som hedder noget i stil med \"Åbn som standard\", klik på denne og vælg \"Ryd standarder\". + Næste gang du forsøger at åbne et billede eller en video, bør du se en app-vælger, hvor du kan vælge Simple Gallery og gøre den til standardapp. + Jeg har låst appen med en adgangskode, men jeg har glemt den. Hvad kan jeg gøre? + Du har to muligheder. Du kan enten geninstallere appen, eller finde den i indstillingerne på din enhed og vælge \"Ryd data\". Dette vil nulstille alle dine indstillinger, det vil ikke slette nogen mediefiler. + Hvordan kan jeg altid få et bestemt album vist i toppen? + Du kan holde fingeren nede på det ønskede album, og vælge tegnestift-ikonet i menuen, dette vil fastgøre den til toppen. Du kan fastgøre flere mapper også. Fastgjorte elementer vil blive sorteret efter standard sorterings-metoden. + Hvordan kan jeg spole fremad i videoer? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Hvad er forskellen på at skjule og ekskludere en mappe? + Eksludering forhindrer kun visning af mappen i Simple Gallery, mens Skjul virker på systemniveau og skjuler mappen fra andre gallerier også. Det fungerer ved at oprette en tom \".nomedia\"-fil i den givne mappe, som du kan slette med enhver filhåndterings-app. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Hvorfor dukker mapper med musikomslag eller klistermærker op? + Det kan ske at du vil se nogle udsædvanlige albummmer. Du kan nemt ekskludere disse, ved at holde fingeren nede på disse og vælge Ekskluder. I den næste dialogboks kan du vælge den ovenliggende mappe, da andre relaterede albummer så sandsynligvis også vil blive forhindret i at blive vist. + En mappe med billeder dukker ikke op, eller den viser ikke alle elementer. Hvad kan jeg gøre? + Der kan være flere grunde, men en løsning er nem. Gå til Indstillinger -> Administrer inkluderede mapper, vælg plusset og naviger til mappen. + Hvad hvis jeg kun ønsker få et par bestemte mapper vist? + At tilføje en mappe ved de Inkluderede mapper, ekskluderer ikke automatisk alt. Det du kan gøre, er at gå til Indstillinger -> Adminsterer ekskluderede mapper, eksludere rodmappen \"/\", og så tilføje de ønskede mapper under Indstillinger -> Administrer inkluderede mapper. + Det vil kun gøre de valgte mapper synlige, da både ekskludering og inkludering virker reskursivt, og hvis en mappe både er ekskluderet og inkluderet, vil den blive vist. + Kan jeg beskære billeder med denne app? + Ja, du kan beskære billeder i editoren, ved at trække i billedets kanter. Du kan gå til editoren, ved enten at holde fingeren nede på et miniaturebillede og vælge Rediger, eller vælge Rediger fra fuldskærmsvisningen. + Kan jeg på en eller anden måde gruppere miniaturebilleder til mediefiler? + Sagtens, brug menupunktet \"Gruppér efter\", mens du ser miniaturebillederne. Du kan gruppere flere efter flere kriterier, inklusiv eksponeringsdato. Hvis du bruger funktionen \"Vis indholdet af alle mapper\", kan du også gruppere dem efter mapper. + Sortering efter eksponeringsdato ser ikke ud til at fungere. Hvordan kan jeg fikse det? + Det skyldes højst sandsynligt at filerne er kopieret fra et andet sted. Du kan fikse det ved at vælge filens miniature, og vælge \"Fiks eksponeringsdato\". + Jeg ser noget \"color banding\" på billederne. Hvordan kan jeg forbedre kvaliteten? + Den nuværende løsning til visning af billeder virker fint i langt de fleste tilfælde, men hvis du vil have en endnu bedre billedkvalitet, kan du aktivere \"Vis billeder i den højst mulige kvalitet\" i appens indstillinger, i sektionen \"Dybt zoombare billeder\". + Jeg har en skjult fil/mappe. Hvordan kan jeg få den vist igen? + Du kan enten trykke på menupunktet \"Vis midlertidigt skjulte\" på hovedskærmen, eller aktivere \"Vis skjulte elementer\" i appens indstillinger for at se det skjulte element. Hvis du vil fjerne skjulningen, skal du blot holde fingeren nede og vælge \"Fjern skjulning\". Mapper er skjult ved at tilføje en skjult \".nomedia\"-fil i dem, som du også kan slette med enhver filhåndterings-app. + Hvorfor fylder appen så meget? + App-mellemlageret kan bruge op til 250MB, det sikrer hurtigere indlæsning. Fylder appen endnu mere kan det skyldes at der ligger meget papirkurven. Filer heri tæller med til appens størrelse. Du kan tømme papirkurven ved at åbne den og slette indholdet, eller fra appens indstillinger. Alle filer i papirkurven slettes automatisk efter 30 dage. + + + + Simple Gallery Pro - Billedhåndtering + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro er et offline-galleri med mange tilpasningsmuligheder. Organiser og rediger dine billeder, gendan slettede filer via papirkurven, beskyt og skjul filer og se adskillige forskellige billed- og videoformater inklusiv RAW, SVG og mange flere. + + Appen indeholder ingen reklamer og kræver ingen unødvendige tilladelser. Da den heller ikke kræver adgang til internettet, er dit privatliv også beskyttet. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FUNKTIONER + ------------------------------------------------- + + • Offline galleri uden reklamer eller pop op\'er + • Simple gallerys billedbehandler – beskær, roter, tilpas størrelse, tegn, filtrer mm. + • Inget krav om internetadgang - en stor fordel for dit privatliv og din sikkerhed + • Ingen krav om unødvendige tilladelser + • Hurtig søgning efter billeder, videoer & filer + • Åbn & se mange forskellige foto- og videoformater (RAW, SVG, panoramic osv.) + • En række intuitive bevægelser til nem redigering og organisering af filer + • Mange muligheder for filtrering, gruppering og sortering af filer + • Tilpas udseendet af Simple Gallery Pro + • Tilgængelig på 32 sprog + • Marker filer som favoriter for hurtig adgang + • Beskyt dine billeder & videoer med et mønster, pinkode eller fingeraftryk + • Brug mønster, pinkode eller fingeraftryk til beskyttelse af åbning af appen eller specifikke funktioner + • Gendan slettede billeder & videoer via papirkurven + • Skift mellem vis/skjul filer for at skjule billeder & videoer + • Opret et redigerbart slideshow med dine filer + • Se detaljeret information om dine filer (opløsning, EXIF-værdier osv.) + • Simple Gallery Pro er open source + … og meget mere! + + FOTOGALLERI - EDITOR + Simple Gallery Pro gør det nemt at redigere dine billeder i en fart. Beskær, flip, roter og tilpas størrelsen på dine billeder. Føler du dig lidt mere kreativ, kan du tilføje filtre og tegne oveni dine billeder! + + UNDERSTØTTELSE AF MANGE FILTYPER + I modsætning til mange andre gallerier supporterer Simple Gallery Pro en masse forskellige filtyper inklusiv JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic-foto, Panoramic-videoer og mange flere. + + GALLERI MED MASSER AF TILPASNINGSMULIGHEDER + Simple Gallery Pro kan tilpasses lige fra brugerfladen til funktionsknapper på nederste værktøjslinje og fungere lige efter dit ønske. Inget andet galleri er så fleksibelt! Takket være at det er open sourse, er det også tilgængeligt på 32 sprog! + + GENDAN SLETTEDE BILLEDER & VIDEOER + Er du ved en fejl kommet til at slette et billede eller en video? Intet problem! Simple Gallery Pro har en handy papirkurv hvorfra du nemt kan gendanne slettede billeder & videoer. + + BESKYT & SKJUL FOTOS, VIDEOER & FILER + Du kan beskytte og skjule billeder, videoer og hele album med en pinkode, et mønster eller din enheds fingeraftryksscanner. Du kan også beskytte selve appen eller låse specifikke funktioner i appen. Du kan for eksempel låse sletning af en fil uden scanning af fingeraftryk og dermed beskytte filer mod at blive slettet ved en fejl. + + Se hele suiten af Simple Tools her: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5e8f7e578..d42212487 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -4,42 +4,66 @@ Galerie Bearbeiten Kamera öffnen - Öffnen mit - Keine passende App gefunden (versteckt) + (ausgenommen) Ordner anheften Ordner loslösen + Oben anheften Ansicht: Alle Medien Alle Medien Ansicht: Als Ordner Ordner wählen Auf Karte zeigen - Unbekannter Ort - Keine Karten-App gefunden - Keine Kamera-App gefunden - Kacheln verkleinern - Kacheln vergrößern - Verstecktes temporär zeigen - Change cover image - Select photo - Use default + Unbekannter Pfad + Lautstärke + Helligkeit + Bildausrichtung sperren + Bildausrichtung entsperren + Bildausrichtung ändern + Bildausrichtung im Hochformat + Bildausrichtung im Querformat + Automatische Bildausrichtung + Aufnahmedatum korrigieren + Korrigiere… + Datum erfolgreich korrigiert. + Es wurden keine Werte für das Aufnahmedatum gefunden + Teile eine verkleinerte Version + Hey,\n\nes sieht so aus, als hättest du von der alten kostenlosen App geupgraded. Du kannst nun die alte Version deinstallieren, die oben in den App-Einstellungen einen \'Upgrade auf Pro\' Button hat.\n\nEs wird nur der Papierkorb gelöscht, die Markierungen von Favoriten entfernt und die App-Einstellungen zurückgesetzt.\n\nDanke! + Zur Dateisuche in allen sichtbaren Ordnern wechseln + Als Standardordner festlegen + Nicht mehr als Standardordner festlegen + + + Filter + Bilder + Videos + GIFs + RAW-Bilder + SVGs + Portraits + Keine Medien für die ausgewählten Filter gefunden. + Filter ändern - Diese Funktion versteckt ausgewählte Ordner (auch für andere Apps), indem dort im Dateisystem eine \'.nomedia\'-Datei abgelegt wird. Dadurch werden auch deren Unterordner versteckt. Solche Ordner werden nur gezeigt, wenn die Einstellung \'Versteckte Ordner zeigen\' aktiv ist (auch andere Apps bieten üblicherweise eine solche Option). Fortfahren? - Ordner ausblenden - Ausgeblendete Ordner - Ausgeblendete Ordner verwalten - Diese Funktion blendet ausgewählte Ordner und deren Unterordner aus (nur in dieser App). Ausgeblendete Ordner können in den Einstellungen verwaltet werden. - Möchten Sie stattdessen einen höherliegenden Ordner ausblenden? - \'Ordner ausblenden\' wird ausgewählte Ordner und deren Unterordner nur in dieser App ausblenden. Andere Apps werden solche Ordner weiterhin anzeigen.\\n\\nWenn Sie Ordner auch für andere Apps verstecken wollen, verwenden Sie dafür die Funktion \'Ordner verstecken\'. + Diese Funktion versteckt die ausgewählten Ordner, indem im Dateisystem eine \'.nomedia\'-Datei abgelegt wird. Dadurch werden die ausgewählten Ordner und alle Unterverzeichnisse auch in anderen Apps nicht angezeigt. Solche Ordner werden nur gezeigt, wenn die Einstellung \'Versteckte Elemente anzeigen\' aktiv ist (auch andere Apps bieten üblicherweise eine solche Option).\nFortfahren? + Ordner ausschließen + Ausgeschlossene Ordner + Ausgeschlossene Ordner verwalten + Diese Funktion blendet die ausgewählten Ordner und deren Unterordner nur in dieser App aus. Ausgeschlossene Ordner können in den Einstellungen verwaltet werden. + Möchtest du stattdessen einen höherliegenden Ordner ausschließen? + \'Ordner ausschließen\' wird ausgewählte Ordner und deren Unterordner nur in dieser App ausblenden. Andere Apps werden solche Ordner weiterhin anzeigen.\n\nWenn du Ordner auch für andere Apps verstecken willst, verwende dafür die Funktion \'Ordner verstecken\'. Alle entfernen - Alle Ordner aus der Ausgeblendet-Liste entfernen? Die Ordner selbst werden nicht gelöscht. + Alle Ordner aus der Liste ausgeblendeter Ordner entfernen? Die Ordner selbst werden nicht gelöscht. + Versteckte Ordner + Versteckte Ordner verwalten + Keinen mit einer \".nomedia\"-Datei versteckten Ordner gefunden. Einbezogene Ordner Einbezogene Ordner verwalten Ordner hinzufügen - Sollten Sie weitere Medien-Ordner haben, die von der App nicht erkannt wurden, können Sie diese hier manuell hinzufügen. + Solltest du weitere Mediendateien haben, die von der App nicht gefunden wurden, kannst du deren Ordner hier manuell hinzufügen. + Es konnten keine Mediendateien gefunden werden. Das Problem kann behoben werden, indem die Ordner mit den Mediendateien manuell hinzugefügt werden. Größe ändern @@ -47,70 +71,352 @@ Breite Höhe Seitenverhältnis beibehalten - Bitte eine gültige Auflösung eingeben + Bitte eine gültige Auflösung eingeben. Editor - Speichern Drehen - Pfad Ungültiger Dateipfad + Invalid video path Bildbearbeitung fehlgeschlagen + Video editing failed + Bildbearbeitung abgebrochen + Video editing cancelled + Datei erfolgreich bearbeitet + Image edited successfully + Video edited successfully Bild bearbeiten mit: - Keine Bildeditor-App gefunden - Unbekannter Dateiort + Edit video with: + Keine Bildeditor-App gefunden + No video editor found + Unbekannter Dateipfad Konnte Quelldatei nicht überschreiben Nach links drehen Nach rechts drehen - Um 180º drehen + Um 180° drehen Spiegeln Horizontal spiegeln Vertikal spiegeln - Fehler: Zuwenig Speicher + Beliebiges + Anderes Schlichter Hintergrund Als Hintergrund festlegen - Hintergrundbild festlegen fehlgeschlagen - Als Hintergrund festlegen mit: - Keine Hintergrundbild-App gefunden + Hintergrundbild festlegen fehlgeschlagen. + Als Hintergrund festlegen mit Hintergrund festlegen… - Hintergrundbild erfolgreich festgelegt + Hintergrundbild erfolgreich festgelegt. Hochformat Querformat + Startbildschirm + Sperrbildschirm + Start- und Sperrbildschirm + + + Diashow + Intervall (Sekunden): + Bilder verwenden + Videos verwenden + GIFs verwenden + Zufällige Reihenfolge + Rückwärts abspielen + Endlos abspielen + Animation + Keine + Verblassen + Schieben + Diashow beendet + Keine Medien für Diashow gefunden + + + Direkte Unterordner gruppieren + + + Gruppieren nach + Dateien nicht gruppieren + Ordner + Datum der letzten Änderung + Zuletzt geändert (täglich) + Zuletzt geändert (monatlich) + Aufnahmedatum + Aufnahmedatum (täglich) + Aufnahmedatum (monatlich) + Dateityp (Bilder/Videos) + Dateierweiterung + Bitte beachte, dass Gruppieren und Sortieren zwei unabhängige Felder sind. + + + Ordner, der auf dem Widget angezeigt wird: + Ordnernamen anzeigen - Versteckte Ordner zeigen Videos automatisch abspielen + Letzte Videowiedergabeposition erinnern Beschriftungen ein/aus - Medien auswählen - Nur Bilder - Nur Videos - Gifs only - Images, videos, gifs - Bilder und Videos - Videos in Endlosschleife spielen - Kacheln bei GIFs animieren + Videos in Endlosschleife abspielen + Kacheln von GIFs animieren Helligkeit beim Betrachten maximieren Kacheln quadratisch zuschneiden - Im Vollbild ausrichten nach + Videodauer anzeigen + Im Vollbild ausrichten nach: Systemeinstellung Gerätedrehung Seitenverhältnis - Schwarzer Hintergrund im Vollbild - Scroll thumbnails horizontally + Schwarzer Hintergrund & schwarze Systemleiste im Vollbild + Kacheln horizontal scrollen + Systemleiste im Vollbild ausblenden + Leere Ordner automatisch löschen + Fotohelligkeit mit vertikalen Gesten ändern + Gesten für Videolautstärke/Helligkeit zulassen + Medienanzahl bei Ordnern anzeigen + Dateieigenschaften in Vollbild-Anzeige einblenden + Eigenschaften auswählen + Ein-Finger-Zoom im Vollbild zulassen + Beim Tippen auf eine Bildschirmseite sofort zwischen Medien wechseln + Starkes Zoomen zulassen + Dateieigenschaften im Vollbild nicht anzeigen, wenn die Systemleiste versteckt ist + Ausgewählte Funktionen am unteren Bildschirmrand anzeigen + Papierkorb auf dem Ordnerbildschirm anzeigen + Stark vergrösserbare Bilder + Zeige Bilder in der höchstmöglichen Qualität + Zeige den Papierkorb als letztes Element auf dem Hauptbildschirm + Erlaube das Schließen der Vollbildansicht mit einer Abwärtsgeste + Erlaube 1:1 Zoom mit zweimaligem, doppeltem Antippen + Öffne Videos immer auf einem separaten Bildschirm mit neuen horizontalen Gesten + Zeige eine Notch, wenn vorhanden + Rotieren von Bildern mit Gesten zulassen + Priorität beim Laden von Dateien + Geschwindigkeit + Kompromiss + Das Anzeigen von ungültigen Dateien vermeiden + Bilddateitypen anzeigen + Erlaube Zoomen in Videos mit doppeltem Antippen + Ordner-Miniaturbildstil + File thumbnail style + Thumbnail spacing + Zeige Anzahl der Dateien in einer seperaten Zeile an + Anzahl der Dateien in Klammern anzeigen + Anzahl der Dateien nicht anzeigen + Begrenze lange Ordnertitel auf 1 Zeile + Quadrat + Abgerundete Ecken + + + Vorschaubilder + Vollbild-Anzeige von Medien + Erweiterte Details + Funktionen am unteren Bildschirmrand + + + Funktionen auswählen + Favoriten umschalten + Schaltet die Sichtbarkeit von Dateien um + + + Benutzerdefiniert + Zurücksetzen + Quadrat + Umwandeln + Filter + Kein + Anpassen + Schatten + Belichtung + Highlights + Helligkeit + Kontrast + Sättigung + Struktur + Gamma + Schwarz + Weiß + Temperatur + Schärfe + Zurücksetzen + Fokus + Kein + Radial + Linear + Gespiegelt + Gaußsch + Text + Textoptionen + Textfarbe + Schriftart + Hinzufügen + Bearbeiten + Begradigen + Schrift + Farbe + Hintergrund + Ausrichtung + Nach Vorne + Löschen + Dein Text + Pinsel + Farbe + Größe + Stärke + Nach Vorne + Löschen + Pinselfarbe + Editor + Editor schließen? + Do you really want to discard the changes? + Ja + Nein + Abbrechen + Akzeptieren + Speichern + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + H spiegeln + V spiegeln + Rückgängig + Wiederholen + Farbauswahl + Transparent + Weiß + Grau + Schwarz + Hellblau + Blau + Violett + Orchidee + Pink + Rot + Orange + Gold + Geld + Olive + Grün + Aquamarin + Pipettierbare Farbe + Trim + + + Wie kann ich Schlichte Galerie als Standardanwendung auswählen? + Zunächst musst du unter \"Apps\" in den Geräteeinstellungen die aktuelle Standardgalerie finden. Suche nach einer Bedienfläche wie \"Standardmässig öffnen\", betätige diese und wähle \"Standardeinstellungen löschen\". + Das nächste Mal, wenn du ein Bild oder ein Video öffnest, sollte ein Auswahlfenster erscheinen und es möglich sein, Schlichte Galerie als Standard-App auszuwählen. + Ich habe die App mit einem Passwort geschützt und es vergessen. Was kann ich tun? + Es gibt zwei Möglichkeiten: Entweder du installierst die App erneut oder du wählst in den Geräteeinstellungen \'Apps\' aus und dann \"Daten löschen\". Alle Einstellungen werden zurückgesetzt, alle Mediendateien bleiben unberührt. + Wie kann ich ein Album immer zuoberst erscheinen lassen? + Du kannst lange auf das gewünschte Album drücken und im Aktionsmenü das Stecknadelsymbol auswählen; es wird nun zuoberst angepinnt. Ebenso kannst du mehrere Ordner anpinnen. Angepinnte Objekte werden nach der Standardmethode sortiert. + Wie kann ich in Videos vor- oder zurückspringen? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Was ist der Unterschied zwischen \'Verstecken\' und \'Ausschließen\' eines Ordners? + \'Ausschließen\' verhindert lediglich, dass der Ordner in Schlichte Galerie angezeigt wird. \'Verstecken\' hingegen versteckt den Ordner auch vor anderen Apps. Dies funktioniert durch das Erstellen einer leeren \".nomedia\"-Datei im betroffenen Ordner, welche du mit jedem Dateimanager wieder löschen kannst. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Wieso erscheinen Ordner mit Musik-Cover oder Stickers? + Es kann geschehen, dass manche ungewöhnliche Alben erscheinen. Diese kannst du ausschließen durch gedrückt halten und Auswählen von Ausschließen. Im nächsten Dialog kannst du den übergeordneten Ordner auswählen und dadurch sollten die anderen zugehörigen Alben auch nicht auftauchen. + Ein Ordner mit Bildern wird nicht angezeigt. Was kann ich tun? + Öffne die Galerie-Einstellungen, wähle dort \'Einbezogene Ordner verwalten\', dann das Plus-Zeichen oben rechts und navigiere zum gewünschten Ordner. + Wie kann ich erreichen, dass nur ein paar definierte Ordner sichtbar sind? + Einen Ordner zu den \'einbezogenen Ordnern\' hinzuzufügen, schließt nicht automatisch alle anderen aus. Eine Möglichkeit ist, in den Galerie-Einstellungen \'Ausgeschlossene Ordner verwalten\' zu wählen, den Stammordner \"/\" auszuschliessen und dann alle gewünschten Ordner in \'Einbezogene Ordner verwalten\' hinzuzufügen. + Kann ich mit dieser App Bilder zuschneiden? + Ja, du kannst Bilder über das Ziehen der Bildecken im Editor zuschneiden. Du gelangst zum Editor indem du lange auf ein Vorschaubild drückst und Bearbeiten auswählst oder durch Auswählen von Bearbeiten in der Vollbildansicht. + Kann ich Mediendatei-Thumbnails irgendwie gruppieren? + Verwende dazu den Menüeintrag \"Gruppieren nach\" in der Miniaturansicht. Du kannst Dateien nach mehreren Kriterien gruppieren, einschließlich Aufnahmedatum. Wenn Du die Funktion \"Alle Ordnerinhalte anzeigen\" verwendest, kannst du sie auch nach Ordnern gruppieren. + Die Sortierung nach Datum ist nicht korrekt, wie kann ich das korrigieren? + Die wahrscheinliche Ursache ist, dass deine Bilder von woanders kopiert worden sind. In diesem Fall solltest du die Miniatur-Ansichten der Bilder durch etwas längeres Antippen markieren und dann im Menü oben rechts die Funktion \'Aufnahmedatum korrigieren\' ausführen. + Ich sehe Banding (streifiger Farbverlauf) auf den Bildern. Wie kann ich die Qualität verbessern? + Die jetzige Methode für die Anzeige von Bildern funktioniert gut, aber für eine noch bessere Bildqualität kann die Einstellung \"Zeige Bilder in der höchstmöglichen Qualität\" im Menü unter \"Stark vergrösserbare Bilder\" gesetzt werden. + Ich habe eine versteckte Datei bzw. einen versteckten Ordner. Wie kann ich diese/n sichtbar stellen? + Du kannst entweder auf \"Verstecktes temporär anzeigen\" im Hauptmenü drücken oder die Einstellung \"Versteckte Elemente anzeigen\" setzen. Wenn du es sichtbar einstellen willst, drücke lange darauf und wähle \"Nicht verstecken\" aus. Ordner werden durch eine versteckte, in ihnen gespeicherte \".nomedia\"-Datei versteckt und das Löschen der Datei ist mit jedem Dateimanger möglich. + Warum beansprucht die App so viel Speicherplatz? + Der Cache der App kann bis zu 250 MB groß werden und sorgt dafür, dass die Bilder schneller geladen werden. Wenn die App noch mehr Speicherplatz beansprucht, liegt das wahrscheinlich daran, dass der Papierkorb zu voll ist. Diese Dateien zählen zum Speicherplatz der App dazu. Du kannst den Papierkorb leeren, indem du ihn öffnest und alle Dateien darin löschst, oder den entsprechenden Button in den Einstellungen betätigst. All 30 Tage wird der Papierkorb automatisch geleert. + + Schlichte Galerie Pro - Foto Manager & Editor - Eine schlichte Galerie zum Betrachten von Bildern und Videos ohne Werbung. + Einfache und übersichtliche Galerie zur Verwaltung von Alben, Fotos, Videos - Ein schlichtes Tool zum Betrachten von Bildern und Videos. Die Medien können nach Datum, Größe, Name sowie auf- oder absteigend sortiert angezeigt werden, in Bilder kann hineingezoomt werden. Die Vorschau-Kacheln werden in mehreren Spalten abhängig von der Displaygröße angezeigt, die Spaltenanzahl ist mit Zweifingergesten änderbar. Die Medien können umbenannt, geteilt, gelöscht, kopiert, verschoben werden. Bilder können zugeschnitten, gedreht oder als Hintergrund festgelegt werden, direkt aus der App heraus. + Schlichte Galerie Pro ist eine stark individualisierbare Offline Galerie. Ordne & bearbeite deine Fotos, stelle gelöschte Fotos mit Hilfe des Papierkorbs wieder her, schütze & verstecke Dateien und zeige eine Vielzahl von Bilder- & Videoformaten an, unter anderem RAW, SVG und viele mehr. - Die Galerie bietet auch für Drittparteien einige Funktionen an: zum Vorschauen von Bildern / Videos, zum Hinzufügen von Anhängen bei eMail-Clients, etc. Sie ist perfekt für den täglichen Gebrauch. + Die App enthält keine Werbung und verlangt keine unnötigen Berechtigungen. Da die App auch keinen Internetzugang benötigt, ist deine Privatsphäre geschützt. - Beinhaltet keine Werbung oder unnötige Berechtigungen. Sie ist komplett Open Source, verwendete Farben sind anpassbar. + ------------------------------------------------- + SCHLICHTE GALERIE PRO – EIGENSCHAFTEN + ------------------------------------------------- - Diese App ist nur eine aus einer größeren Serie von schlichten Apps. Der Rest davon findet sich auf http://www.simplemobiletools.com + • Offline Galerie ohne Werbung und Popups + • Schlichte Galerie Fotoeditor – Zuschneiden, Drehen, Ändern der Größe, Zeichnen, Filtern & mehr + • Kein Internetzugriff benötigt – mehr Privatsphäre und Sicherheit für dich + • Keine unnötigen Berechtigungen erforderlich + • Suche schnell nach Bildern, Videos & Dateien + • Öffne & zeige verschiedene Bild- und Videoformate an (RAW, SVG, Panoramabilder usw.) + • Eine Vielzahl von intuitiven Gesten zur einfachen Bearbeitung & Ordnung von Dateien + • Viele Möglichkeiten Dateien zu filtern, gruppieren & sortieren + • Personalisiere das Erscheinungsbild der Galerie + • Verfügbar in 32 Sprachen + • Markiere Dateien als Favoriten, um schnell auf diese zuzugreifen + • Schütze deine Fotos & Videos mit einem Muster, PIN oder Fingerabdruck + • Nutze PIN, Muster & Fingerabruck zum Schutz der gesamten App oder auch bestimmter Funktionen + • Stelle gelöschte Bilder & Videos aus dem Papierkorb wieder her + • Ändere die Sichtbarkeit von Dateien und Ordnern, um Fotos und Videos zu verstecken + • Erstelle eine eigene Diashow deiner Dateien + • Zeige detaillierte Informationen deiner Dateien an (Auflösung, EXIF Werte usw.) + • Schlichte Galerie Pro ist Open Source + … und viele, viele mehr! + + FOTOEDITOR + Schlichte Galerie Pro macht es schnell und einfach deine Bilder zu bearbeiten. Schneide Bilder zu, drehe sie und ändere die Größe. Wenn du dich etwas kreativer fühlst, kannst du Filter hinzufügen und auf deinen Bildern malen! + + UNTERSTÜTZUNG FÜR VIELE DATEITYPEN + Im Gegensatz zu einigen anderen Galerien unterstütz Schlichte Galerie Pro eine Vielzahl verschiedener Dateitypen. Unter anderem JPEG, PNG, MP4, MKV, RAW, SVG, Panoramabilder, Panoramavideos und viele mehr. + + STARK INDIVIDUALISIERBARE GALERIE + Von der Benutzeroberfläche zu den Funktionen in der unteren Symbolleiste, Schlichte Galerie Pro ist stark anpassbar und funktioniert ganz nach deinen Wünschen. Keine Andere Galere verfügt über dieses Maß an Flexibilität. Danke Open Source sind wir in 32 Sprachen verfügbar! + + WIEDERHERSTELLUNG GELÖSCHTER FOTOS & VIDEOS + Versehentlich ein wertvolles Foto oder Video gelöscht? Keine Sorge! Schlichte Galerie Pro verfügt über einen praktischen Papierkorb, aus dem du gelöschte Bilder & Videos leicht wiederherstellen kannst. + + SCHÜTZE UND VERSTECKE FOTOS, VIDEOS & DATEIEN + Mit einem PIN, Muster oder dem Fingerabdrucksensor deines Gerätes kannst du Fotos, Videos und komplette Alben verstecken und schützen. Du kannst auch die App selber schützen oder bestimmte Funktionen der App sperren. Beispielsweise kannst du eine Datei nicht ohne Scan des Fingerabdrucks löschen, um deine Dateien vor versehentlichem Löschen zu schützen. + + Schau dir die vollständige Serie der Schlichten Apps hier an: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + Φιλτράρισμα πολυμέσων + Εικόνες + Βίντεο + GIFs + RAW Εικόνες + SVGs + Πορτραίτα + Δεν βρέθηκε κανένα αρχείο πολυμέσων με τα επιλεγμένα φίλτρα. + Αλλαγή φίλτρων + + + Αυτή η λειτουργία κρύβει τον φάκελο προσθέτοντας ένα \".nomedia\" αρχείο μέσα του, θα κρύψει και όλους τους υποφακέλους επίσης. Μπορείτε να τους δείτε με την επιλογή του \'Εμφάνιση κρυφών δεδομένων\' στις Ρυθμίσεις. Συνέχεια? + Εξαίρεση + Εξαίρεση φακέλων + Διαχείριση εξαιρεμένων φακέλων + Αυτό θα εξαιρέσει το επιλεγμένο μαζί με τους υποφακέλους από την Απλή Συλλογή μόνο. Μπορείτε να διαχειριστείτε τους φακέλους που εξαιρέθηκαν στις Ρυθμίσεις. + Να εξαιρεθεί μήπως ο γονικός φάκελος; + Εξαιρώντας τους φακέλους, θα τους αποκρύψει μαζί με τους υποφακέλους τους μόνο στην Απλή Συλλογή, θα είναι ορατοί στις υπόλοιπες εφαρμογές.\n\nΑν θέλετε να τους αποκρύψετε και στις υπόλοιπες εφαρμογές, χρησιμοποιήστε την λειτουργία Απόκρυψη. + Διαγραφή όλων + Να αφαιρεθούν όλοι οι φάκελοι από την λίστα των εξαιρεμένων; Αυτό δεν θα διαγράψει τους φακέλους. + Κρυφοί φάκελοι + Διαχείριση κρυφών φακέλων + Φαίνεται πως δεν υπάρχουν κρυφοί φάκελοι με \".nomedia\" αρχείο. + + + Φάκελοι που συμπεριλαμβάνονται + Διαχείριση φακέλων που συμπεριλαμβάνονται + Προσθήκη φακέλου + Αν υπάρχουν κάποιοι φάκελοι που περιέχουν πολυμέσα, αλλά δεν αναγνωρίζονται από την εφαρμογή, μπορείτε να τους προσθέσετε χειροκίνητα εδώ.\n\nΗ προσθήκη στοιχείων εδώ, δεν θα εξαιρέσει κάποιον άλλο φάκελο. + Δεν βρέθηκαν αρχεία πολυμέσων. Μπορεί να λυθεί αυτό, προσθέτοντας τους φακέλους που περιέχουν αρχεία πολυμέσων με μη αυτόματο τρόπο. + + + Αλλαγή μεγέθους + Αλλαγή μεγέθους και αποθήκευση + Πλάτος + Ύψος + Διατήρηση αναλογίας + Παρακαλώ εισάγετε σωστή ανάλυση + + + Επεξεργαστής + Περιστροφή + Άκυρη διαδρομή εικόνας + Άκυρη διαδρομή βίντεο + Η επεξεργασία εικόνας απέτυχε + Η επεξεργασία βίντεο απέτυχε + Η επεξεργασία εικόνας ακυρώθηκε + Η επεξεργασία βίντεο ακυρώθηκε + Επιτυχής επεξεργασία αρχείου + Επιτυχής επεξεργασία εικόνας + Επιτυχής επεξεργασία βίντεο + Επεξεργασία εικόνας με: + Επεξεργασία βίντεο με: + Δεν βρέθηκε επεργαστής εικόνων + Δεν βρέθηκε επεργαστής βίντεο + Άγνωστη τοποθεσία αρχείου + Δεν ήταν δυνατή η αντικατάσταση του αρχείου πηγής + Περιστροφή αριστερά + Περιστροφή δεξιά + Περιστροφή κατά 180º + Περιστροφή + Οριζόντια περιστροφή + Κατακόρυφη περιστροφή + Προσαρμογή + Άλλο + + + Απλή Συλλογή + Εφαρμογή ως ταπετσαρία + Η εφαρμογή ως ταπετσαρία απέτυχε + Εφαρμογή ως ταπετσαρία με: + Η ταπετσαρία εφαρμόζεται... + Η ταπετσαρία εφαρμόστηκε + Αναλογία κατακόρυφης προβολής + Αναλογία οριζόντιας προβολής + Αρχική οθόνη + Οθόνη κλειδώματος + Αρχική και Οθόνη Κλειδώματος + + + Παρουσίαση διαφανιών + Χρόνος επανάληψης (δευτ.) : + Συμπερίληψη φωτογραφιών + Συμπερίληψη βίντεο + Συμπερίληψη GIFs6 + Τυχαία σειρά + Κίνηση προς τα πίσω + Επανάληψη εμφάνισης διαφανειών + Κινήσεις + Χωρίς + Εξασθένηση + Ολίσθηση + Τέλος εμφάνισης διαφανειών + Δεν βρέθηκαν πολυμέσα για την εμφάνιση διαφανειών + + + Ομαδοποίηση υποφακέλων + + + Ομαδοποίηση κατά + Χωρίς + Φάκελο + Τελευταία τροπ/ηση + Τελευταία τροπ/ηση (ημερήσια) + Τελευταία τροπ/ηση (μηνιαία) + Ημερομ. λήψης + Ημερομ. λήψης (ημερήσια) + Ημερομ. λήψης (μηνιαία) + Τύπο αρχείου + Επέκταση + Σημειώστε ότι ομαδοποίηση και ταξινόμηση είναι 2 ανεξάρτητα πεδία + + + Εμφάνιση φακέλου στο widget: + Εμφάνιση ονόματος φακέλου + + + Αυτόματη αναπαραγωγή βίντεο + Απομνημόνευση τελευταίας θέσης αναπαραγωγής βίντεο + Αλλαγή προβολής ονόματος αρχείων + Επανάληψη βίντεο + Εμφάνιση κινούμενων GIFs στα εικονίδια + Μέγιστη φωτεινότητα κατά την προβολή πλήρους οθόνης + Κόψιμο εικονιδίων σε τετράγωνα + Εμφάνιση διάρκειας βίντεο + Περιστροφή πλήρους οθόνης σύμφωνα με + Ρυθμίσεις συστήματος + Περιστροφή συσκευής + Αναλογία εικόνας + Μαύρο φόντο και μπάρα κατάστασης σε πλήρη οθόνη + Κύλιση εικονιδίων οριζόντια + Αυτόματη απόκρυψη στοιχείων συστήματος σε πλήρη οθόνη + Διαγραφή άδειων φακέλων, όταν διαγραφεί το περιεχόμενό τους + Να επιτρέπεται ο έλεγχος φωτεινότητας με κατακόρυφες κινήσεις + Να επιτρέπεται ο έλεγχος έντασης του βίντεο και φωτεινότητας με κατακόρυφες κινήσεις (gestures) + Εμφάνιση του αριθμού πολυμέσων στον φάκελο, στην κύρια οθόνη + Εμφάνιση λεπτομερειών στα πολυμέσα σε κατάσταση πλήρους οθόνης + Διαχείριση εκτεταμένων λεπτομερειών + Να επιτρέπεται μεγένθυση με ένα δάχτυλο σε πλήρη οθόνη + Να επιτρέπεται η άμεση αλλαγή των μέσων με το άγγιγμα στις άκρες της οθόνης + Επιτρέπει βαθύ ζουμ στις εικόνες + Απόκρυψη λεπτομερειών όταν η μπάρα κατάστασης είναι κρυμμένη + Εμφάνιση μερικών κουμπιών λειτουργιών στο κάτω μέρος της οθόνης + Εμφάνιση του κάδου ανακύκλωσης στην οθόνη φακέλων + Βαθιά μεγέθυνση εικόνων + Εμφάνιση εικόνων με την υψηλότερη δυνατή ποιότητα + Εμφάνιση του Κάδου ως τελευταίο στοιχείο στην κύρια οθόνη + Επιτρέψτε το κλείσιμο προβολής πλήρους οθόνης με χειρονομία προς τα κάτω + Επιτρέπει μεγέθυνση 1:1 με δύο διπλά χτυπήματα + Να ανοίγονται πάντα τα βίντεο σε ξεχωριστή οθόνη με νέες οριζόντιες χειρονομίες + Εμφάνιση μιας εγκοπής αν διατίθεται + Επιτρέπει την περιστροφή εικόνων με χειρονομίες + Προτεραιότητα φόρτωσης αρχείου + Ταχύτητα + Συμβιβασμός + Αποφυγή εμφάνισης μη έγκυρων αρχείων + Εμφάνιση τύπου αρχείου εικόνας + Επιτρέπει την μεγέθυνση βίντεο με διπλό πάτημα + Στυλ μικρογραφίας φακέλου + Στυλ μικρογραφίας αρχείου + Απόσταση μικρογραφιών + Εμφάνιση πλήθους αρχείων σε ξεχωριστή γραμμή + Εμφάνιση πλήθους αρχείων σε αγκύλες + Χωρίς εμφάνιση μέτρησης αρχείων + Περιορισμό μεγάλων τίτλων φακέλων σε 1 γραμμή + Τετράγωνο + Στρογγυλεμένες γωνίες + + + Εικονίδια + Πολυμέσα πλήρους οθόνης + Περισσότερες λεπτομέρειες + Λειτουργίες στο κάτω μέρος + + + Διαχείρηση εμφάνισης λειτουργιών + Εναλλαγή αγαπημένων + Εναλλαγή προβολής αρχείου + + + Προσαρμογή + Επαναφορά + Τετράγωνο + Μετατροπή + Φίλτρο + Χωρίς + Ρυθμίσεις + Σκιές + Έκθεση + Φωτισμένα + Φωτισμός + Αντίθεση + Κορεσμός + Σαφήνεια + Γάμμα + Μαύρα + Λευκά + Θερμοκρασία + Οξύτητα + Επαναφορά + Εστίαση + Χωρίς + Ακτίνα + Γραμμικά + Καθρέπτης + Θόλωση + Κείμενο + Επιλογή κειμένου + Χρώμα κειμένου + Γραμ/σειρά + Προσθήκη + Επεξεργασία + Ίσιωμα + Γραμ/σειρά + Χρώμα + BG Χρώμα + Ευθυγράμμιση + Σε Γραμ/σειρά + Διαγραφή + Το κείμενό μου + Βούρτσα + Χρώμα + Μέγεθος + Σκληρότητα + Σε Γραμ/σειρά + Διαγραφή + Χρώμα Βούρτσας + Επεξεργαστής + Κλείσιμο Επεξεργαστή? + Θέλετε πραγματικά να απορρίψετε τις αλλαγές; + Ναί + Όχι + Ακύρωση + Αποδοχή + Αποθήκευση + Εξαγωγή… + Εξαγωγή %s. + Αυτοκόλλητο + Χρώμα Αυτοκόλλητου + Επιλογές Αυτοκόλλητου + Προσθήκη + Χρώμα + Διαγραφή + Εμπρός + Ίσιωμα + Αντικατάσταση + Αδιαφάνεια + Αντίθεση + Κορεσμός + Φωτεινότητα + Μεταφορτώσεις + Επικάλειψη + Κανονικό + Σκούρο + Οθόνη + Overlay + Ξάνοιγμα + Πολλαπ/σμός + Κάψιμο χρώματος + Απαλό φως + Σκληρό φως + Χωρίς + Χρυσό + Φωτεινή διαρροή 1 + Μωσαϊκό + Χαρτί + Βροχή + Αντικέ + Αναστροφή Ο + Αναστροφή Κ + Αναίρεση + Επανάληψη + Επιλογέας χρώματος + Διαφάνεια + Λευκό + Γκρί + Μαύρο + Ανοιχτό μπλέ + Μπλέ + Μώβ + Ορχιδέα + Ρόζ + Κόκκινο + Πορτοκαλί + Χρυσό + Κίτρινο + Λαδί + Πράσινο + Γαλαζοπράσινο + Χρώμα πίνακα Pipet + Κόψιμο + + + Πώς μπορώ να κάνω την Απλή Συλλογή προεπιλεγμένη εφαρμογή συλλογής πολυμέσων; + Αρχικά πρέπει να βρείτε την τρέχουσα προεπιλεγμένη εφαρμογή γκάλερι στις Ρυθμίσεις (τμήμα Εφαρμογών) της συσκευής. Αναζητήστε για ένα κουμπί που αναφέρει κάτι σαν \"Άνοιγμα με προεπιλογή\", πατήστε το, μετά επιλέξτε \"Καθαρισμός προεπιλεγμένων\". + Την επόμενη φορά που θα προσπαθήσετε να ανοίξετε μία εικόνα ή ένα βίντεο θα πρέπει να δείτε έναν διάλογο επιλογής εφαρμογών, όπου μπορείτε να επιλέξετε Απλή Συλλογή να την κάνετε προεπιλεγμένη εφαρμογή. + Κλείδωσα την εφαρμογή με κωδικό, αλλά τον ξέχασα. Τι μπορώ να κάνω; + Υπάρχουν 2 λύσεις. Είτε επανεγκατάσταση της εφαρμογής, ή να εντοπίσετε την εφαρμογή στις ρυθμίσεις της συσκευής και να επιλέξετε \"Καθαρισμό δεδομένων\". Θα επαναφέρει όλες τις ρυθμίσεις και δεν θα σβήσει κάποιο αρχείο πολυμέσου. + Πώς μπορώ να κάνω ένα άλμπουμ να φαίνεται στην κορυφή; + Μπορείτε να πατήσετε παρατεταμένα στο άλμπουμ και να επιλέξετε το εικονίδιο καρφιτσώματος στο μενού, αυτό θα το καρφιτσώσει στην κορυφή. Επίσης μπορείτε να καρφιτσώσετε πολλαπλούς φακέλους, τα καρφιτσωμένα αντικείμενα θα είναι ταξινομημένα με την προεπιλεγμένη μέθοδο. + Πώς μπορώ να τρέξω μπροστά (fast forward) τα βίντεο; + Μπορείτε να το κάνετε πατώντας δύο φορές την πλευρά της οθόνης ή πατώντας το κείμενο τρέχοντος ή μέγιστης διάρκειας κοντά στη γραμμή αναζήτησης. Αν ενεργοποιήσετε το άνοιγμα βίντεο σε ξεχωριστή οθόνη στις ρυθμίσεις εφαρμογής, μπορείτε επίσης να χρησιμοποιήσετε και τις οριζόντιες κινήσεις. + Ποια είναι διαφορά μεταξύ απόκρυψης και εξαίρεσης ενός φακέλου; + Η εξαίρεση δεν επιτρέπει την εμφάνιση του φακέλου μόνο στην Απλή Συλλογή, ενώ η απόκρυψη λειτουργεί σε επίπεδο συστήματος και θα αποκρύψει τον φάκελο και από άλλες εφαρμογές γκάλερι. Λειτουργεί δημιουργώντας ένα άδειο \".nomedia\" αρχείο στον επιλεγμένο φάκελο, το οποίο μπορείτε να το διαγράψετε και με οποιονδήποτε διαχειριστή αρχείων. Σημειώστε ότι μερικές συσκευές δεν αποδέχονται την απόκρυψη φακέλων όπως Κάμερας, Στιγμιοτύπων, και Λήψεων. + Γιατί εμφανίζονται φάκελοι με εξώφυλλο μουσικής ή αυτόκολλητα; + Είναι πιθανόν να δείτε κάποια περίεργα άλμπουμ να εμφανίζονται. Μπορείτε να τα εξαιρέσετε εύκολα με παρατεταμένο άγγιγμα και επιλογή του Εξαίρεση. Στον επόμενο διάλογο μπορείτε να επιλέξετε επάνω φάκελο. Είναι πιθανό να μην επιτρέψει την εμφάνιση και άλλων σχετικών άλμπουμ. + Ένας φάκελος με εικόνες δεν εμφανίζεται. Τι μπορώ να κάνω; + Υπάρχουν διάφοροι λόγοι, αλλά είναι εύκολη η λύση. Επιλέξτε Ρυθμίσεις -> Διαχείριση Συμπεριβαλομένων Φακέλων, επιλέξτε Προσθήκη και πλοηγηθείτε στον ζητούμενο φάκελο. + Πώς μπορώ να επιλέξω μόνο κάποιους φακέλους να εμφανίζονται; + Η προσθήκη ενός φακέλου στους Συμπεριλαμβανόμενους Φακέλους δεν εξαιρεί αυτόματα τίποτα. Μπορείτε να πάτε στις Ρυμίσεις-> Διαχείριση Εξαιρεμένων Φακέλων, εξαιρέστε τον ριζικό φάκελο \"/\", έπειτα προσθέστε τους φακέλους στο Ρυθμίσεις -> Διαχείριση Συμπεριλαμβανομένων Φακέλων. + Αυτό θα κάνει ορατούς μόνο τους επιλεγμένους φακέλους, καθώς η εξαίρεση και η συμπερίληψη λειτουργούν αναδρομικά και αν ενας φάκελος ανήκει και στα δύο, θα εμφανιστεί. + Μπορώ να περικόψω εικόνες με την εφαρμογή; + Ναι, μπορείτε να περικόψετε εικόνες στον επεξεργαστή, σύροντας τις γωνίες εικόνας. Μπορείτε να μεταβείτε στον επεξεργαστή είτε πατώντας παρατεταμένα μια μικρογραφία εικόνας και επιλέγοντας Επεξεργασία, είτε επιλέγοντας Επεξεργασία από την προβολή πλήρους οθόνης. + Μπορώ να ομαδοποιήσω κάπως τις μικρογραφίες των αρχείων πολυμέσων? + Σίγουρα, απλά με την χρήση \"Ομαδοποίηση κατά\" Επέκταση ή Τύπο αρχείου. Μπορείτε την ομαδοποίηση με πολλά κριτήρια, συμπεριλαμβανομένης της ημερομηνίας λήψης. Εάν χρησιμοποιείτε τη λειτουργία \"Εμφάνιση όλων των περιεχομένων στο φάκελο\" μπορείτε να ομαδοποιήσετε και τους φακέλους επίσης. + Η ταξινόμηση κατά ημερομηνία δεν φαίνεται να λειτουργεί σωστά, πώς μπορώ να τη διορθώσω? + Αυτό πιθανότατα προκαλείται από την αντιγραφή των αρχείων από κάπου. Μπορείτε να το διορθώσετε επιλέγοντας τις μικρογραφίες αρχείων και επιλέγοντας \"Επιδιόρθωση ημερ. λήψης\". + Βλέπω κάποια χρωματική ζώνη στις εικόνες. Πώς μπορώ να βελτιώσω την ποιότητα? + Η τρέχουσα λύση για την εμφάνιση εικόνων λειτουργεί πολύ καλά στην πλειονότητα των περιπτώσεων, αλλά εάν θέλετε ακόμα καλύτερη ποιότητα εικόνας, μπορείτε να ενεργοποιήσετε την \"Εμφάνιση εικόνων με την υψηλότερη δυνατή ποιότητα\" στις ρυθμίσεις της εφαρμογής, στο πεδίο \"Βαθιά μεγέθυνση εικόνων\". + Έχω αποκρύψει ένα αρχείο/φάκελο. Πώς μπορώ να το επανεμφανίσω? + Μπορείτε είτε να επιλέξετε στο μενού \"Εμφάνιση προσωρινά κρυφών στοιχείων\" στην κύρια οθόνη, είτε να αλλάξετε σε \"Εμφάνιση κρυφών στοιχείων\" στις ρυθμίσεις της εφαρμογής για να δείτε το κρυφό στοιχείο. Αν θέλετε να το αποκρύψετε, πατήστε παρατεταμένα και επιλέξτε \"Απόκρυψη\". Οι φάκελοι αποκρύπτονται προσθέτοντας ένα κρυφό αρχείο \".nomedia\" , μπορείτε επίσης να διαγράψετε το αρχείο με οποιονδήποτε διαχειριστή αρχείων. + Γιατί η εφαρμογή καταλαμβάνει τόσο μεγάλο χώρο; + Η προσωρινή μνήμη της εφαρμογής μπορεί να δεσμεύσει έως και 250MB, διασφαλίζοντας την ταχύτερη προφόρτωση των εικόνων. Αν η εφαρμογή χρησιμοποιεί ακόμα περισσότερο χώρο, πιθανότατα οφείλεται στην κατοχή αντικειμένων στον Κάδο. Αυτά τα αρχεία υπολογίζονται στο μέγεθος της εφαρμογής. Μπορείτε να αδειάσετε τον Κάδο Ανακύκλωσης ανοίγοντάς τον και διαγράφοντας όλα τα αρχεία ή από τις ρυθμίσεις της εφαρμογής. Κάθε αρχείο στον Κάδο διαγράφεται αυτόματα μετά από 30 ημέρες. + + + + Απλή Συλλογή Pro - Διαχείριση & Επεξεργασία + + Μια premium εφαρμογή διαχείρισης/επεξεργασίας φωτο βίντεο GIF χωρίς διαφημήσεις + + Η Απλή Συλλογή Pro είναι χωρίς σύνδεση και εξαιρετικά προσαρμόσιμη. Οργανώστε και επεξεργαστείτε τις φωτογραφίες σας, ανακτήσετε διαγραμμένα αρχεία απο τον κάδο ανακύκλωσης, προστατεύσετε και αποκρύψτε αρχεία, προβάλετε πλήθος διαφορετικών φωτογραφιών και μορφών βίντεο, συμπεριλαμβανομένων των RAW, SVG και πολλών άλλων. + + Η εφαρμογή δεν περιέχει διαφημίσεις και περιττά δικαιώματα. Εφόσον δεν απαιτεί πρόσβαση στο διαδίκτυο, έτσι προστατεύεται το απόρρητό σας. + + ------------------------------------------------- + ΧΑΡΑΚΤΗΡΙΣΤΙΚΑ-ΑΠΛΗΣ ΣΥΛΛΟΓΗΣ PRO + ------------------------------------------------- + + • Χωρίς σύνδεση διαφημίσεις ή αναδυόμενα παράθυρα + • Επεξεργαστής Απλής Συλλογής – κόψιμο, περιστροφή, αλλαγή μεγέθους, σχεδίαση, φίλτρα και άλλα + • Δεν απαιτείται πρόσβαση στο διαδίκτυο, παρέχοντας μεγαλύτερη προστασία της ιδιωτικής ζωής και ασφάλειας + • Δεν απαιτούνται περιττά δικαιώματα + • Γρήγορη αναζήτηση εικόνων, βίντεο και αρχείων + • Άνοιγμα και προβολή πολλών διαφορετικών τύπων φωτογραφιών και βίντεο (RAW, SVG, πανοραμικών κλπ) + • Μια ποικιλία διαισθητικών χειρονομιών για εύκολη επεξεργασία και οργάνωση αρχείων + • Πολλοί τρόποι για φιλτράρισμα, ομαδοποίησης και ταξινόμησης αρχείων + • Προσαρμογή εμφάνισης της Απλής Συλλογής Pro + • Διατίθεται σε 32 γλώσσες + • Σημειώστε αρχεία ως αγαπημένα για γρήγορη πρόσβαση + • Προστατέψτε τις φωτογραφίες σας και βίντεο με μοτίβο, κωδικό ή δακτυλικό αποτύπωμα + • Χρησιμοποιήστε κωδικό, μοτίβο ή δακτυλικό αποτύπωμα για προστασία έναρξης της εφαρμογής ή συγκεκριμένων λειτουργιών + • Επαναφορά διαγραμμένων φωτογραφιών και βίντεο από τον κάδο ανακύκλωσης + • Εναλλαγή προβολής αρχείων για απόκρυψη φωτογραφιών και Βίντεο + • Δημιουργήστε μια προσαρμόσιμη παρουσίαση των αρχείων σας + • Δείτε λεπτομερείς πληροφορίες των αρχείων σας (ανάλυση, τιμές EXIF κλπ.) + • Η Απλή Συλλογή Pro είναι ανοικτού κώδικα + … και πάρα πολλά ακόμα! + + ΕΞΕΡΓΑΣΤΗΣ ΑΠΛΗΣ ΣΥΛΛΟΓΗΣ + Η Απλή Συλλογή Pro σας διευκολύνει να επεξεργαστείτε τις φωτογραφίες σας άμεσα. Περικοπή, αναστροφή, περιστροφή και αλλαγή μεγέθους των εικόνων σας. Εάν αισθάνεστε λίγο πιο δημιουργικοί, μπορείτε να προσθέσετε φίλτρα και σχεδίαση στις φωτογραφίες σας! + + ΥΠΟΣΤΗΡΙΞΗ ΠΟΛΛΩΝ ΤΥΠΩΝ ΑΡΧΕΙΩΝ + Σε αντίθεση με κάποιες άλλες εφαρμογές η Απλή Συλλογή Pro υποστηρίζει ένα τεράστιο φάσμα διαφορετικών τύπων αρχείων, όπως JPEG, PNG, MP4, MKV, RAW, SVG, Πανοραμικές φωτογραφίες, βίντεο πανοραμικών και πολλά άλλα. + + ΠΟΛΥ ΠΡΟΣΑΡΜΟΣΙΜΟΣ ΔΙΑΧΕΙΡΙΣΤΗΣ + Από το UI στα κουμπιά λειτουργιών στην κάτω γραμμή εργαλείων, η Απλή Συλλογή Pro είναι ιδιαίτερα προσαρμόσιμη και λειτουργεί όπως εσείς θέλετε. Καμιά άλλη εφαρμογή δεν έχει τέτοια ευελιξία! Χάρη στον ανοιχτό κώδικα, είναι επίσης διαθέσιμη σε 32 γλώσσες! + + ΕΠΑΝΑΦΟΡΑ ΔΙΑΓΡΑΜΜΕΝΩΝ ΦΩΤΟ ΚΑΙ ΒΙΝΤΕΟ + Διαγράψατε τυχαία μια πολύτιμη φωτογραφία ή βίντεο; Μην ανησυχείτε! Η Απλή Συλλογή Pro διαθέτει έναν εύχρηστο κάδο ανακύκλωσης όπου μπορείτε να ανακτήσετε τις διαγραμμένες φωτογραφίες και βίντεο πανεύκολα. + + ΠΡΟΣΤΑΣΙΑ ΚΑΙ ΑΠΟΚΡΥΨΗ ΑΡΧΕΙΩΝ ΦΩΤΟ ΚΑΙ ΒΙΝΤΕΟ + Χρησιμοποιώντας κωδικό, μοτίβο ή τον σαρωτή δακτυλικών αποτυπωμάτων της συσκευής σας, μπορείτε να προστατεύσετε και να αποκρύψετε φωτογραφίες, βίντεο ή ολόκληρα άλμπουμ. Μπορείτε να προστατεύσετε την ίδια την εφαρμογή ή να κλειδώσετε συγκεκριμένες λειτουργίες της. Για παράδειγμα, δεν μπορείτε να διαγράψετε ένα αρχείο χωρίς χρήση των δακτυλικών αποτυπωμάτων, συμβάλλοντας στην προστασία των αρχείων σας από τυχαία διαγραφή. + + Δείτε την πλήρη σειρά των Απλών Εργαλείων εδώ: + https://www.simplemobiletools.com + + Αποκλειστική ιστοσελίδα της Απλή Συλλογή Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index feea9961a..764dbcf7c 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1,45 +1,69 @@ Simple Gallery - Gallery + Galería Editar Abrir cámara - Abrir con… - No se encontró una aplicación válida - (oculto) + (Oculto) + (Excluido) Fijar carpeta No fijar carpeta + Anclar arriba Mostrar el contenido de todas las carpetas Todos los medios Cambiar a vista de carpeta Otra carpeta Mostrar en el mapa Ubicación desconocida - No se encontró aplicación de mapas - No se encontró aplicación de cámara - Aumentar el número de columnas - Reducir el número de columnas - Mostrar ocultos temporalmente - Change cover image - Select photo - Use default + Volumen + Brillo + Bloquear orientación + Desbloquear orientación + Cambiar orientación + Forzar retrato + Forzar paisaje + Usar la orientación por defecto + Fijar fecha de toma + Fijando… + Fecha fijada correctamente + No se han encontrado valores de fechas tomadas + Comparte una versión redimensionada + Oye,\n\nParece que actualizaste de la aplicación gratuita vieja. Ahora puedes desinstalar la versión vieja, que tiene un botón \'Actualizar a Pro\' en la parte superior de los ajustes de la app.\n\nSólo tendrás los elementos de la Papelera de Reciclaje eliminados, favoritos desmarcados y también tendrás que reiniciar los ajustes de la app.\n\n¡Gracias! + Cambiar a la búsqueda de archivos en todas las carpetas visibles + Poner como carpeta predeterminada + Quitar como carpeta predeterminada + + + Filtro de medios + Imágenes + Videos + GIFs + Imágenes RAW + SVGs + Retratos + No se han encontrado ficheros con los filtros seleccionados. + Cambiar filtros - Esta función oculta la carpeta agregando un archivo \'.nomedia\' en ella, y ocultará también las subcarpetas. Puede mostrarlas cambiando la opción \'Mostrar carpetas ocultas\' en los Ajustes. ¿Continuar? + Esta función oculta la carpeta agregando un archivo \'.nomedia\' en ella, y también ocultará las subcarpetas. Puede mostrarlas modificando la opción \'Mostrar carpetas ocultas\' en los Ajustes. ¿Continuar? Excluir Carpetas excluidas Gestionar carpetas excluidas Esto excluirá la selección junto con sus subcarpetas, solamente de Simple Gallery. Puede gestionar las carpetas excluidas en los Ajustes. ¿Excluir mejor la carpeta superior? - Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\\n\\nIf you want to hide them from other apps too, use the Hide function. + Excluir las carpetas las excluirá junto a sus subcarpetas sólo en Simple Gallery, estas seguirán siendo visibles en otras aplicaciones.\n\nSi desea ocultarlo de otras aplicaciones, utilice la función de \"Ocultar\". Eliminar todo ¿Eliminar todas las carpetas de la lista de excluidas? Esto no borrará las carpetas. + Carpetas ocultas + Gestionar carpetas ocultas + Parece que no tienes carpetas ocultas con archivos \".nomedia\". Carpetas incluidas Gestionar carpetas incluidas Agregar carpeta Si tiene algunas carpetas que contengan multimedia, pero que no fueron reconocidas por la aplicación, puede agregarlas manualmente aquí. + Ningún archivo multimedia ha sido encontrado. Puedes resolverlo añadiendo las carpetas que contengan archivos multimedia manualmente. Redimensionar @@ -51,13 +75,20 @@ Editor - Guardar Rotar - Ruta - Ruta de imagen no válida - Falló la edición de imagen - Editar imagen usando: - No se encontró editor de imágenes + Ruta de imagen inválida + Ruta de video inválida + Edición de imagen fallida + Edición de video fallida + Edición de imagen cancelada + Edición de video cancelada + Archivo editado con éxito + Imagen editada con éxito + Video editado con éxito + Editar imagen con: + Editar video con: + No se encontró editor de imágenes + No se encontró editor de video Ubicación del archivo desconocida No se ha podido sobrescribir el archivo de origen Rotar a la izquierda @@ -66,51 +97,327 @@ Girar Horizontalmente Verticalmente - Error: sin memoria + Libre + Otro Fondos de pantalla Simple Gallery Establecer como fondo de pantalla Error al establecer fondo de pantalla Establecer como fondo de pantalla con: - No se encontró aplicación para ello - Estableciendo fondo de pantalla… + Estableciendo fondo de pantalla... Fondo de pantalla establecido correctamente Relación de aspecto tipo retrato Relación de aspecto tipo paisaje + Pantalla de inicio + Pantalla de bloqueo + Pantalla de inicio y de bloqueo + + + Presentación de diapositivas + Intervalo (segundos): + Incluir fotos + Incluir videos + Incluir GIFs + Orden aleatorio + Mover hacia atrás + Presentación en bucle + Animación + Ninguna + Desvanecimiento + Desplazamineto + La presentación de diapositivas terminó + No se han encontrado medios para la presentación de diapositivas + + + Agrupar subcarpetas directas + + + Agrupar por + No agrupar + Carpeta + Fecha de modificación + Fecha de modificacion (Día) + Fecha de modificación (Mes) + Fecha de creación + Fecha de creación (Día) + Fecha de creación (Mes) + Tipo de arhivo + Extensión + Tenga en cuenta que la agrupación y la clasificación son 2 campos independientes + + + Carpeta mostrada en el widget: + Mostrar nombre de carpeta - Mostrar carpetas ocultas Reproducir vídeos automáticamente + Recordar la última posición en la reproducción de video Cambiar la visibilidad del nombre de archivo - Mostrar multimedia - Solo imágenes - Solo vídeos - Gifs only - Images, videos, gifs - Imágenes y vídeos - Reproducción continua de vídeos - Animar las miniaturas de gifs + Reproducir videos en bucle + Animar las miniaturas de GIFs Brillo máximo cuando se muestra multimedia Recortar miniaturas en cuadrados + Mostrar la duración de los videos Rotar multimedia en pantalla completa según Configuración del sistema Rotación del dispositivo Relación de aspecto - Utilizar siempre fondo oscuro en pantalla completa - Scroll thumbnails horizontally + Fondo y barra de estado negra en medios de pantalla completa + Desplazar miniaturas horizontalmente + Ocultar la interfaz del sistema en pantalla completa + Eliminar carpetas vacias después de borrar su contenido + Permite controlar el brillo en las fotos con gestos verticales + Permite controlar el volumen y el brillo del video con gestos verticales + Mostrar el número de archivos dentro de las carpetas en la página principal + Mostrar información de los archivos en pantalla completa + Información de los archivos a mostrar + Permitir zoom con un dedo en pantalla completa + Desplazarse en los medios haciendo clic en los lados de la pantalla + Permitir zoom profundo + Ocultar detalles ampliados cuando la barra de estado está oculta + Mostrar botones de acción en la parte inferior de la pantalla + Mostrar la papelera de reciclaje en la pantalla de carpetas + Imágenes ampliables con zoom + Mostrar imágenes con la mayor calidad posible + Mostrar la papelera de reciclaje como el último elemento en la pantalla principal + Permite cerrar la vista de pantalla completa con un gesto hacia abajo. + Permitir zoom 1:1 con un toque doble + Abrir videos en una pantalla separada con nuevos gestos horizontales + Mostrar una muesca si está disponible + Permite girar con gestos + Prioridad de carga de archivos + Velocidad + Compromiso + Evita mostrar archivos inválidos + Mostrar tipos de archivo de imagen + Permitir hacer zoom en videos con doble toque + Estilo de las miniaturas de las carpetas + File thumbnail style + Thumbnail spacing + Mostrar el recuento de archivos en una línea separada + Mostrar el recuento de archivos entre paréntesis + No mostrar el recuento de archivos + Limitar los títulos largos de las carpetas a 1 línea + Cuadrado + Esquinas redondeadas + + + Miniaturas + Medios a pantalla completa + Detalles ampliados + Acciones en la parte inferior + + + Administrar los botones de la parte inferior + Agregar a favoritos + Alternar visibilidad de archivo + + + Personalizado + Reiniciar + Cuadrado + Recortar + Filtros + Ninguno + Ajustes + Sombras + Exposición + Luces + Brillo + Contraste + Saturación + Claridad + Colores + Negros + Blancos + Temperatura + Nitidez + Reiniciar + Enfoque + Ninguno + Radial + Lineal + Reflejado + Gaussiano + Texto + Opciones de texto + Color de texto + Fuente + Añadir + Editar + Enderezar + Fuente + Color + Color de fondo + Alineación + Traer al frente + Eliminar + Tu texto + Pincel + Color + Tamaño + Dureza + Traer al frente + Eliminar + Color del pincel + Editor + ¿Cerrar editor? + ¿Realmente quieres descartar los cambios? + + No + Cancelar + Aceptar + Guardar + Exportando… + Exportando %s. + Sticker + Color de Sticker + Opciones de Sticker + Añadir + Color + Eliminar + Al Frente + Enderezar + Remplazar + Opacidad + Contraste + Saturación + Brillo + Subidas + Modo de fusión + Normal + Más oscuro + Pantalla + Superpuesto + Más claro + Multiplicar + Quemadura de color + Luz suave + Luz fuerte + Ninguno + Dorado + Falta de luz 1 + Mosaico + Papel + Lluvia + Vintage + Girar Hor + Girar Vert + Deshacer + Rehacer + Cuentagotas + Transparente + Blanco + Gris + Negro + Azul claro + Azul + Púrpura + Orquídea + Rosa + Rojo + Naranja + Dorado + Amarillo + Oliva + Verde + Aguamarina + Color pipetable + Recortar + + + ¿Cómo puedo hacer que Simple Gallery sea la galería de dispositivos predeterminada? + Primero tiene que encontrar la galería predeterminada actualmente en la sección Aplicaciones de la configuración de su dispositivo, busque un botón que diga algo como \"Abrir por defecto \", haga clic en él, luego seleccione \"Borrar valores predeterminados \". + La próxima vez que intente abrir una imagen o video, debería ver un selector de aplicaciones, donde puede seleccionar Galería Simple y convertirla en la aplicación predeterminada. + He protegido la aplicación con una contraseña, pero la he olvidado. ¿Que puedo hacer? + Puede resolverlo de 2 maneras. Puede reinstalar la aplicación o encontrar la aplicación en la configuración de su dispositivo y seleccionar \"Borrar datos \". Restablecerá todas sus configuraciones, no eliminará ningún archivo multimedia. + ¿Cómo puedo hacer que un álbum siempre aparezca en la parte superior? + Puede aguantar pulsado el álbum deseado y seleccionar el ícono Pin en el menú de acción, que lo fijará en la parte superior. También puede anclar varias carpetas, los artículos fijados se ordenarán por el método de clasificación predeterminado. + ¿Cómo puedo avanzar videos? + Puede hacerlo tocando dos veces el costado de la pantalla o tocando los textos de duración actual o máxima cerca de la barra de búsqueda. Si habilita la apertura de videos en una pantalla separada en la configuración de la aplicación, también puede usar gestos horizontales. + ¿Cuál es la diferencia entre ocultar y excluir una carpeta? + Excluir evita mostrar la carpeta solo en Simple Gallery, mientras que Ocultar funciona en el sistema y oculta la carpeta de otras galerías también. Funciona al crear un archivo \".nomedia \" vacío en la carpeta determinada, que luego puede eliminar también con cualquier administrador de archivos. Tenga en cuenta que algunos dispositivos no permiten ocultar carpetas como Cámara, Capturas de pantalla y Descargas. + ¿Por qué aparecen las carpetas con la portada de la música o las pegatinas? + Puede suceder que veas aparecer algunos álbumes inusuales. Puede excluirlos fácilmente presionándolos durante mucho tiempo y seleccionando Excluir. En el siguiente cuadro de diálogo, puede seleccionar la carpeta principal, lo más probable es que evite que aparezcan otros álbumes relacionados. + Una carpeta con imágenes no aparece, ¿qué puedo hacer? + Eso puede tener múltiples razones, pero resolverlo es fácil. Simplemente vaya a Configuración -> Administrar carpetas incluidas, seleccione Más y vaya a la carpeta requerida. + ¿Qué pasa si quiero solo algunas carpetas concretas visibles? + Agregar una carpeta en las carpetas incluidas no excluye automáticamente nada. Lo que puede hacer es ir a Ajustes -> Administrar carpetas excluidas, excluir la carpeta raíz \"/\", luego agregar las carpetas deseadas en Configuración -> Administrar carpetas incluidas. + Esto hará que solo las carpetas seleccionadas sean visibles, ya que tanto la exclusión como la inclusión son recursivas y si una carpeta está excluida e incluida, aparecerá. + ¿Puedo recortar imágenes con esta aplicación? + Sí, puede recortar imágenes en el editor arrastrando las esquinas de la imagen. Puede acceder al editor pulsando prolongadamente una imagen en miniatura y seleccionando Editar, o seleccionando Editar en la vista de pantalla completa. + ¿Puedo de alguna manera agrupar miniaturas de archivos multimedia? + Claro, solo use el elemento de menú \"Agrupar por \" mientras esté en la vista de miniaturas. Puede agrupar archivos según varios criterios, incluida la Fecha de toma. Si usa la función \"Mostrar todo el contenido de las carpetas\" también puede agruparlas por carpetas. + La ordenación por fecha tomada no parece funcionar correctamente, ¿cómo puedo solucionarlo? + Lo más probable es que sea causado por los archivos que se copian de algún lugar. Puede solucionarlo seleccionando las miniaturas de archivo y seleccionando \"Fijar fecha de toma\". + Veo algunas bandas de color en las imágenes. ¿Cómo puedo mejorar la calidad? + La solución actual para mostrar imágenes funciona bien en la gran mayoría de los casos, pero si desea una calidad de imagen aún mejor, puede habilitar \"Mostrar imágenes con la calidad más alta posible\" en la configuración de la aplicación, en la opción \"Zoom profundo" imágenes\". + He escondido un archivo / carpeta. ¿Cómo puedo mostrarlo? + Puede presionar el elemento de menú \"Mostrar temporalmente elementos ocultos\" en la pantalla principal o alternar \"Mostrar elementos ocultos\" en la configuración de la aplicación para ver el elemento oculto. Si desea mostrarlo, solo manténgalo presionado y seleccione \"Mostrar\". Las carpetas se ocultan agregando un archivo oculto \".Nomedia\" en ellas, también puede eliminar el archivo con cualquier administrador de archivos. + ¿Por qué la aplicación ocupa tanto espacio? + El caché de aplicaciones puede ocupar hasta 250 MB, asegura una carga de imagen más rápida. Si la aplicación ocupa aún más espacio, lo más probable es que tenga elementos en la Papelera de reciclaje. Esos archivos cuentan para el tamaño de la aplicación. Puede borrar la Papelera de reciclaje abriéndola y eliminando todos los archivos, o desde la configuración de la aplicación. Todos los archivos en el contenedor se eliminan automáticamente después de 30 días. + + Simple Gallery Pro - Gestor y editor de fotos - Una galería para ver fotos y vídeos sin publicidad. + Galería rápida y segura para gestión profesional de fotos, vídeos y los GIFs - Una herramienta sencilla que se puede usar para ver fotos y vídeos. Los elementos se pueden ordenar por fecha, tamaño, nombre tanto ascendente como descendente; se puede hacer zoom en las fotos. Los archivos de medios se muestran en múltiples columnas dependiendo del tamaño de la pantalla, y se puede cambiar el número de columnas mediante gestos. Permite renombrar, compartir, borrar, mover. Las imágenes también se pueden recortar, rotar o usarse como fondo de pantalla directamente desde la aplicación. + Simple Gallery Pro es una galería altamente personalizable disponible sin conexión. Organiza y edita tus fotos, recupera archivos eliminados con la papelera de reciclaje, protege y oculta archivos y mira una gran variedad de diferentes formatos de fotos y videos incluyendo RAW, SVG y muchos más. - Gallery también se ofrece para uso de terceros para previsualizar imágenes/vídeos, agregar adjuntos en clientes de correo, etc. Es perfecta para uso diario. + La app no contiene anuncios ni permisos innecesarios. Ya que la app tampoco requiere acceso a internet, tu privacidad está protegida. - No contiene publicidad ni permisos innecesarios. Es totalmente libre, proporciona colores personalizables. + ------------------------------------------------- + SIMPLE GALLERY PRO – CARACTERÍSTICAS + ------------------------------------------------- - Esta aplicación es solamente una pieza de una serie más grande de aplicaciones. Puede encontrar el resto en http://www.simplemobiletools.com + • Galería disponible sin conexión sin anuncios ni ventanas emergentes + • Editor de fotos – Recorta, rota, redimensiona, dibuja, filtra y más + • Acceso a internet no necesario, dándote más privacidad y seguridad + • No requiere permisos innecesarios + • Búsqueda rápida de imágenes, videos y archivos + • Abre y mira muchos tipos de fotos y videos diferentes (RAW, SVG, panorámica, etc.) + • Variedad de gestos intuitivos para editar y organizar archivos fácilmente + • Montones de formas de filtrar, agrupar y ordenar archivos + • Personaliza la apariencia de Simple Gallery Pro + • Disponible en 32 lenguajes + • Seleccionar archivos como favoritos para acceder rápidamente + • Protege tus fotos y videos con patrón, pin o huella digital + • Usa pin, patrón y huella digital para proteger la apertura de la app o también funciones específicas + • Recupera fotos y videos eliminados desde la papelera de reciclaje + • Alterna la visibilidad de archivos para ocultar fotos y videos + • Crea una presentación de diapositivas personalizable de tus archivos + • Mira información detallada de tus archivos (Resolución, valores EXIF, etc.) + • Simple Gallery Pro es de código abierto + … ¡y mucho, mucho más! + + EDITOR DE FOTOS + Simple Gallery Pro hace más fácil editar tus fotos rápidamente. Corta, gira, rota y redimensiona tus fotos. Si te sientes un poco más creativo, ¡Puedes añadir filtros y dibujar en tus fotos! + + SOPORTA MUCHOS TIPOS DE ARCHIVOS + Al contrario de otras galerías y organizadores de fotos, Simple Gallery Pro soporta un enorme rango de tipos de archivos diferentes incluyendo JPEG, PNG, MP4, MKV, RAW, SVG, fotos panorámicas, videos panorámicos y muchos más. + + GALERÍA ALTAMENTE PERSONALZIABLE + Desde la IU hasta los botones de funciones en la barra de herramientas inferior, Simple Gallery Pro es altamente personalizable y trabaja de la forma que quieras. ¡Ninguna otra galería tiene este tipo de flexibilidad! Gracias a que es de código abierto, ¡También está disponible en 32 lenguajes! + + RECUPERA FOTOS Y VIDEOS ELIMINADOS + ¿Accidentalmente eliminaste una foto o video precioso? ¡No te preocupes! Simple Gallery Pro contiene una práctica papelera de reciclaje donde puedes recuperar fácilmente fotos y videos eliminados. + + PROTEGE Y OCULTA FOTOS, VIDEOS Y ARCHIVOS + Usando pin, patrón o el lector de huella de tu dispositivo, puedes proteger y ocultar fotos, videos y álbumes enteros. Puedes proteger la propia aplicación o bloquear funciones específicas en la misma. Por ejemplo, no puedes eliminar un archivo sin un escaneo de huella, ayudando a proteger tus archivos de ser eliminados accidentalmente. + + Mira la suite completa de Simple Tools aquí: + https://www.simplemobiletools.com + + Sitio web de Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + Iragazi multimedia + Irudiak + Bideoak + GIFak + RAW irudiak + SVGak + Erretratuak + Hautatutako filtroa duen multimedia fitxategirik ez da aurkitu. + Aldatu filtroak + + + Funtzio honek karpeta \'.nomedia\' fitxategi bat gehituz ezkutatzen du, azpikarpeta guztiak ere ezkutatuko ditu. Ezarpenetako \'Erakutsi ezkutatutako elementuak\' aukera aldatuz ikus ditzakezu. Jarraitu? + Baztertu + Baztertutako karpetak + Kudeatu baztertutako karpetak + Honek hautatutakoa bere azpikarpetekin baztertutako ditu Galeria Sinpletik soilik. Baztertutako karpetak ezarpenetan kudeatu ditzakezu. + Baztertu nagusia horren ordez? + Karpetak baztertzeak hauek eta beren azpikarpetak Galeria Sinplean soilik ezkutatuko ditu, beste aplikazioetan oraindik ikusgai egongo dira.\n\nBeste aplikazioetatik ere ezkutatu nahi badituzu, erabili Ezkutatu funtzioa. + Kendu guztiak + Kendu karpeta guztiak baztertutako zerrendatik? Honek ez ditu karpetak ezabatuko. + Ezkutatutako karpetak + Kudeatu ezkutatutako karpetak + Badirudi \".nomedia\" fitxategia duen ezkutatutako karpetarik ez duzula. + + + Sartutako karpetak + Kudeatu sartutako karpetak + Gehitu karpeta + Multimedia duen karpetarik baduzu, baina aplikazioak ez badu hauteman, hemen eskuz gehitu dezakezu.\n\nHainbat elementu hemen gehitzeak ez du beste karpetarik baztertuko. + Multimedia fitxategirik ez da aurkitu. Fitxategi horiek dituzten karpetak eskuz gehituz konpondu dezakezu. + + + Aldatu tamaina + Aldatu hautatutakoaren tamaina eta gorde + Zabalera + Altuera + Mantendu aspektu-erlazioa + Sartu baliozko bereizmena + + + Editorea + Biratu + Irudiaren bide-izen baliogabea + Bideoaren bide-izen baliogabea + Irudia editatzeak huts egin du + Bideoa editatzeak huts egin du + Irudia editatzea utzi da + Bideoa editatzea utzi da + Fitxategia ongi editatu da + Irudia ongi editatu da + Bideoa ongi editatu da + Editatu irudia honekin: + Editatu bideoa honekin: + Irudi-editorerik ez da aurkitu + Bideo-editorerik ez da aurkitu + Fitxategi kokapen ezezaguna + Ezin izan da jatorrizko fitxategia gainidatzi + Biratu ezkerrera + Biratu eskuinera + Biratu 180º + Irauli + Irauli horizontalki + Irauli bertikalki + Askea + Bestelakoa + + + Horma-paper sinplea + Ezarri horma-paper gisa + Horma-paper gisa ezartzeak huts egin du + Ezarri horma-paper gis honekin: + Horma-papera ezartzen… + Horma-papera ongi ezarri da + Aspektu-erlazio bertikala + Aspektu-erlazio horizontala + Hasierako pantaila + Blokeatutako pantaila + Hasierako eta blokeatutako pantaila + + + Diaporama + Tartea (segunduak): + Sartu argazkiak + Sartu bideoak + Sartu GIFak + Ausazko ordena + Mugitu atzera + Errepikatu diaporama + Animazioa + Bat ere ez + Lausotu + Diapositiba + Diaporama bukatu da + Diaporamarako multimediarik ez da aurkitu + + + Taldekatu zuzeneko azpikarpetak + + + Taldekatu + Ez taldekatu fitxategiak + Karpeta + Azken aldaketa + Azken aldaketa (egunero) + Azken aldaketa (hilero) + Ateratze-data + Ateratze-data (egunero) + Ateratze-data (hilero) + Fitxategi mota + Luzapena + Kontuan hartu taldekatzea eta ordenatzea bi eremu independente direla. + + + Widgetean erakutsitako karpeta: + Erakutsi karpetaren izena + + + Erreproduzitu bideoak automatikoki + Gogoratu azken bideoaren erreprodukzio-posizioa + Aldatu fitxategi-izenaren ikusgaitasuna + Errepikatu bideoak + Animatu GIFak iruditxoetan + Gehienezko distira multimedia pantaila osoan ikustean + Moztu iruditxoak karratutan + Erakutsi bideoen iraupena + Biratu pantaila osoko multimedia: + Sistemaren ezarpena + Gailu-biratzea + Aspektu-erlazioa + Hondo beltza pantaila osoko multimediarekin + Korritu iruditxoak horizontalki + Automatikoki ezkutatu sistemaren EI pantaila osoko multimediarekin + Ezabatu hutsik dauden karpetak beren edukia ezabatu ondoren + Baimendu argazkiaren distira kontrolatzea keinu bertikalekin + Baimendu bideoaren bolumena eta distira kontrolatzea keinu bertikalekin + Erakutsi karpetaren multimedia-kopurua ikuspegi nagusian + Erakutsi xehetasun hedatuak pantaila osoko multimedian + Kudeatu xehetasun hedatuak + Baimendu zooma hatz bakarrarekin pantaila osoko multimedian + Baimendu multimedia berehala aldatzea pantaila-ertzeetan klik eginez + Baimendu zoom sakona irudietan + Ezkutatu xehetasun hedatuak egoera-barra ezkutatuta dagoenean + Erakutsi ekintza-botoiak pantailaren beheko aldean + Erakutsi zakarrontzia karpeten pantailan + Zoom sakona egin daitezkeen irudiak + Erakutsi irudiak ahal den kalitate handienean + Erakutsi zakarrontzia azken elementu gisa pantaila nagusian + Baimendu pantaila osoko ikuspegia ixtea beherako keinuarekin + Baimendu 1:1 zooma bi aldiz birritan ukitzean + Ireki bideoak beste pantaila betean keinu horizontalekin + Erakutsi koska bat erabilgarri badago + Baimendu irudiak keinuekin biratzea + Fitxategien kargatze-lehentasuna + Abiadura + Konpromisoa + Ekidin fitxategi baliogabeak erakustea + Erakutsi irudi-fitxategi motak + Baimendu bideoen zooma birritan ukitzean + Karpeta-iruditxoaren estiloa + Fitxategi-iruditxoaren estiloa + Iruditxoen arteko tartea + Erakutsi fitxategi kopurua beste lerro batean + Erakutsi fitxategi kopurua parentesi artean + Ez erakutsi fitxategi kopurua + Mugatu karpeten izenburu luzeak lerro batera + Karratua + Ertz biribilduak + + + Iruditxoak + Pantaila osoko multimedia + Xehetasun hedatuak + Beheko ekintzak + + + Kudeatu ikusgai dauden beheko ekintzak + Aldatu gogokoa + Aldatu fitxategien ikusgaitasuna + + + Pertsonalizatu + Berrezarri + Karratua + Eraldatu + Filtroa + Bat ere ez + Doitu + Itzalak + Esposizio + Eremu distiratsuak + Distira + Kontrastea + Saturazioa + Argitasuna + Gamma + Beltzak + Zuriak + Tenperatura + Zorroztasuna + Berrezarri + Fokatzea + Bat ere ez + Erradiala + Lineala + Ispilua + Gaussiarra + Testua + Testu-aukerak + Testuaren kolorea + Letra-tipoa + Gehitu + Editatu + Zuzendu + Letra-tipoa + Kolorea + HP kolorea + Lerrokatzea + Aurrera + Ezabatu + Zure testua + Pintzela + Kolorea + Tamaina + Gogortasuna + Aurrera + Ezabatu + Pintzelaren kolorea + Editorea + Itxi editorea? + Ziur zaude aldaketak baztertu nahi dituzula? + Bai + Ez + Utzi + Onartu + Gorde + Esportatzen… + Esportatzen %s. + Eranskailua + Eranskailuaren kolorea + Eranskailu-aukerakSticker Options + Gehitu + Kolorea + Ezabatu + Aurrera + Zuzendu + Ordeztu + Opakutasuna + Kontrastea + Saturazioa + Distira + Igotakoak + Gainjarri + Normala + Ilundu + Pantaila + Gainjarri + Argitu + Biderkatu + Kolore erreta + Argi leuna + Argi gogorra + Bat ere ez + Urrea + Argi-filtrazioa 1 + Mosaikoa + Papera + Euria + Vintage + Biratu H + Biratu B + Desegin + Berregin + Kolore-hautatzailea + Gardena + Zuria + Grisa + Beltza + Urdin argia + Urdina + Morea + Orkidea + Arrosa + Gorria + Laranja + Urrea + Horia + Oliba + Berdea + Akuamarinoa + Hautatu kolorea (pipeta) + Moztu + + + How can I make Simple Gallery the default device gallery? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + Simple Gallery Pro - Photo Manager & Editor + + A premium app for managing and editing your photos, videos, GIFs without ads + + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. + + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Ikusi Simple Tools-en sorta osoa hemen: + https://www.simplemobiletools.com + + Simple Gallery Pro-ren webgune autonomoa: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml new file mode 100644 index 000000000..4c46af20d --- /dev/null +++ b/app/src/main/res/values-fi/strings.xml @@ -0,0 +1,427 @@ + + + Simple Gallery + Galleria + Muokkaa + Avaa kamera + (piilotettu) + (excluded) + Kiinnitä kansio + Poista kiinnitys + Kiinnitä ylimmäksi + Näytä kaikkien kansioiden sisältö + Kaikki kansiot + Vaihda kansionäkymään + Muu kansio + Näytä kartalla + Tuntematon sijainti + Äänenvoimakkuus + Kirkkaus + Lukitse näytönkierto + Vapauta näytönkierto + Change orientation + Force portrait + Force landscape + Use default orientation + Fix Date Taken value + Fixing… + Dates fixed successfully + No Date Taken values have been found + Share a resized version + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Switch to file search across all visible folders + Set as default folder + Unset as default folder + + + Suodata media + Kuvat + Videot + GIFit + RAW images + SVGs + Portraits + Mediaa ei löytynyt valituilla suotimilla. + Muuta suotimia + + + Tämä piilottaa kansion ja alikansiot lisäämällä \'.nomedia\' tiedoston kansioon. Näet ne valitsemalla \'Näytä piilotetut kansiot\' asetuksissa. Continue? + Poissulje + Poissuljetut kansiot + Hallitse poissuljettuja kansioita + Tämä poissulkee valitun ja alikansiot vain Simple Gallerysta. Voit hallinnoida poissuljettuja kansioita asetuksista. + Poissulje yläkansio tämän sijaan? + Kansion poissulkeminen piilottaa kansion alikansioineen vain Simple Galleryssa, ne jäävät näkyviin muihin sovelluksiin.\n\nJos haluat piilottaa kansion myös muissa sovelluksissa, käytä piilota-funktiota. + Poista kaikki + Poista kaikki kansiot poissuljettujen listasta? Tämä ei poista kansioita. + Piilotetut kansiot + Hallitse piilotettuja kansioita + Seems like you don\'t have any folders hidden with a \".nomedia\" file. + + + Sisällytä kansiot + Hallitse sisällettyjä kansioita + Lisää kansio + Jos sinulla on kansioita, jotka sisältää mediaa, mutta sovellus ei tunnistanut, voit lisätä ne manuaalisesti tähän.\n\Lisääminen ei poissulje muita kansioita. + No media files have been found. You can solve it by adding the folders containing media files manually. + + + Rajaa + Rajaa valinta ja tallenna + Leveys + Korkeus + Säilytä kuvasuhde + Aseta oikea resoluutio. + + + Editori + Käännä + Kuvan polkua ei ole + Invalid video path + Kuvan muokkaus epäonnistui + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + Muokkaa kuvaa: + Edit video with: + Kuvamuokkainta ei löytynyt + No video editor found + Tuntematon tiedostosijainti + Lähdetiedoston ylikirjoitus epäonnistui + Käännä vasemmalle + Käännä oikealle + Käännä 180º + Pyöräytä + Pyöräytä vaakasuoraan + Pyöräytä pystysuoraan + Free + Other + + + Simple Wallpaper + Aseta taustakuvaksi + Taustakuvan asetus epäonnistui + Aseta taustakuvaksi sovelluksella: + Asetetaan taustakuvaa… + Taustakuva asetettu onnistuneesti + Kuvasuhde pystyssä + Kuvasuhde vaakatasossa + Aloitusnäyttö + Lukitusnäyttö + Aloitusnäyttö ja lukitusnäyttö + + + Diaesitys + Aikaväli (sekunteja): + Sisällytä Kuvat + Sisällytä Videot + Sisällytä GIFit + Satunnainen järjestys + Liiku takaisinpäin + Jatkuva diaesitys + Animation + None + Fade + Slide + Diaesitys päättyi + Mediaa diaesitykseen ei löytynyt + + + Group direct subfolders + + + Group by + Do not group files + Folder + Last modified + Last modified (daily) + Last modified (monthly) + Date taken + Date taken (daily) + Date taken (monthly) + File type + Extension + Please note that grouping and sorting are 2 independent fields + + + Folder shown on the widget: + Show folder name + + + Toista videot automaattisesti + Remember last video playback position + Tiedostonimien näkyvyys + Jatkuvat videot + Animoi GIFit pienoiskuvissa + Täysi kirkkaus mediaa katsoessa + Leikkaa pienoiskuvat neliöiksi + Show video durations + Käännä koko ruudun mediaa + Järjestelmän asetukset + Laitteen kierto + Kuvasuhde + Musta tausta ja tilapalkki täyden ruudun tilassa + Vieritä pienoiskuvia vaakasuorassa + Piilota järjestelmän UI automaattisesti koko näytön mediassa + Poista tyhjät kansiot kansion tyhjennyksen jälkeen + Salli kirkkauden säätäminen pystysuorilla eleillä + Salli videon äänenvoimakkuuden ja kirkkauden säätö pystysuorilla eleillä + Näytä kansioiden sisällön määrä päänäkymässä + Näytä yksityiskohtaiset tiedot täyden näytön tilassa + Hallitse yksityiskohtaisia tietoja + Salli lähentäminen yhdellä sormella täyden ruudun tilassa + Salli median selaaminen ruudun reunoja koskettamalla + Allow deep zooming images + Piilota yksityiskohtaiset tiedot kun tilapalkki on piilotettu + Show some action buttons at the bottom of the screen + Show the Recycle Bin at the folders screen + Deep zoomable images + Show images in the highest possible quality + Show the Recycle Bin as the last item on the main screen + Allow closing the fullscreen view with a down gesture + Allow 1:1 zooming in with two double taps + Always open videos on a separate screen with new horizontal gestures + Show a notch if available + Allow rotating images with gestures + File loading priority + Speed + Compromise + Avoid showing invalid files + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Esikatselukuvat + Täyden näytön media + Yksityiskohtaiset tiedot + Bottom actions + + + Manage visible bottom actions + Toggle favorite + Toggle file visibility + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + Miten voin tehdä Simple Gallerystä oletusgalleriasovelluksen? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + Simple Gallery Pro - Photo Manager & Editor + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. + + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e98176869..e87c78775 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,117 +1,422 @@ - Simple Galerie + Simple Gallery Galerie - Édition - Ouvrir caméra - Ouvrir avec - Aucune application valide trouvée + Modifier + Ouvrir l\'appareil photo (caché) - Épingler les dossiers + (exclu) + Épingler le dossier Désépingler le dossier - Afficher le contenu de tous les dossiers + Épingler en haut + Affichage \"Tous les dossiers\" Tous les dossiers - Permuter vers la vue du dossier + Affichage \"Galerie\" Autre dossier Afficher sur la carte Position inconnue - Aucune application de cartes n\'a été trouvée - Aucune application d\appareil photo n\'a été trouvée - Augmenter le nombre de colonnes - Réduire le nombre de colonnes - Afficher temporairement les fichiers masqués - Change cover image - Select photo - Use default + Volume + Luminosité + Verrouiller la rotation + Déverrouiller la rotation + Orientation + Forcer la vue portrait + Forcer la vue paysage + Utiliser l\'orientation par défaut + Corriger les dates de prise de vue + Correction en cours… + Dates corrigées + Aucune date de prise de vue trouvée + Partager une version redimensionnée + Hé,\n\nvous avez apparemment fait une mise à niveau à partir de l\'ancienne application gratuite. Vous pouvez maintenant désinstaller l\'ancienne version avec le bouton \'Mettre à niveau vers Pro\' en haut des paramètres de l\'application.\n\nVos éléments de la corbeille seront supprimés, les éléments favoris seront non marqués et vous devrez également restaurer les paramètres de votre application.\n\nMerci ! + Basculer vers la recherche de fichiers + Dossier par défaut + Oublier le dossier + + + Filtrer les médias + Images + Vidéos + GIFs + Images RAW + SVGs + Portraits + Aucun fichier média trouvé avec les filtres sélectionnés + Modifier les filtres - Cette option masque le dossier en ajoutant un fichier \'.nomedia\' à l\'intérieur, cela masquera aussi tous les sous-dossiers. Vous pouvez les voir en modifiant l\'option \'Afficher les dossiers cachés\' dans les Paramètres. Continuer ? - Exclure + Cette option cache le dossier en y ajoutant un fichier \".nomedia\", cela cachera aussi tous les sous-dossiers. Vous pouvez les voir en appuyant sur le symbole \"Œil\" (permettant l\'affichage) depuis les paramètres. Continuer ? + Exclure le dossier Dossiers exclus Gérer les dossiers exclus - Cela va exclure la sélection ainsi que ses sous-dossiers depuis Simple Galerie uniquement. Vous pouvez gérer les dossiers exclus depuis les Paramètres. - Exclure un parent plutôt ? - Exclure des dossiers les masquera ainsi que leurs sous-dossiers uniquement dans Simple Galerie, ils seront toujours visibles depuis d\'autres applications.\\n\\nSi vous voulez aussi les masquer ailleurs, utilisez la fonction Masquer. + Cela va exclure la sélection ainsi que ses sous-dossiers depuis Simple Gallery uniquement. Vous pouvez gérer les dossiers exclus depuis les paramètres. + Exclure un dossier parent ? + Exclure des dossiers les cachera ainsi que leurs sous-dossiers uniquement dans Simple Gallery, ils seront toujours visibles depuis d\'autres applications.\n\nSi vous voulez aussi les cacher ailleurs, utilisez la fonction \"Cacher\". Tout supprimer - Supprimer tous les dossiers de la liste des exclusions ? Ceci n\'effacera pas les dossiers. + Supprimer tous les dossiers de la liste des exclusions ? Cela n\'effacera pas les dossiers. + Dossiers cachés + Gérer les dossiers cachés + Il semblerait que vous n\'ayez pas de dossier caché avec un fichier \".nomedia\". - Dossiers inclus - Gérer les dossiers inclus + Dossiers ajoutés + Gérer les dossiers ajoutés Ajouter un dossier - Si vous avez des dossiers contenant du media et qui ne sont pas reconnus par l\'application alors, vous pouvez les ajouter manuellement ici. + Si vous avez des dossiers contenant des médias qui ne sont pas affichés dans l\'application, vous pouvez les ajouter manuellement ici.\n\nCet ajout n\'exclura aucun autre dossier. + Aucun fichier multimédia n\'a été trouvé. Vous pouvez ajouter manuellement des dossiers contenant des fichiers multimédia. Redimensionner Redimensionner la sélection et enregistrer Largeur Hauteur - Garder le ratio - Veuillez entrez une résolution valide + Conserver le rapport d\'affichage + Veuillez entrer une résolution valide Éditeur - Sauvegarder - Tourner - Chemin - Chemin invalide - Échec de l\'édition de l\'image - Éditer l\'image avec : - Aucun éditeur d\'image trouvé - Emplacement du fichier inconnu. - Ne parvient pas à réécrire par dessus le fichier source + Pivoter + Emplacement d\'image invalide + Emplacement de vidéo invalide + L\'édition de l\'image a échoué + L\'édition de la vidéo a échoué + L\'édition de l\'image a été annulée + L\'édition de la vidéo a été annulée + Le fichier a été éditée avec succès + L\'image a été éditée avec succès + La vidéo a été éditée avec succès + Modifier l\'image avec : + Modifier la vidéo avec : + Aucun éditeur d\'image trouvé + Aucun éditeur de vidéo trouvé + Emplacement du fichier inconnu + Impossible de remplacer le fichier source Pivoter à gauche Pivoter à droite Pivoter à 180º Retourner Retourner horizontalement - Retourner verticallement - Erreur excès de mémoire + Retourner verticalement + Libre + Autre - Simple fond d\'écran + Fond d\'écran simple Définir comme fond d\'écran - Échec de la définition en tant que fond d\'écran. + Échec de la définition en tant que fond d\'écran Définir comme fond d\'écran avec : - Aucune application trouvée pour continuer cette action - Paramètre de fond d\'écran… - Fond d\'écran défini avec succès. - Ratio aspect Portrait - Ratio aspect Paysage + Définition du fond d\'écran en cours… + Fond d\'écran défini + Rapport d\'affichage portrait + Rapport d\'affichage paysage + Écran d\'accueil + Écran de déverrouillage + Écran d\'accueil et écran de déverrouillage + + + Diaporama + Intervalle (secondes) : + Inclure les images + Inclure les vidéos + Inclure les GIFs + Ordre aléatoire + Défilement inversé + Diaporama en boucle + Animation + Aucune + Fondu + Glissement + Diaporama terminé + Aucun média trouvé pour le diaporama + + + Mode sous-dossiers + + + Grouper par + Ne pas grouper les fichiers + Dossier + Date de modification + Date de modification (par jour) + Date de modification (par mois) + Date de prise de vue + Date de prise de vue (par jour) + Date de prise de vue (par mois) + Type de fichier + Extension + \'Grouper par\' et \'Trier par\' sont 2 modes indépendants + + + Dossier affiché dans le widget : + Afficher le nom du dossier - Afficher les dossiers cachés Lecture automatique des vidéos - Permutter la visibilité des noms de fichier - Afficher les médias - Seulement les images - Vidéos uniquement - Gifs only - Images, videos, gifs - Images et vidéos - Tourner en boucle les vidéos - Gifs animés sur les miniatures - Luminosité maximale lors de l\'affichage de media - Rogner les miniatures en carrés - Pivoter les medias plen écran selon + Mémoriser la position de lecture des vidéos + Permuter l\'affichage des noms de fichier + Lecture en boucle des vidéos + Animer les miniatures des GIF + Luminosité maximale + Recadrer les miniatures en carré + Afficher la durée des vidéos + Orientation de l\'affichage Paramètres système Rotation de l\'appareil - Ratio d\'aspect - Dark background at fullscreen media - Scroll thumbnails horizontally + Rapport d\'affichage + Arrière-plan et barre d\'état noirs + Défiler les miniatures horizontalement + Masquer automatiquement l\'interface utilisateur + Supprimer les dossiers vides après avoir supprimé leur contenu + Contrôler la luminosité des images par gestes verticaux + Contrôler le volume et la luminosité des vidéos avec des gestes verticaux + Afficher le nombre de fichiers des dossiers + Afficher les informations supplémentaires du média en plein écran + Gérer les informations supplémentaires + Activer les zoom à un doigt sur les images en plein écran + Appuyer sur les cotés de l\'écran pour changer instantanément de média + Activer les options de zoom avancées + Cacher les informations supplémentaires si la barre d\'état est masquée + Afficher les boutons d\'action + Afficher la corbeille en vue \"Dossier\" + Niveau de zoom + Afficher les images avec la meilleure qualité possible + Afficher la corbeille en fin de liste sur l\'écran principal + Fermer la vue plein écran par un geste vers le bas + Permettre un zoom avant 1:1 par double appui + Ouvrir les vidéos dans une application externe + Afficher une encoche si disponible + Pivoter les images par gestes + Priorité de chargement des fichiers + Rapide + Compromis + Éviter l\'affichage de fichiers invalides + Afficher les types d\'image + Zoomer les vidéos par un double appui + Style des miniatures des dossiers + Style des miniatures des fichiers + Espacement des miniatures + Afficher le nombre de fichiers sur une autre ligne + Afficher le nombre de fichiers entre parenthèses + Ne pas afficher le nombre de fichiers + Limiter à une ligne les noms de fichiers + Carré + Arrondi + + + Miniatures + Plein écran + Détails supplémentaires + Barre d\'actions + + + Gérer la barre d\'actions + Ajouter aux favoris + Visibilité du fichier + + + Libre + Réinitialiser + Carré + Transformer + Filtres + Aucun + Ajuster + Ombres + Exposition + Détails + Luminosité + Contraste + Saturation + Clarté + Gamma + Noirs + Blancs + Température + Netteté + Réinitialiser + Floutage + Aucun + Radial + Linéaire + Miroir + Gaussien + Texte + Options du texte + Couleur du texte + Police + Ajouter + Éditer + Redresser + Police + Couleur + Fond + Alignement + Devant + Supprimer + Votre texte + Pinceau + Couleur + Taille + Contour + Devant + Supprimer + Couleur du pinceau + Éditeur + Fermer l\'éditeur ? + Voulez-vous vraiment abandonner les modifications ? + Oui + Non + Annuler + Accepter + Enregistrer + Exportation… + Exportation %s + Sticker + Couleur du Sticker + Options du Sticker + Ajouter + Couleur + Supprimer + Devant + Redresser + Remplacer + Transparence + Contraste + Saturation + Luminosité + Téléchargements + Superposition + Normal + Assombrir + Écran + Recouvrir + Alléger + Dupliquer + Brûlure de couleur + Lumière douce + Lumière forte + Aucune + Doré + Fuite légère 1 + Mosaïque + Papier + Pluie + Vintage + Symétrie H + Symétrie V + Annuler + Refaire + Sélecteur de couleur + Transparent + Blanc + Gris + Noir + Bleu clair + Bleu + Violet + Orchidée + Rose + Rouge + Orange + Or + Jaune + Olive + Vert + Aquamarin + Couleur de la pipette + Couper + + + Comment faire de Simple Gallery ma galerie par défaut ? + Il faut dans un premier temps, trouver l\'application \"Galerie\" par défaut dans la section \"Applications\" des paramètres de l\'appareil, puis appuyer sur \"Ouvrir par défaut\", et enfin sélectionner \"Réinitialiser les paramètres par défaut\". La prochaine fois que vous ouvrirez une image ou une vidéo, il vous sera proposé de choisir une application, choisissez \"Simple Gallery\" puis \"Toujours\". + J\'ai verrouillé l\'application avec un mot de passe et je ne m\'en rappelle plus. Que faire ? + Il y a deux façons de procéder. Soit vous réinstallez l\'application, soit vous recherchez l\'application dans les paramètres de l\'appareil et appuyez sur \"Effacer les données\". Cela va seulement réinitialiser les paramètres de l\'application et ne supprimera pas vos fichiers. + Comment faire pour qu\'un album soit toujours affiché tout en haut ? + Vous devez simplement effectuer un appui prolongé sur l\'album en question et choisir l\'icône \"Épingler\" dans le menu d\'actions. Vous pouvez en épingler plusieurs. Les éléments épinglés seront alors triés selon l\'ordre par défaut. + Comment avancer rapidement dans les vidéos ? + Appuyez deux fois sur le côté de l\'écran, ou appuyez sur la valeur de durée actuelle ou maximale près de la barre de recherche. Si vous activez l\'ouverture des vidéos sur un écran séparé dans les paramètres de l\'application, vous pouvez également utiliser des gestes horizontaux. + Quelle est la différence entre cacher et exclure un dossier ? + \"Exclure un dossier\" permet de ne pas l\'afficher uniquement dans Simple Gallery, alors que \"Cacher un dossier\" rend le dossier invisible sur l\'ensemble de l\'appareil, y compris pour les autres applications de galerie. Dans le dernier cas, un fichier \".nomedia\" est créé dans le dossier caché, et peut être supprimé avec n\'importe quel explorateur de fichiers. Notez que certains appareils ne permettent pas de masquer certains dossiers tels qu\'Appareil photo, Captures d\'écran et Téléchargements. + Pourquoi des dossiers avec des pochettes d\'albums musicaux ou des miniatures d\'images sont affichés ? + Il est possible que des albums qui ne devraient pas être affichés le soient. Vous pouvez les exclure facilement en les sélectionnant par un appui prolongé, puis en choisissant l\'option \"Exclure\", après quoi vous pouvez aussi sélectionner le dossier parent, ce qui devrait éviter l\'apparition d\'albums similaires. + Un dossier avec des images n\'apparaît pas. Que faire ? + Cela peut arriver pour de multiples raisons, mais c\'est facile à résoudre. Allez dans \"Paramètres\", puis \"Gérer les dossiers ajoutés\", appuyez sur \"+\" et sélectionnez le dossier voulu. + Comment faire apparaître uniquement certains dossiers ? + Ajouter un dossier dans les \"Dossiers ajoutés\" rend visible l\'ensemble du contenu du dossier. Pour exclure certains dossiers, il faut aller dans \"Paramètres\", puis \"Gérer les dossiers exclus\", exclure le dossier racine \"/\", puis ajouter les dossiers souhaités dans \"Paramètres\", puis \"Gérer les dossiers ajoutés\". Seuls les dossiers sélectionnés seront visibles, du fait que les exclusions et inclusions sont récursives, et si un dossier est à la fois exclus et inclus, il sera affiché. + Puis-je recadrer des images avec cette application ? + Oui, vous pouvez recadrer les images dans l\'éditeur en faisant glisser les coins de l\'image. Vous pouvez accéder à l\'éditeur en appuyant longuement sur une vignette d\'image et en sélectionnant \"Modifier\", ou en sélectionnant \"Modifier\" en mode plein écran. + Puis-je regrouper les miniatures des fichiers multimédias ? + Bien sûr, il vous suffit d\'utiliser l\'option de menu \"Grouper par\" lorsque vous êtes dans l\'affichage des miniatures. Vous pouvez regrouper les fichiers selon plusieurs critères, y compris la date de prise de vue. Si vous utilisez la fonction \"Affichage Tous les dossiers\", vous pouvez également les regrouper par dossier. + Le tri par date de prise de vue ne semble pas fonctionner correctement, comment puis-je le corriger ? + Il est très probablement causé par les fichiers copiés quelque part. Vous pouvez le corriger en sélectionnant les miniatures du fichier et en sélectionnant \"Corriger la valeur des dates de prise des photos\". + Je vois des bandes de couleurs sur les images. Comment puis-je améliorer la qualité ? + La solution actuelle d\'affichage des images fonctionne bien dans la grande majorité des cas, mais si vous voulez une qualité d\'image encore meilleure, vous pouvez activer l\'option \"Afficher les images avec la plus haute qualité possible\" dans la section \"Niveau de zoom maximal des images\" des paramètres de l\'application. + J\'ai caché un fichier ou un dossier. Comment puis-je en rétablir l\'affichage ? + Vous pouvez soit appuyer sur l\'option \"Afficher les fichiers cachés\" du menu de l\'écran principal, ou appuyer sur le bouton \"Afficher les éléments cachés\" dans les paramètres de l\'application. Si vous voulez rétablir leur affichage, effectuez un appui prolongé dessus et appuyez sur le symbole \"Œil\" permettant l\'affichage. Les dossiers sont cachés en ajoutant un fichier \".nomedia\" à leur racine, vous pouvez également supprimer ce fichier avec n\’importe quel explorateur de fichiers. + Pourquoi l\'application prend-elle tant de place ? + Le cache d\'application peut prendre jusqu\'à 250 Mo pour accélérer le chargement des images. Si l\'application occupe encore plus d\'espace, c\'est probablement parce que vous avez des éléments dans la corbeille. Ces fichiers comptent pour la taille de l\'application. Vous pouvez vider la corbeille en l\'ouvrant et en supprimant tous les fichiers ou à partir des paramètres de l\'application. Chaque fichier de la corbeille est automatiquement supprimé après 30 jours. + + Simple Gallery Pro - Photo Manager & Editeur - Un album pour visionner photos et vidéos sans publicité. + Browse your memories without any interruptions with this photo and video gallery - Un simple outil pour visionner les photos et les vidéos. Elles peuvent être triées par dates, tailles, noms dans les deux sens (alphabétique comme désalphabétique), il est possible de zoomer sur les photos. Les fichiers sont affichés sur de multiple colonnes en fonction de la taille de l\'écran, vous pouvez changer le nombre de colonnes par pincement. Elles peuvent être renommées, partagées, supprimées, copiées et déplacées. Les images peuvent en plus être tournées, rognées ou être définies comme fond d\'écran directement depuis l\'application. + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. - La galerie est aussi offerte pour l\'utiliser comme une tierce partie pour de la prévisualisation des images/vidéos, joindre aux clients mail etc. C\'est parfait pour un usage au quotidien. - - L\'application ne contient ni de publicité ni d\'autorisation inutile. Elle est totalement OpenSource et est aussi fournie avec un thème sombre. - - Cette application est juste l\'une des applications d\'une plus grande suite. Vous pouvez trouver les autres sur http://www.simplemobiletools.com - + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + Filtrar medios + Imaxes + Vídeos + GIFs + Imaxes RAW + SVGs + Retratos + Non se atopou multimedia do tipo indicado polo filtro. + Cambiar filtro + + + Esta función oculta o cartafol engadíndolle ficheiro \'.nomedia\', tamén ocultará os subcartafoles. Podes velos pulsando a opción \'Mostrar elementos ocultos\' nos Axustes. Continuar? + Excluír + Cartafoles excluídos + Xestionar cartafoles excluídos + Isto ocultará a selección xunto cos seus subcartafoles só en Simple Gallery. Podes xestionar os cartafoles ocultos en Axustes. + Excluír o cartafol pai no seu lugar? + Excluír un cartafol xunto cos subcartafoles só terá efecto en Simple Gallery, seguirán sendo visibles noutros aplicativos.\n\nSe tamén queres excluílos noutros aplicativos, utiliza a opción Agochar. + Eliminar todos + Eliminar todos os cartafoles da lista de excluídos? Isto non borrará os cartafoles. + Cartafoles ocultos + Xestionar cartafoles ocultos + Semella que non tes ningún cartafol oculto cun ficheiro \".nomedia\". + + + Cartafoles incluídos + Xestionar cartafoles incluídos + Engadir cartafol + Se tes algún cartafol con medios, mais non foi recoñecido polo aplicativo, pódelo engadir manualmente.\n\nEngadindo aquí elementos non eliminarás outros. + Non se atopou ningún ficheiro multimedia. Podes solucionalo engadindo manualmente algúns cartafoles con eses ficheiros. + + + Redimensionar + Selección da redimensión e gardar + Ancho + Alto + Manter proporcións + Por favor escribe unha resolución válida + + + Editor + Rotar + Ruta á imaxe non válida + Invalid video path + Fallo na edición da imaxe + Video editing failed + Cancelouse a edición da imaxe + Video editing cancelled + Ficheiro editado correctamente + Image edited successfully + Video edited successfully + Editar imaxe con: + Edit video with: + Non se atopou ningún editor + No video editor found + Localización do ficheiro descoñecida + Non se puido sobrescribir o ficheiro + Rotar a esquerda + Rotar a dereita + Rotar 180º + Voltear + Voltear horizontalmente + Voltear verticalmente + Libre + Outro + + + Fondo de pantalla + Establecer como fondo de pantalla + Fallou establecer fondo de pantalla + Establecer fondo de pantalla con: + Establecendo fondo de pantalla… + Fondo de pantalla establecido correctamente + Proporción de Retrato + Proporción de Paisaxe + Pantalla de incio + Pantalla de bloqueo + Pantallas de inicio e bloqueo + + + Presentación + Intervalo (segundos): + Incluír fotos + Incluír vídeos + Incluír GIFs + Orde aleatoria + Mover atrás + Reproducir en bucle + Animación + Ningún + Esvaecemento + Deslizamento + Rematou a presentación + Non se atopou multimedia para a presentación + + + Agrupar subcartafoles directos + + + Agrupar por + Non agrupar ficheiros + Cartafol + Último modificado + Último modificado (diario) + Último modificado (mensual) + Data de captura + Data de captura (diaria) + Data de captura (mensual) + Tipo de ficheiro + Extensión + Por favor, ten en conta que agrupar e ordenar son dous campos diferentes + + + Cartafol a mostrar no widget: + Mostrar nome de cartafol + + + Reproducir vídeos automáticamente + Lembrar a posición do vídeo na última vez + Mudar a visibilidade do ficheiro + Víeos en bucle + Animar os GIFs na icona + Brillo ao máximo cando mire medios + Recortar iconas a cadrados + Mostrar a duración dos vídeos + Rotar medios a pantalla completa a + Axuste ao sistema + Rotación do dispositivo + Relación de aspecto + Fondo negro e barra de estado en reprodución a pantalla completa + Desprazar iconas horizontalmente + Agochar controis do sistema cando visualice a pantalla completa + Borrar cartafoles baleiros cando elmine oseu contido + Permitir controlar o brillo da foto con xestos verticais + Permitir controlar o volume do vídeo e o brillo con xestos verticais + Mostrar a conta de medios do cartafol na vista principal + Mostrar información pormenorizada sobre medios a pantalla completa + Xestionar información polo miúdo + Permitir zoom cun dedo a pantalla completa + Permitir o cambio instantáneo de medios premendo os lados da pantalla + Permitir ampliar moito as imaxes + Agochar detalles extendidos cando a barra de estado está oculta + Mostrar botóns para accións na parte baixa da pantalla + Mostrar a Papeleira na pantalla de cartafoles + Imaxes con moita ampliación + Mostrar imaxes na súa máxima calidade + Mostrar a Papeleira no derradeiro posto da pantalla principal + Permitir pechar a visualización en pantalla completa deslizando cara abaixo + Permitir ampliar a 1:1 con dous toques + Abrir sempre os vídeos nunha pantalla diferente con novos xestos horizontais + Mostrar unha marca se a houbese + Permitir rotar unha imaxe con xestos + Prioridade de carga de ficheiro + Velocidade + Compromiso + Non mostrar ficheiros non válidos + Mostrar tipos de ficheiros de imaxe + Permitir zoom nos vídeos co dobre-toque sobre eles + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Iconas + Medios a pantalla completa + Detalles ampliados + Accións do fondo + + + Xestionar accións visibles do fondo + Alternar favorito + Alternar visibilidade do ficheiro + + + Personalizado + Reiniciar + Cadrado + Transformar + Filtrar + Ningún + Axustar + Sombras + Exposición + Resalte + Brillo + Contraste + Saturación + Claridade + Gamma + Negros + Brancos + Temperatura + Nitidez + Reiniciar + Enfoque + Ningún + Radial + Lineal + Reflectir + Gaussiano + Texto + Opcións de Texto + Cor de Texto + Fonte + Engadir + Editar + Endereitar + Fonte + Cor + Cor Fondo + Alineamento + Á Fronte + Borrar + O teu texto + Pincel + Cor + Tamaño + Dureza + Á Fronte + Borrar + Cor do Pincel + Editor + Pechar o Editor? + Do you really want to discard the changes? + Si + Non + Cancelar + Aceptar + Gardar + Exportando… + Exportando %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Volteo H + Volteo V + Desfacer + Refacer + Escolla de Cor + Transparente + Branco + Gris + Negro + Azul claro + Azul + Púrpura + Rosa brillante + Rosa + Vermello + Laranxa + Dourado + Amarelo + Verde oliva + Verde + Augamariña + Escoller coa pipeta + Trim + + + Como podo facer que Simple Gallery sexa a galería por defecto no meu dispositivo? + Primeiro debes atopar a galería por omisión actual na sección de App nos axustes do dispositivo, buscar un botón que diga algo como \"Abrir por omisión\", pulsalo e despois seleccionar \"Limpar por omisión\". + A próxima vez que intentes abrir unha imaxe ou vídeo aparecerá un selector de aplicativos onde podes escoller Simple Gallery e facela a aplicativo por defecto/n. + Asegurei o aplicativo cun contrasinal, pero esquecino. Que podo facer? + Pode solucionado de dous xeitos. Ou ben reinstalar o aplicativo ou buscar o aplicativo os axustes do dispositivo e escoller \"Limpar datos\". Restablecerá todos os seus axustes, mais non eliminará ficheiros de medios. + Como podo facer que un álbum apareza sempre arriba de todo? + Pode manter premido o álbum e escoller a icona de Fixar no menú de accións, isto fixarao arriba. Pode fixar varios cartafoles tamén, os elementos fixados estarán ordenados polo criterio por omisión. + Como podo aumentar a velocidade de reprodución do vídeo? + Pode facelo tocando dúas veces o lateral da pantalla, ou tocando os textos de duración máxima ou actual preto da barra de avance. Se activa abrir os vídeos nunha pantalla separada, tamén pode usar xestos horizontais. + Cal é a diferenza entre agochar e excluír un cartafol? + A exclusión prevén que se mostre o cartafol só en Simple Gallery, mentras Agochar funciona para todo o sistema e agocha o cartafol para outras galerías tamén. Esto funciona creando un ficheiro baldeiro de nome \".nomedia\" no cartafol, que tamén pode quitar con calquera xestor de ficheiros.Teña en conta que algúns dispositivos non permiten cartafoles agochados como Cámara, Capturas de pantalla e Descargas. + Por que aparecen cartafoles de música con portadas ou pegatinas? + Pode acontecer que vexa que aparecen álbumes raros. Pode excluílos con facilidade mantendo premidos e escollendo Excluír. No seguinte diálogo pode escoller o cartafol pai, esto probablemente agoche outros álbumes relacionados. + Un cartafol con imaxes non aparece, que podo facer? + Isto pode acontecer por varias razóns, pero é doado resolvelo. Vaia a Axustes -> xestionar cartafoles incluídos, escolle o Máis e navegar ate o cartafol requerido. + E que pasa se só quero que sexan visibles certos cartafoles + Engadir un cartafol a Cartafoles incluídos non exclúe nada de xeito automático. O que pode facer é ir a Axustes -> Xestionar cartafoles incluídos, excluír o cartafol root \"/\", e despois engadir os cartafoles desexados con Axustes -> Xestionar Cartafoles Incluídos. + Isto fará visibles só aos cartafoles escollidos, como tanto excluír e incluír son recursivos e si está excluído e logo incluído, será mostrado. + Podo recortar imaxes con este aplciativo? + Si, pode recortar imaxes no editor, arrastrando as esquinas das imaxes. Pode chegar ao editor tanto presionando na imaxe durante un anaco e seleccionando Editar, ou seleccionando Editar dende a vista de pantalla completa. + Podo agrupar as iconas dalgún xeito? + Claro, use no menu \"Agrupar por\" na vista de iconas. Pode facelo por múltiples criterios, incluindo a data de captura. Se usa a función \"Mostrar o contido dos cartafoles\", tamén pode agruparlos por cartafoles. + Semella que Ordenar por Data non está a funcionar, como o amaño? + Seguramente foi causa de copiar os ficheiros de algures. Podes amañalo seleccionando as iconas e \"Arranxar o valor da Data de Captura\". + Vexo bandas de cor nas imaxes. Como podo mellorar a súa calidade? + A solución actual de mostra das imaxes funciona ben na maioría dos casos, pero se desexa unha calidade maior, active \"Mostrar imaxes na máxima calidade posible\" na configuración, na sección \"Imaxes con moita ampliación\". + Teño un ficheiro ou cartafol oculto. Como o amoso? + Pode ou ben activar \"Mostrar temporalmente ficheiros agochados\" no menu principal, ou activar \"Amosar obxectos ocultos\" na configuración. Se quere amosalo sempre, pode presionar uns intres e seleccionar \"Amosar\". Os cartafoles agóchanse engadindo un ficheiro oculto \".nomedia\" no seu interior; tamén pode borrar ese ficheiro con calquera xestor de ficheiros. + Por que consume tanto espacio este aplicativo? + Os ficheiros temporais poden chegar aos 250MB e server para cargar máis rápido. Se o aplicativo está a a consumir máis espazo, o máis probable é que teña ficheiros na Papeleira de Reciclaxe.Estes ficheiros contan no tamaño da aplicación.Podes baleirala abríndoa e borrando os ficheiros, ou desde a configuración.Cada ficheiro da Papeleira bórrase automáticamente tras 30 días. + + + Simple Gallery Pro - Xestor e Editor de fotos + + Busca nos recordos sen interrupcións na galería de fotos e vídeo + + Simple Gallery Pro unha galería altamente personalizable. Organiza & edita as túas fotos, recupera ficheiros borrados desde a papeleira, protexe & oculta ficheiros e visualiza gran número de formatos de fotos & vídeo incluíndo RAW, SVG e moitos máis. + + O app non ten publicidade nin permisos innesarios. Como tampouco require acceso a internet, a túa privacidade está protexida. + + ------------------------------------------------- + SIMPLE GALLERY PRO – Características + ------------------------------------------------- + + • Galería sen conexión a internet, publicidade nin ventás emerxentes + • Editor de fotos Simple gallery – recorta, rota, cambia tamaño, debuxa, fitros, e máis + • Non precisa acceso a internet, dándoche máis seguridade e privacidade + • Non pide permisos innecesarios + • Busca rápida de ficheiros de foto e vídeo + • Abre e visualiza gran cantidade de formatos (RAW, SVG, panoramic etc) + • Xestos intuitivos para editar e organizar os ficheiros + • Múltiples filtros, agrupamento e orde dos ficheiros + • Personaliza o aspecto de Simple Gallery Pro + • Dispoñible en 32 idiomas + • Marca ficheiros como favoritos para acceso rápido + • Protexe fotos e vídeos cun patrón, PIN ou impresión dixital + • Usa PIN, patrón e impresión dixital para protexer a apertura do app ou indicar funcións específicas + • Recupera fotos e vídeos borrados desde a papeleira + • Cambia a visibilidade dos ficheiros para ocultar fotos e vídeos + • Crea presentacións personalizadas cos teus ficheiros + • Mira a información detalladas dos ficheiros (resolución, valores EXIF etc) + • Simple Gallery Pro é código aberto + … e moito máis! + + PHOTO GALLERY EDITOR + Simple Gallery Pro permite editar as fotos no momento. Recorta, voltea, rota e redimensiona as túas fotos. Se te sintes inspirado tamén pode engadir filtros e debuxar nas imaxes! + + SOPORTA MOITOS TIPOS DE FICHEIROS + Simple Gallery Pro soporta gran variedade de formatos como JPEG, PNG, MP4, MKV, RAW, SVG, Panoramicas, Vídeos panorámicos e moito máis. Non como outros. + + XESTOR DE FOTOS MOI PERSONALIZABLE + Desde a interface ata a función dos botóns da barra de ferramentas, Simply Gallery Pro é moi personalizable e funciona do xeito que ti queres. Ningún outro xestor de galerías ten esta flexibilidade! Ao ser código aberto, está dipoñible en 32 idiomas! + + RECUPERA FOTOS & VIDEOS ELIMINADOS + Borraches sen querer unha foto ou vídeo? Non te apenes! Simple Gallery Pro ten unha función para recuperar facilmente vídeos e fotos borrados. + + PROTEXE & OCULTA FOTOS, VIDEOS & FICHEIROS + Usando un prendedor, patrón ou a impresión dixital no escáner do dispositivo podes protexer e agochar fotos, vídeos e álbumes enteiros. Podes protexer a app ou establecer funcións específicas no app. Por exemplo, que non poidas eliminar un ficheiro sen o escáner da impresión dixital, axudándoche a protexer o borrado accidental. + + Aquí tes todas as Simple Tools: + https://www.simplemobiletools.com + + Sitio web de Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml new file mode 100644 index 000000000..61dbb91cc --- /dev/null +++ b/app/src/main/res/values-hr/strings.xml @@ -0,0 +1,427 @@ + + + Jednostavna galerija + Galerija + Uredi + Otvori kameru + (skriveno) + (izuzeto) + Prikvači mapu + Otkvači mapu + Prikvači na vrh + Prikaži cijeli sadržaj mape + Sve mape + Prijeđi na prikaz mape + Ostale mape + Prikaži na karti + Nepoznata lokacija + Glasnoća + Svjetlina + Zaključaj orijentaciju slike + Otključaj orijentaciju slike + Promijeni orijentaciju slike + Prikaz slike u portretnom formatu + Prikaz slike u pejzažnom načinu rada + Koristi zadanu orijentaciju slike + Ispravi vrijednost datuma snimanja + Popravljam… + Datumi uspješno popravljeni + No Date Taken values have been found + Share a resized version + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Switch to file search across all visible folders + Set as default folder + Unset as default folder + + + Filtriranje medija + Slike + Video + GIF-ovi + RAW slike + SVG-ovi + Portraits + Nije pronađena nijedna datoteka s odabranim filtrom. + Promijeni filter + + + Ova funkcija skriva mapu dodavanjem \'.nomedia\' datoteke u njega. Također će sakriti i sve podmape. Možete ih vidjeti uključivanjem opcije \'Prikaži skrivene mape\' u Postavkama. Nastaviti? + Izuzmi + Izuzete mape + Upravljaj izuzetim mapama + Ovo će isključiti odabir zajedno s podmapama samo iz Jednostavne galerije. Izuzete mape možete upravljati u Postavkama. + Izostavi rađe glavnu mapu? + Izuzevši mape učinit će ih zajedno s podmapama skrivene samo u jednostavnoj galeriji, i dalje će biti vidljive u drugim aplikacijama.\n\nAko ih želite sakriti i od drugih aplikacija, upotrijebite funkciju Sakrij. + Ukloni sve + Želite li ukloniti sve mape s popisa izuzetih? Ovo neće izbrisati mape. + Skrivene mape + Upravljanje skrivenim mapama + Čini se da nemate skrivenih mapa s \".nomedia\" datotekom. + + + Uključene mape + Upravljajte uključenim mapama + Dodaj mapu + Ako imate neke mape koje sadrže medije, ali ih aplikacija nije prepoznala, ručno ih možete dodati ovdje.\n\nDodavanjem nekih stavki ovdje nećete izuzeti bilo koju drugu mapu. + No media files have been found. You can solve it by adding the folders containing media files manually. + + + Promjeni veličinu + Promjeni veličinu odabranog i spremi + Širina + Visina + Zadrži omjer slike + Molim unesite valjanu rezoluciju + + + Uređivač + Rotiraj + Neispravna putanja slike + Invalid video path + Uređivanje slika nije uspjelo + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + Uredi sliku pomoću: + Edit video with: + Nije pronađen uređivač slika + No video editor found + Nepoznata lokacija datoteke + Nije moguće presnimiti izvornu datoteku + Rotiraj ulijevo + Rotiraj udesno + Rotiraj za 180º + Okreni + Okreni horizontalno + Okreni vertikalno + Slobodan odabir + Ostalo + + + Jednostavna pozadina + Postavi kao pozadinu + Postavljanje pozadine nije uspjelo + Postavi kao pozadinu pomoću: + Postavljanje pozadine… + Postavljanje pozadine uspješno + Portretni omjer slike + Pejzažni omjer slike + Početni zaslon + Zaključani zaslon + Početni i zaključani zaslon + + + Dijaprojekcija + Interval (sekunde): + Dodaj slike + Dodaj videe + Dodaj GIF-ove + Nasumični redoslijed + Pomakni unatrag + Prikaži dijaprojekciju kao petlju + Animation + None + Fade + Slide + Kraj dijaprojekcije + Nema datoteka za dijaprojekciju + + + Group direct subfolders + + + Grupiraj po + Nemoj grupirati ove datoteke + Mapa + Zadnje uređivano + Last modified (daily) + Last modified (monthly) + Datum snimanja + Date taken (daily) + Date taken (monthly) + Tip datoteke + Vrsta datoteke + Please note that grouping and sorting are 2 independent fields + + + Mapa prikazana na widgetu: + Prikaži naziv mape + + + Automatsko pokretanje videa + Zapamti poziciju zadnjeg reproduciranog videozapisa + Uključi prikaz naziva datoteka + Ponavljanje videa + Prikaz animacije GIF-ova na sličicama + Maksimalna svjetlina pri pregledu datoteka + Izreži sličice u kvadrate + Prikaži trajanje videozapisa + Rotiraj datoteku u punom zaslonu za + Postavke sustava + Rotacija uređaja + Omjer slike + Crna pozadina i statusna traka na zaslonu na cijelom zaslonu + Listaj sličice horizontalno + Automatski sakrij sučelje sustava na medijima na cijelom zaslonu + Izbrišite prazne mape nakon brisanja njihovog sadržaja + Omogućite kontrolu svjetline fotografije vertikalnim pokretima + Omogući kontrolu glasnoće videa i svjetline pomoću vertikalnih pokreta + Prikaz broja medija mapa u glavnom prikazu + Prikaži proširene pojedinosti preko medija na cijelom zaslonu + Upravljaj proširenim pojedinostima + Omogući zumiranje jednim prstom na mediju cijelog zaslona + Dopusti trenutačno mijenjanje medija dodirom na stranice zaslona + Omogućite duboko zumiranje slika + Sakrij proširene pojedinosti kada je traka statusa skrivena + Prikaži neke gumbe za radnju pri dnu zaslona + Prikažite koš za smeće na zaslonu mapa + Duboko zumirane slike + Prikaži slike u najvišoj mogućoj kvaliteti + Prikaži koš za smeće kao posljednju stavku na glavnom zaslonu + Omogućite zatvaranje prikaza preko cijelog zaslona pokretom prema dolje + Dopusti zumiranje 1: 1 s dva dvostruka dodira + Always open videos on a separate screen with new horizontal gestures + Show a notch if available + Allow rotating images with gestures + File loading priority + Speed + Compromise + Avoid showing invalid files + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Sličice + Mediji na cijelom zaslonu + Proširene pojedinosti + Radnju pri dnu zaslona + + + Upravljaj radnjama pri dnu zaslona + Uključi/isključi favorite + Uključi/isključi vidljivost datoteka + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + Kako mogu postaviti Jednostavnu galeriju kao zadanu galeriju uređaja? + Prvo morate pronaći trenutačno zadanu galeriju u odjeljku Aplikacije, u Postavkama uređaja, potražite gumb koji kaže nešto poput opcije \"Otvori prema zadanim postavkama\", kliknite na tu opciju, a zatim odaberite \"Izbriši zadane postavke\". + Sljedeći put kada pokušate otvoriti sliku ili video, trebali biste vidjeti alat za odabir aplikacija, gdje možete odabrati Jednostavnu galeriju i postaviti je kao zadanu aplikaciju. + Zaključao sam aplikaciju zaporkom, ali zaboravio sam. Što mogu učiniti? + Može se riješiti na dva načina. Možete ponovno instalirati aplikaciju ili pronaći aplikaciju u postavkama uređaja i odabrati "\Izbriši podatke\". Navedeno će resetirati sve postavke, ali neće ukloniti nikakve medijske datoteke. + Kako postići da je album uvijek na vrhu? + Dugo pritisnute željeni album i odaberite ikonu igle na akcijskom izborniku, koji će ga pričvrstiti na vrh. Možete prikvačiti više mapa odjednom, prikvačene stavke će biti razvrstane prema zadanom načinu razvrstavanja. + Kako mogu ubrzati video? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Koja je razlika između skrivanja i izuzimanja mape? + Izuzimanje sprječava prikaz mape samo u Jednostavnoj galeriji, a skrivanje radi na razini sustava i skriva mapu iz drugih galerija. Djeluje stvaranjem praznih \".nomedia\" datoteka u zadanoj mapi, koju možete ukloniti pomoću bilo kojeg upraviteljem datoteka. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Zašto se prikazuju mape s naslovnicama albuma i minijaturama slika? + Može se dogoditi da vidite neke neobične albume. Možete ih jednostavno izuzeti tako da ih dugo pritisnete i odaberete Izuzmi. U sljedećem dijaloškom okviru možete odabrati glavnu mapu, čime će te spriječiti prikazivanje ostalih povezanih albuma. + Mapa s fotografijama se ne prikazuje, što mogu učiniti? + To može imati više razloga, ali rješenje je jednostavno. Samo idite u Postavke -> Upravljanje uključenim mapama, odaberite ikonu Plus i prijeđite na traženu mapu. + Što ako želim vidjeti samo nekoliko određenih mapa? + Dodavanje direktorija u uključene mape ne izuzima ništa automatski. Da biste to učinili, idite na "Postavke", a zatim "Upravljanje izuzetim mapama", izuzmite korijenski direktorij \"/\", a zatim dodajte željene direktorije u "Postavke" i "Upravljanje uključenim mapama". + Samo će odabrane mape biti vidljive, jer su izuzimanja i uključivanja rekurzivni, a ako je mapa isključena i uključena, bit će prikazana. + Mogu li izrezati slike pomoću ove aplikacije? + Da, možete obrezati slike u uređivaču povlačenjem uglova. Možete doći do uređivača dugim pritiskom na minijaturu slike i odabirom Uređivanje ili odabirom Uredi iz prikaza preko cijelog zaslona. + Mogu li nekako grupirati sličice medijskih datoteka? + Naravno, koristeći stavku izbornika \"Grupiraj po\" u prikazu sličica. Možete grupirati datoteke prema više kriterija, uključujući datum snimanja. Ako koristite funkciju "Prikaži sve sadržaje mape", možete ih grupirati i mapama. + Sortiranje po datumu nije točno, kako to mogu ispraviti? + Vjerojatno uzrok tome da su Vaše slike kopirane s drugih mjesta. U tom slučaju trebali biste dugo dodirnuti sličice prikazanih slika, a zatim u izborniku u gornjem desnom kutu, odabrati funkciju \'Ispravi vrijednost datuma snimanja\'. + Vidim nekakvno preklapanje boja na slikama. Kako mogu poboljšati kvalitetu? + Trenutno rješenje za prikazivanje slika dobro funkcionira u velikoj većini slučajeva, ali ako želite još bolju kvalitetu slike, možete omogućiti prikazivanje slika u najvišoj mogućoj kvaliteti u postavkama aplikacije, u odjeljku "Duboko zumirane slike". + Sakrio sam datoteku/mapu. Kako je mogu otkriti? + Možete pritisnuti stavku izbornika "Privremeno prikazati skrivene stavke" na glavnom zaslonu ili uključiti "Prikaži skrivene stavke" u postavkama aplikacije da biste vidjeli skrivenu stavku. Ako je želite otkriti, samo ju dugo pritisnite i odaberite "Otkrij". Mape su skrivene dodavanjem skrivene datoteke ".Nomedia", možete i izbrisati navedenu datoteku. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + Simple Gallery Pro - Photo Manager & Editor + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. + + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 709a41f0d..4e54d4201 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1,116 +1,423 @@ - + Simple Gallery - Gallery - Edit - Open camera - Open with - No valid app found - (hidden) - Pin folder - Unpin folder - Show all folders content - All folders - Switch to folder view - Other folder - Show on map - Unknown location - No application with maps has been found - No Camera app has been found - Increase column count - Reduce column count - Temporarily show hidden - Change cover image - Select photo - Use default + Galéria + Szerkesztés + Kamera megnyitása + (rejtett) + (kizárva) + Mappa kitűzés + Mappa kitűzés megszüntetése + Kitűzés felülre + Mutassa az összes mappa tartalmát + Összes mappa + Váltás mappa nézetre + Egyéb mappa + Mutassa a térképen + Ismeretlen helyszín + Hangerő + Fényesség + Tájolás zárolása + Tájolás feloldása + Tájolás változtatása + Álló mód kényszerítése + Fekvő mód kényszerítése + Alapértelmezett tájolás használata + Dátum javítása + Javítás... + Sikeres dátum javítás + Nem találhatók Dátum értékek + Átméretezett verzió megosztása + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Váltás a fájlkeresésre az összes látható mappában + Set as default folder + Unset as default folder + + + Média szűrő + Kép + Videó + GIF + RAW kép + SVG + Portrék + A kiválasztott szűrők nem találtak médiafájlokat. + Szűrők változtatása - This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden folders\' option in Settings. Continue? - Exclude - Excluded folders - Manage excluded folders - This will exclude the selection together with its subfolders from Simple Gallery only. You can manage excluded folders in Settings. - Exclude a parent instead? - Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\\n\\nIf you want to hide them from other apps too, use the Hide function. - Remove all - Remove all folders from the list of excluded? This will not delete the folders. + Ez a funkció elrejti a mappát egy \'.nomedia\' fájl hozzáadásával, és elrejti az almappákat is. Láthatóvá teheti ezeket a Beállítások \"Mutassa a rejtett elemeket\" menüpontban. Folytatja? + Kizárás + Kizárt mappák + Kizárt mappák kezelése + Ez kizárja a kijelölést és az alkönyvtárakat a Simple Gallery alkalmazásból. A kizárt mappákat a Beállításokban kezelheti. + Kizárja a szülő mappát? + A mappák kizárásával az almappákkal együtt elrejti a Simple Gallery alkalmazásban, de továbbra is láthatóak maradnak más alkalmazásokban.\n\nHa el szeretné rejteni őket más alkalmazásokban is, használja az Elrejtés funkciót. + Összes eltávolítása + Összes mappa eltávolítása a Kizárás listából. Ez nem törli a mappákat. + Rejtett mappák + Rejtett mappák kezelése + Úgy tűnik, a mappái nincsenek elrejtve egy \".nomedia\" fájllal. - Included folders - Manage included folders - Add folder - If you have some folders which contain media, but were not recognized by the app, you can add them manually here. + Befoglalt mappák + Befoglalt mappák kezelése + Mappa hozzáadása + Ha vannak olyan mappák, amelyek média fájlokat tartalmaznak, de az alkalmazás nem ismerte fel, akkor kézzel is hozzáadhatja ezeket.\n\nAz elemek hozzáadása nem zár ki más mappákat. + Nem talált médiafájlokat. Ezt megoldhatja a médiafájlokat tartalmazó mappák manuális hozzáadásával. - Resize - Resize selection and save - Width - Height - Keep aspect ratio - Please enter a valid resolution + Átméretezés + Kiválasztás átméretezése és mentés + Szélesség + Magasság + Képarány megtartása + Írjon be érvényes felbontást - Editor - Save - Rotate - Path - Invalid image path - Image editing failed - Edit image with: - No image editor found - Unknown file location - Could not overwrite the source file - Rotate left - Rotate right - Rotate by 180º - Flip - Flip horizontally - Flip vertically - Out of memory error + Szerkesztő + Forgatás + Érvénytelen kép elérési útvonal + Invalid video path + Sikertelen kép szerkesztés + Video editing failed + A képszerkesztés megszakítva + Video editing cancelled + A fájl szerkesztése sikerült + Image edited successfully + Video edited successfully + Kép szerkesztés ezzel: + Edit video with: + Nem található kép szerkesztő + No video editor found + Ismeretlen fájl hely + Nem lehet felülírni a forrás fájlt + Forgatás balra + Forgatás jobbra + 180º-os forgatás + Tükrözés + Tükrözés vízszintesen + Tükrözés függőlegesen + Szabad + Egyéb Simple Wallpaper - Set as Wallpaper - Setting as Wallpaper failed - Set as wallpaper with: - No app capable of it has been found - Setting wallpaper… - Wallpaper set successfully - Portrait aspect ratio - Landscape aspect ratio + Beállítás háttérképként + Nem sikerült a beállítás háttérképként + Beállítás háttérképként ezzel: + Beállítás háttérképként... + Sikeresen beállítva háttérképnek + Álló képarány + Fekvő képarány + Kezdő képernyő + Zárolás képernyő + Kezdő és zárolás képernyő + + + Diavetítés + Időköz (másodperc): + Fotók befoglalása + Videók befoglalása + GIF befoglalása + Véletlen sorrend + Áthelyezés hátra + Diavetítés ismétlése + Animáció + Nincs + Áttűnés + Csúsztatás + A diavetítés vége + A diavetítéshez nem található média + + + Közvetlen almappa csoport + + + Csoportosítás + Nincs csoportosítás + Mappa + Utolsó módosítás + Utoljára módosítva (naponta) + Utoljára módosítva (havonta) + Dátum + Felvétel dátuma (naponta) + Felvétel dátuma (havonta) + Fájl típus + Kiterjesztés + Kérjük, vegye figyelembe, hogy a csoportosítás és a rendezés 2 egymástól független mező + + + Mappa mutatása a widgeten: + Mutassa a mappa nevét - Show hidden media - Play videos automatically - Toggle filename visibility - Show media - Images only - Videos only - Gifs only - Images, videos, gifs - Images and videos - Loop videos - Animate gifs at thumbnails - Max brightness when viewing media - Crop thumbnails into squares - Rotate fullscreen media by - System setting - Device rotation - Aspect ratio - Dark background at fullscreen media - Scroll thumbnails horizontally + Automatikus videó lejátszás + Emlékezzen a videó utolsó lejátszási pozícióra + Fájlnév láthatóság módosítása + Videók ismétlése + Animált GIF miniatűr + Maximális fényerő a teljes képernyős médiánál + Miniatűrök négyzet alakúra vágva + Mutassa a videó időtartamát + Teljes képernyős média forgatása + Rendszer beállítások + Eszköz elforgatás + Képarány + Fekete háttérszín teljes képernyős médiánál + Miniatűrök görgetése vízszintesen + Automatikusan elrejti a rendszer UI-t teljes képernyőn + Az üres mappák törlése a tartalom törlése után + Engedélyezi a kép fényerő módosítást függőleges gesztusokkal + Engedélyezi a videó hangerő és fényerő módosítást függőleges gesztusokkal + Mutassa a fájlok számát a mappákban + Mutassa a kiterjesztett adatokat a teljes képernyős médián keresztül + Bővített részletek kezelése + Engedélyezi az egy ujjas nagyítást a teljes képernyős médiában + Engedélyezi a azonnali média váltást a képernyő oldalára kattintva + Engedélyezi a képek mély nagyítását + Bővített részletek elrejtése az állapotsor rejtett állapotában + Mutassa a művelet gombokat a képernyő alján + Mutassa a Lomtárat a mappák képernyőjén + Mély nagyítású képek + Mutassa a képeket a lehető legjobb minőségben + Mutassa a Lomtárat a fő képernyő utolsó elemeként + Engedélyezi a teljes képernyős nézetet a lefelé mozdulattal + Engedélyezi az 1:1 nagyítást két dupla érintéssel + Mindig külön képernyőn nyissa meg a videókat új vízszintes mozdulattal + Notch mutatása, ha elérhető + Engedélyezi a kép forgatását gesztusokkal + Fájl betöltés prioritása + Gyors + Kiegyensúlyozott + Kerülje az érvénytelen fájlok mutatását + Mutassa a kép fájl típusokat + A videók nagyításának engedélyezése dupla koppintással + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Miniatűrök + Teljes képernyős média + Bővített részletek + Gomb műveletek + + + Látható gomb műveletek kezelése + Kedvencek módosítása + Fájl láthatóság módosítása + + + Egyéni + Alaphelyzetbe + Négyzet + Átalakítás + Szűrő + Nincs + Beállít + Árnyékok + Kitettség + Kiemelések + Fényerő + Kontraszt + Színtelítettség + Világosság + Gamma + Feketék + Fehérek + Színhőmérséklet + Élesség + Alaphelyzetbe + Fókusz + Nincs + Sugárirányú + Lineáris + Tükrözött + Gaussian + Szöveg + Szöveg beállítások + Szöveg színe + Betűtípus + Hozzáadás + Szerkesztés + Egyenesített + Betűtípus + Szín + Háttérszín + Igazítás + Előre + Törlés + Saját szöveg + Ecset + Szín + Méret + Keménység + Előre + Törlés + Ecset színe + Szerkesztő + Bezárja a szerkesztőt? + Valóban el akarja dobni a változtatásokat? + Igen + Nem + Mégsem + Elfogad + Mentés + Exportálás… + %s exportálása. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Tükrözés vízszintesen + Tükrözés függőlegesen + Visszavonás + Visszavom + Színválasztó + Áttetsző + Fehér + Szürke + Fekete + Világoskék + Kék + Bíbor + Orchidea + Rózsaszín + Piros + Narancssárga + Arany + Sárga + Oliva + Zöld + Aquamarin + Pipetta szín + Trim + + + Hogyan tudom beállítani a Simple Gallery-t alapértelmezett galériának? + Először meg kell találnia az alapértelmezett galériát az eszköz beállításainak Alkalmazások részében. Keressen egy olyan gombot, amely valami olyasmit, mint az \"Legyen alapértelmezett\", kattintson rá, majd válassza a \"Alapértelmezések törlése\" pontot. +A következő alkalommal, amikor megpróbál megnyitni egy képet vagy videót, megjelenik egy alkalmazásválasztó, ahol kiválaszthatja a Simple Gallery lehetőséget, és beállíthatja alapértelmezett alkalmazásnak. + Zároltam az alkalmazást jelszóval, de elfelejtettem. Mit tehetek? + 2 módon is megoldhatja. Újratelepítheti az alkalmazást, vagy megkeresi az alkalmazást az eszköz beállításai között, és válassza az \"Adatok törlése\" lehetőséget. Minden beállítást visszaállít alapértelmezettre. Ez nem távolítja el a média fájlokat. + Hogyan állíthatok be egy albumot úgy, hogy mindig felül legyen? + Hosszan nyomja meg a kívánt albumot, és válassza ki a Kitűzés ikont a művelet menüben, ami rögzíti felülre. Többféle mappát is kitűzhet, ezeket az elemeket az alapértelmezett rendezési mód szerint rendezi. + Hogyan tudom előre tekerni a videókat? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Mi a különbség a mappa elrejtése és kizárása között? + A Kizárás megakadályozza, hogy a mappát a Simple Gallery megjelenítse, az Elrejtés pedig rendszer szinten működik, és elrejti a mappát más galériákból is. Úgy működik, hogy létrehoz egy üres \". nomedia\" nevű fájlt az adott mappában, amelyet bármikor eltávolíthat bármilyen fájlkezelővel is. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Miért jelennek meg a zenei borítóval vagy matricával rendelkező mappák? + Lehet, hogy látni fog néhány szokatlan album megjelenést. Könnyen kizárhatja a hosszú megnyomással és a Kizárás kiválasztásával. A következő párbeszédablakban kiválaszthatja a szülő mappát, és valószínűleg megakadályozza, hogy a többi kapcsolódó album is megjelenjen. + A képekkel nem rendelkező mappa nem jelenik meg, vagy nem jeleníti meg az összes elemet. Mit tehetek? + Ennek több oka lehet, de megoldása egyszerű. Menjen a Beállítások -> Befoglalt mappák kezelése lehetőségre, válassza a plusz jelet, és keresse meg a kívánt mappát. + Mi van, ha csak néhány különleges mappát szeretnék látni? + A Befoglalt mappákhoz tartozó mappák hozzáadása nem zár ki automatikusan semmit. Amit tehetünk, menjünk a Beállítások -> Kizárt mappák kezelése, kizárjuk a gyökérmappát \"/ \", utána hozzáadjuk a kívánt mappákat a Beállítások -> Befoglalt mappák kezelése menüpontban. +Ezzel csak a kiválasztott mappák láthatók, mivel a kizárás és a befoglalás rekurzív. Ha egy mappát hozzáadunk mindkettőhöz, akkor megjelenik. + Tudom vágni a képeket ezzel az alkalmazással? + Igen, megvághatja a képeket a szerkesztőben a kép sarkainak húzásával. A szerkesztőhöz eljuthat egy miniatűr kép hosszú megnyomásával és a Szerkesztés választásával, vagy a Szerkesztés választásával a teljes képernyős nézetben. + Valamilyen módon össze tudom csoportosítani a médiafájl bélyegképeit? + Persze, a miniatűr nézetben használja a \"Csoport\" menüpontot. A fájlokat többféle kritérium alapján csoportosíthatja, beleértve a dátumot is. Ha a \"Mutassa az összes mappa tartalmát\" funkciót választja, akkor mappákba is csoportosíthatja ezeket. + A dátum szerinti rendezés nem működik megfelelően, hogyan tudom megjavítani? + Valószínűleg a fájlok másolásából származik. Ezt a fájl bélyegképének kiválasztásával és a \"Dátum javítása\" lehetőség kiválasztásával oldhatja meg. + Néhány színcsíkot látok a képeken. Hogyan javíthatom a minőséget? + Az esetek többségében a kép megjelenítés jelenlegi megoldása jól működik. Ha még jobb képminőséget szeretne, engedélyezheti a \"Mutassa a képeket a lehető legjobb minőségben\" opcióval az alkalmazás beállításaiban, a \"Mély nagyítású képek\" szakaszban. + Elrejtettem egy fájlt/mappát. Hogyan tudom látni? + A rejtett elemek megtekintéséhez nyomja meg a \"Rejtettek ideiglenes mutatása\" elemet a fő képernyőn, vagy válassza a \"Mutassa a rejtett elemeket\" az alkalmazás beállításaiban. Ha meg akarja szüntetni, csak hosszan nyomja meg, és válassza a \"Elrejtés megszüntetés\" lehetőséget. A mappák elrejtése egy rejtett \". nomedia\" fájl hozzáadásával történik. Ezt a fájlt bármelyik fájlkezelővel is törölheti. + Miért használ az alkalmazás ennyi helyet? + Az alkalmazás gyorsítótára akár a 250 MB-ot is meghaladhatja, és ez gyorsabb megjelenítést biztosít. Ha az alkalmazás még több helyet foglal el, a legvalószínűbb oka, hogy a Lomtárban is van elem. Ezek a fájlok is az alkalmazás méretébe számítanak bele. Törölheti a Lomtárat azzal, hogy megnyitja és törli az összes fájlt vagy az alkalmazás beállításait. A Lomtárban lévő minden fájl 30 nap elteltével automatikusan törlődik. + + Simple Gallery Pro - Photo Manager & Editor - A gallery for viewing photos and videos without ads. + Browse your memories without any interruptions with this photo and video gallery - 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 or set as Wallpaper directly from the app. + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. - The Gallery is also offered for third party usage for previewing images / videos, adding attachments at email clients etc. It\'s perfect for everyday usage. + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. - Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- - This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Nézze meg a Simple Tools csomagot itt: + https://www.simplemobiletools.com + + A Simple Gallery Pro önálló weboldala: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + Filter media + Gambar + Video + GIF + Gambar RAW + SVG + Potret + Tidak ditemukan berkas media yang sesuai dengan filter ini. + Ubah filter + + + Fungsi ini menyembunyikan folder dengan menambahkan berkas \'.nomedia\' ke dalamnya, juga akan menyembunyikan semua subfoldernya. Anda bisa melihatnya dengan mengaktifkan opsi \'Tampilkan berkas tersembunyi\' di Pengaturan. Lanjutkan? + Kecualikan + Folder yang dikecualikan + Kelola folder yang dikecualikan + Ini akan mengecualikan folder yang dipilih dan seluruh subfoldernya hanya di Simple Gallery. Anda bisa mengelola folder yang dikecualikan di Pengaturan. + Kecualikan folder induk? + Mengecualikan folder akan menyembunyikan folder tersebut dan seluruh subfoldernya hanya di Simple Gallery, namun masih bisa dilihat di aplikasi lain.\n\nJika anda ingin menyembunyikannya dari aplikasi lain juga, gunakan fitur Sembunyikan. + Buang semua + Buang semua folder dari daftar pengecualian? Ini tidak akan menghapus folder tersebut. + Folder tersembunyi + Kelola folder tersembunyi + Sepertinya tidak ada folder yang tersembunyi dengan berkas \".nomedia\" di dalamnya. + + + Folder yang disertakan + Kelola folder yang disertakan + Tambah folder + Jika ada folder yang berisi berkas media namun tidak dikenali oleh aplikasi ini, anda bisa menambahkannya secara manual.\n\nMenambahkan beberapa item di sini tidak akan mengecualikan folder yang lain. + Tidak ada berkas media yang ditemukan. Anda bisa memperbaikinya dengan menambahkan folder yang berisi berkas media secara manual. + + + Ubah ukuran + Ubah ukuran dan simpan + Lebar + Tinggi + Jaga aspek rasio + Silakan masukkan resolusi yang valid + + + Penyunting + Rotasi + Jalur gambar tidak valid + Invalid video path + Gagal menyunting gambar + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + Sunting gambar dengan: + Edit video with: + Tidak ditemukan aplikasi penyunting gambar + No video editor found + Lokasi berkas tidak diketahui + Tidak bisa mengganti berkas sumber + Putar ke kiri + Putar ke kanan + Putar 180º + Balik + Balik horizontal + Balik vertikal + Bebas + Lainnya + + + Simple Wallpaper + Setel sebagai Wallpaper + Gagal menyetel sebagai Wallpaper + Setel sebagai Wallpaper dengan: + Menyetel wallpaper… + Wallpaper berhasil disetel + Aspek rasio potret + Aspek rasio lanskap + Layar beranda + Layar kunci + Beranda dan layar kunci + + + Slideshow + Interval (detik): + Sertakan foto + Sertakan video + Sertakan GIF + Urutan acak + Mundur + Slideshow tanpa henti + Animasi + Tidak ada + Pudar + Slide + Slideshow berakhir + Tidak ditemukan media untuk slideshow + + + Kelompokkan subfolder langsung + + + Kelompokkan menurut + Jangan kelompokkan berkas + Folder + Terakhir diubah + Terakhir diubah (harian) + Terakhir diubah (bulanan) + Tanggal diambil + Tanggal diambil (harian) + Tanggal diambil (bulanan) + Tipe berkas + Ekstensi + Harap dicatat bahwa mengelompokkan dan mengurutkan adalah 2 hal yang berbeda + + + Folder yang ditampilkan pada widget: + Tampilkan nama folder + + + Putar video secara otomatis + Ingat posisi pemutaran terakhir + Ubah visibilitas nama berkas + Video berulang + Animasi GIF di thumbnail + Kecerahan maksimum saat melihat di layar penuh + Pangkas thumbnail menjadi persegi + Tampilkan durasi video + Rotasi layar penuh menurut + Pengaturan sistem + Rotasi perangkat + Aspek rasio + Latar belakang hitam saat layar penuh + Gulir thumbnail secara horizontal + Otomatis sembunyikan UI sistem saat layar penuh + Hapus folder kosong setelah menghapus isinya + Izinkan mangatur kecerahan foto dengan gestur vertikal + Izinkan mangatur kecerahan dan volume video dengan gestur vertikal + Tampilkan jumlah media di tampilan utama + Tampilkan detail tambahan saat layar penuh + Kelola detail tambahan + Izinkan zum dengan satu jari di layar penuh + Izinkan mengganti media dengan mengklik sisi layar + Izinkan zum gambar lebih dalam + Sembunyikan detail tambahan ketika bilah status disembunyikan + Tampilkan tombol tindakan di layar bagian bawah + Tampilkan Keranjang Sampah di layar folder + Zum gambar mendalam + Tampilkan gambar dalam kualitas tertinggi + Tampilkan Keranjang Sampah sebagai item terakhir di layar utama + Izinkan keluar dari layar penuh dengan menggeser ke bawah + Izinkan zum 1:1 dengan dua kali ketuk + Selalu buka video pada layar terpisah dengan gestur horizontal baru + Tampilkan notch jika tersedia + Izinkan memutar gambar dengan gestur + Prioritas pemuatan berkas + Kecepatan + Kompromi + Hindari menampilkan berkas yang tidak valid + Tampilkan tipe berkas gambar + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Thumbnail + Media layar penuh + Detail tambahan + Tindakan bawah + + + Kelola tombol tindakan bawah + Favorit + Ubah visibilitas berkas + + + Bebas + Reset + Persegi + Ukuran + Filter + Tidak ada + Sesuaikan + Bayangan + Eksposur + Sorotan + Kecerahan + Kontras + Saturasi + Perjelas + Gamma + Hitam + Putih + Suhu + Ketajaman + Reset + Fokus + Tidak ada + Radial + Linear + Cermin + Gaussian + Teks + Opsi Teks + Warna Teks + Font + Tambah + Edit + Luruskan + Font + Warna + Warna BG + Perataan + Ke Depan + Hapus + Ketik teks + Kuas + Warna + Ukuran + Kekerasan + Ke Depan + hapus + Warna Kuas + Editor + Tutup Editor? + Do you really want to discard the changes? + Ya + Tidak + Batal + Terima + Simpan + Mengekspor… + Mengekspor %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Balik H + Balik V + Urungkan + Ulangi + Pemilih Warna + Transparan + Putih + Abu-abu + Hitam + Biru cerah + Biru + Ungu + Orchid + Pink + Merah + Oranye + Emas + Kuning + Olive + Hijau + Aquamarin + Warna pipet + Trim + + + Bagaimana cara menjadikan Simple Gallery sebagai aplikasi galeri default? + Pertama anda harus menemukan galeri default saat ini di bagian Aplikasi di Pengaturan perangkat, cari tombol dengan label seperti \"Buka secara default\", klik, lalu pilih \"Hapus default\". + Lain kali anda mencoba membuka gambar atau video, anda akan diminta memilih aplikasi, dan anda bisa memilih Simple Gallery dan menjadikannya default. + Saya mengunci aplikasi dengan sandi, tapi saya lupa. Apa yang harus dilakukan? + Anda bisa menyelesaikannya dengan 2 cara. Anda bisa pasang ulang aplikasi, atau cari aplikasi ini di Pengaturan perangkat dan pilih \"Hapus data\". Ini akan menyetel ulang semua pengaturan anda, dan tidak akan menghapus berkas media apapun. + Bagaimana agar album selalu muncul paling atas di dalam daftar? + Anda bisa menekan lama album tersebut dan pilih ikon Pin di menu tindakan, hal tersebut akan membuat album tetap berada di bagian paling atas daftar. Anda juga bisa menyematkan beberapa folder, item yang di-pin akan diurutkan berdasarkan metode urutan default. + Bagaimana cara mempercepat laju video? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Apa perbedaan antara menyembunyikan dan mengecualikan folder? + Mengecualikan tidak akan menampilkan folder di Simple Gallery saja, sedangkan Sembunyikan bekerja sesuai aturan sistem dan akan menyembunyikan folder dari aplikasi galeri yang lain. Cara kerjanya dengan membuat berkas \".nomedia\" kosong pada folder yang diinginkan, yang bisa anda hapus juga dengan aplikasi pengelola berkas. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Mengapa folder dengan gambar album musik atau stiker muncul? + Kadang anda melihat beberapa album yang tidak biasa muncul. Anda bisa dengan mudah menyembunyikannya dengan menekan lama dan pilih Kecualikan. Pada dialog berikutnya, anda lalu bisa memilih folder induk, yang akan mencegah album terkait muncul kembali. + Ada folder berisi gambar namun tidak muncul, apa yang harus dilakukan? + Itu bisa disebabkan berbagai alasan, namun solusinya mudah. Pergi ke Pengaturan -> Atur folder yang disertakan, pilih Tambah dan cari folder yang diinginkan. + Bagaimana jika saya hanya ingin beberapa folder saja yang terlihat? + Menambahkan folder di Folder yang Disertakan tidak otomatis mengecualikan folder yang lain. Yang bisa anda lakukan adalah pergi ke Pengaturan -> Atur Folder yang Dikecualikan, lalu kecualikan folder root \"/\", lalu tambahkan folder yang diinginkan di Pengaturan -> Atur Folder yang Disertakan. + Itu akan membuat folder yang dipilih saja yang muncul, dan jika sebuah folder disertakan dan dikecualikan secara bersamaan, folder tersebut akan muncul. + Bisakah saya memangkas gambar dengan aplikasi ini? + Ya, anda bisa melakukannya di Penyunting, dengan menyeret sudut gambar. Anda bisa masuk ke penyunting dengan menekan lama thumbnail gambar dan memilih Sunting, atau pilih Sunting dari tampilan layar penuh. + Bisakah saya mengelompokkan thumbnail berkas media? + Bisa, gunakan menu \"Kelompokkan menurut\" pada tampilan thumbnail. Anda bisa mengelompokkan berkas dengan berbagai kriteria, termasuk Tanggal Diambil. Jika anda menggunakan fungsi \"Tampilkan semua isi folder\", anda juga bisa mengelompokkan berdasarkan foldernya. + Tidak bisa mengurutkan berdasarkan Tanggal Diambil, bagaimana cara memperbaikinya? + Itu umumnya disebabkan karena berkas yang disalin dari tempat lain. Anda bisa memperbaikinya dengan memilih berkas thumbnail dan pilih \"Perbaiki Tanggal Diambil\". + Saya melihat beberapa pita warna pada gambar. Bagaimana saya meningkatkan kualitasnya? + Solusi saat ini untuk menampilkan gambar berfungsi dengan baik dalam sebagian besar kasus, namun jika anda ingin kualitas gambar yang lebih baik, anda bisa mengaktifkan \"Tampilkan gambar dalam kualitas tertinggi\" di pengaturan aplikasi, pada bagian \"Zum gambar mendalam\". + Saya punya berkas/folder tersembunyi. Bagaimana cara memunculkannya? + Anda bisa memilih menu \"Tampilkan sementara berkas tersembunyi\" di layar utama, atau \"Tampilkan berkas tersembunyi\" di pengaturan aplikasi untuk menampilkannya. Jika anda tidak ingin menyembunyikannya, tekan lama dan pilih \"Jangan sembunyikan\". Folder disembunyikan dengan menambahkan berkas \".nomedia\" di dalamnya, anda bisa menghapus berkas tersebut dengan aplikasi pengelola berkas. + Kenapa aplikasi menggunakan sangat banyak ruang kosong? + Cache aplikasi bisa mencapai 250MB, ini untuk menjamin pemuatan gambar yang lebih cepat. Jika aplikasi menggunakan lebih banyak lagi ruang kosong, sangat memungkinkan anda memiliki item di dalam Keranjang Sampah. Berkas tersebut akan menambah ukuran aplikasi. Anda bisa mengosongkan Keranjang sampah dengan cara membukanya dan menghapus semua berkas, atau dari pengaturan aplikasi. Semua berkas di dalam keranjang sampah akan otomatis dihapus setelah 30 hari. + + + + Simple Gallery Pro - Pengelola & Penyunting Foto + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro adalah aplikasi galeri luring yang sangat mudah diubahsuai. Mengelola & menyunting foto anda, memulihkan berkas yang terhapus dari keranjang sampah, melindungi & menyembunyikan berkas, dan menampilkan banyak format foto & video, diantaranya RAW, SVG dan masih banyak lainnya. + + Aplikasi sama sekali tidak berisi iklan dan tidak membutuhkan perizinan yang tidak perlu. Dan karena aplikasi juga tidak membutuhkan akses internet, privasi anda terlindungi. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FITUR + ------------------------------------------------- + + • Galeri luring tanpa iklan dan popup + • Penyunting galeri foto sederhana – pangkas, putar, ubah ukuran, gambar, filter & masih banyak lagi + • Tidak membutuhkan akses internet, memberi anda jaminan keamanan dan privasi + • Tidak membutuhkan izin yang tidak diperlukan + • Cepat mencari gambar, video & berkas + • Buka & lihat berbagai macam jenis foto dan video (RAW, SVG, panorama, dll) + • Gestur untuk mempermudah penyuntingan & mengelola berkas + • Banyak cara untuk menambahkan filter, mengelompokkan & mengurutkan berkas + • Sesuaikan tampilan Simple Gallery Pro + • Tersedia dalam 32 bahasa + • Tandai berkas sebagai favorit untuk akses cepat + • Lindungi foto & video anda dengan pola, pin atau sidik jari + • Gunakan pin, pattern & sidik jari untuk mencegah aplikasi dibuka + • Pulihkan foto & video yang dihapus dari keranjang sampah + • Ubah visibilitas berkas untuk menyembunyikan foto & video + • Buat slideshow khusus dari berkas anda + • Lihat informasi detail berkas (resolusi, EXIF, dll) + • Simple Gallery Pro bersumber terbuka + … dan masih banyak lagi! + + PENYUNTING GALERI FOTO + Simple Gallery Pro mempermudah anda menyunting foto. Pangkas, balik, putar dan ubah ukuran gambar anda. Jika anda merasa kreatif anda bisa menambahkan filter dan menggambar pada foto anda! + + MENDUKUNG BANYAK TIPE BERKAS + Tidak seperti penampil galeri & pengelola foto lainnya, Simple Gallery Pro mendukung sangat banyak tipe berkas yang berbeda diantaranya JPEG, PNG, MP4, MKV, RAW, SVG, foto & video Panorama, dan masih banyak lagi. + + PENGELOLA GALERI YANG MUDAH DISESUAIKAN + Dari UI sampai tombol tindakan pada bilah perkakas bawah, Simple Gallery Pro sangat mudah disesuaikan dengan keinginan anda. Tidak ada galeri lainnya yang memiliki kemampuan seperti ini! Karena bersumber terbuka, tersedia juga dalam 32 bahasa! + + PULIHKAN FOTO & VIDEO YANG TERHAPUS + Secara tidak sengaja menghapus foto atau video? Jangan khawatir! Simple Gallery Pro juga menyertakan fitur Keranjang Sampah yang mempermudah anda memulihkan foto & video yang terhapus. + + MELINDUNGI & MENYEMBUNYIKAN FOTO, VIDEO & BERKAS + Menggunakan pin, pola atau pemindai sidik jari perangkat, anda bisa melindungi dan menyembunyikan foto, video & seluruh album. Anda bisa melindungi aplikasinya atau mengunci fungsi-fungsi yang lebih spesifik di dalam aplikasi. Contohnya, anda tidak bisa menghapus berkas tanpa memindai sidik jari, membantu melindungi anda menghapus berkas secara tidak sengaja. + + Lihat semua aplikasi Simple Tools di sini: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml new file mode 100644 index 000000000..3b8a0edd4 --- /dev/null +++ b/app/src/main/res/values-in/strings.xml @@ -0,0 +1,427 @@ + + + Simple Gallery + Galeri + Sunting + Buka kamera + (tersembunyi) + (dikecualikan) + Pin folder + Lepas pin folder + Pin ke atas + Tampilkan semua isi folder + Semua folder + Beralih ke tampilan folder + Folder lainnya + Tampilkan di peta + Lokasi tidak diketahui + Volume + Kecerahan + Kunci orientasi + Buka kunci orientasi + Ubah orientasi + Paksa potret + Paksa lanskap + Gunakan orientasi default + Perbaiki Tanggal Diambil + Memperbaiki… + Tanggal berhasil diperbaiki + No Date Taken values have been found + Bagikan versi yang diubah ukurannya + Hai,\n\nsepertinya anda memperbarui dari aplikasi gratis versi lama. Anda sekarang bisa mencopot versi yang lama, yang ada tombol \'Tingkatkan ke Pro\' di bagian atas pengaturan aplikasi.\n\nHanya item Keranjang sampah yang akan dihapus, item favorit menjadi tak bertanda dan anda juga harus menyetel ulang pengaturan aplikasi.\n\nTerima kasih! + Beralih ke pencarian berkas di semua folder yang terlihat + Set as default folder + Unset as default folder + + + Filter media + Gambar + Video + GIF + Gambar RAW + SVG + Potret + Tidak ditemukan berkas media yang sesuai dengan filter ini. + Ubah filter + + + Fungsi ini menyembunyikan folder dengan menambahkan berkas \'.nomedia\' ke dalamnya, juga akan menyembunyikan semua subfoldernya. Anda bisa melihatnya dengan mengaktifkan opsi \'Tampilkan berkas tersembunyi\' di Pengaturan. Lanjutkan? + Kecualikan + Folder yang dikecualikan + Kelola folder yang dikecualikan + Ini akan mengecualikan folder yang dipilih dan seluruh subfoldernya hanya di Simple Gallery. Anda bisa mengelola folder yang dikecualikan di Pengaturan. + Kecualikan folder induk? + Mengecualikan folder akan menyembunyikan folder tersebut dan seluruh subfoldernya hanya di Simple Gallery, namun masih bisa dilihat di aplikasi lain.\n\nJika anda ingin menyembunyikannya dari aplikasi lain juga, gunakan fitur Sembunyikan. + Buang semua + Buang semua folder dari daftar pengecualian? Ini tidak akan menghapus folder tersebut. + Folder tersembunyi + Kelola folder tersembunyi + Sepertinya tidak ada folder yang tersembunyi dengan berkas \".nomedia\" di dalamnya. + + + Folder yang disertakan + Kelola folder yang disertakan + Tambah folder + Jika ada folder yang berisi berkas media namun tidak dikenali oleh aplikasi ini, anda bisa menambahkannya secara manual.\n\nMenambahkan beberapa item di sini tidak akan mengecualikan folder yang lain. + Tidak ada berkas media yang ditemukan. Anda bisa memperbaikinya dengan menambahkan folder yang berisi berkas media secara manual. + + + Ubah ukuran + Ubah ukuran dan simpan + Lebar + Tinggi + Jaga aspek rasio + Silakan masukkan resolusi yang valid + + + Penyunting + Rotasi + Jalur gambar tidak valid + Invalid video path + Gagal menyunting gambar + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + Sunting gambar dengan: + Edit video with: + Tidak ditemukan aplikasi penyunting gambar + No video editor found + Lokasi berkas tidak diketahui + Tidak bisa mengganti berkas sumber + Putar ke kiri + Putar ke kanan + Putar 180º + Balik + Balik horizontal + Balik vertikal + Bebas + Lainnya + + + Simple Wallpaper + Setel sebagai Wallpaper + Gagal menyetel sebagai Wallpaper + Setel sebagai Wallpaper dengan: + Menyetel wallpaper… + Wallpaper berhasil disetel + Aspek rasio potret + Aspek rasio lanskap + Layar beranda + Layar kunci + Beranda dan layar kunci + + + Slideshow + Interval (detik): + Sertakan foto + Sertakan video + Sertakan GIF + Urutan acak + Mundur + Slideshow tanpa henti + Animasi + Tidak ada + Pudar + Slide + Slideshow berakhir + Tidak ditemukan media untuk slideshow + + + Kelompokkan subfolder langsung + + + Kelompokkan menurut + Jangan kelompokkan berkas + Folder + Terakhir diubah + Terakhir diubah (harian) + Terakhir diubah (bulanan) + Tanggal diambil + Tanggal diambil (harian) + Tanggal diambil (bulanan) + Tipe berkas + Ekstensi + Harap dicatat bahwa mengelompokkan dan mengurutkan adalah 2 hal yang berbeda + + + Folder yang ditampilkan pada widget: + Tampilkan nama folder + + + Putar video secara otomatis + Ingat posisi pemutaran terakhir + Ubah visibilitas nama berkas + Video berulang + Animasi GIF di thumbnail + Kecerahan maksimum saat melihat di layar penuh + Pangkas thumbnail menjadi persegi + Tampilkan durasi video + Rotasi layar penuh menurut + Pengaturan sistem + Rotasi perangkat + Aspek rasio + Latar belakang hitam saat layar penuh + Gulir thumbnail secara horizontal + Otomatis sembunyikan UI sistem saat layar penuh + Hapus folder kosong setelah menghapus isinya + Izinkan mangatur kecerahan foto dengan gestur vertikal + Izinkan mangatur kecerahan dan volume video dengan gestur vertikal + Tampilkan jumlah media di tampilan utama + Tampilkan detail tambahan saat layar penuh + Kelola detail tambahan + Izinkan zum dengan satu jari di layar penuh + Izinkan mengganti media dengan mengklik sisi layar + Izinkan zum gambar lebih dalam + Sembunyikan detail tambahan ketika bilah status disembunyikan + Tampilkan tombol tindakan di layar bagian bawah + Tampilkan Keranjang Sampah di layar folder + Zum gambar mendalam + Tampilkan gambar dalam kualitas tertinggi + Tampilkan Keranjang Sampah sebagai item terakhir di layar utama + Izinkan keluar dari layar penuh dengan menggeser ke bawah + Izinkan zum 1:1 dengan dua kali ketuk + Selalu buka video pada layar terpisah dengan gestur horizontal baru + Tampilkan notch jika tersedia + Izinkan memutar gambar dengan gestur + Prioritas pemuatan berkas + Kecepatan + Kompromi + Hindari menampilkan berkas yang tidak valid + Tampilkan tipe berkas gambar + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Thumbnail + Media layar penuh + Detail tambahan + Tindakan bawah + + + Kelola tombol tindakan bawah + Favorit + Ubah visibilitas berkas + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + Bagaimana cara menjadikan Simple Gallery sebagai aplikasi galeri default? + Pertama anda harus menemukan galeri default saat ini di bagian Aplikasi di Pengaturan perangkat, cari tombol dengan label seperti \"Buka secara default\", klik, lalu pilih \"Hapus default\". + Lain kali anda mencoba membuka gambar atau video, anda akan diminta memilih aplikasi, dan anda bisa memilih Simple Gallery dan menjadikannya default. + Saya mengunci aplikasi dengan sandi, tapi saya lupa. Apa yang harus dilakukan? + Anda bisa menyelesaikannya dengan 2 cara. Anda bisa pasang ulang aplikasi, atau cari aplikasi ini di Pengaturan perangkat dan pilih \"Hapus data\". Ini akan menyetel ulang semua pengaturan anda, dan tidak akan menghapus berkas media apapun. + Bagaimana agar album selalu muncul paling atas di dalam daftar? + Anda bisa menekan lama album tersebut dan pilih ikon Pin di menu tindakan, hal tersebut akan membuat album tetap berada di bagian paling atas daftar. Anda juga bisa menyematkan beberapa folder, item yang di-pin akan diurutkan berdasarkan metode urutan default. + Bagaimana cara mempercepat laju video? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Apa perbedaan antara menyembunyikan dan mengecualikan folder? + Mengecualikan tidak akan menampilkan folder di Simple Gallery saja, sedangkan Sembunyikan bekerja sesuai aturan sistem dan akan menyembunyikan folder dari aplikasi galeri yang lain. Cara kerjanya dengan membuat berkas \".nomedia\" kosong pada folder yang diinginkan, yang bisa anda hapus juga dengan aplikasi pengelola berkas. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Mengapa folder dengan gambar album musik atau stiker muncul? + Kadang anda melihat beberapa album yang tidak biasa muncul. Anda bisa dengan mudah menyembunyikannya dengan menekan lama dan pilih Kecualikan. Pada dialog berikutnya, anda lalu bisa memilih folder induk, yang akan mencegah album terkait muncul kembali. + Ada folder berisi gambar namun tidak muncul, apa yang harus dilakukan? + Itu bisa disebabkan berbagai alasan, namun solusinya mudah. Pergi ke Pengaturan -> Atur folder yang disertakan, pilih Tambah dan cari folder yang diinginkan. + Bagaimana jika saya hanya ingin beberapa folder saja yang terlihat? + Menambahkan folder di Folder yang Disertakan tidak otomatis mengecualikan folder yang lain. Yang bisa anda lakukan adalah pergi ke Pengaturan -> Atur Folder yang Dikecualikan, lalu kecualikan folder root \"/\", lalu tambahkan folder yang diinginkan di Pengaturan -> Atur Folder yang Disertakan. + Itu akan membuat folder yang dipilih saja yang muncul, dan jika sebuah folder disertakan dan dikecualikan secara bersamaan, folder tersebut akan muncul. + Bisakah saya memangkas gambar dengan aplikasi ini? + Ya, anda bisa melakukannya di Penyunting, dengan menyeret sudut gambar. Anda bisa masuk ke penyunting dengan menekan lama thumbnail gambar dan memilih Sunting, atau pilih Sunting dari tampilan layar penuh. + Bisakah saya mengelompokkan thumbnail berkas media? + Bisa, gunakan menu \"Kelompokkan menurut\" pada tampilan thumbnail. Anda bisa mengelompokkan berkas dengan berbagai kriteria, termasuk Tanggal Diambil. Jika anda menggunakan fungsi \"Tampilkan semua isi folder\", anda juga bisa mengelompokkan berdasarkan foldernya. + Tidak bisa mengurutkan berdasarkan Tanggal Diambil, bagaimana cara memperbaikinya? + Itu umumnya disebabkan karena berkas yang disalin dari tempat lain. Anda bisa memperbaikinya dengan memilih berkas thumbnail dan pilih \"Perbaiki Tanggal Diambil\". + Saya melihat beberapa pita warna pada gambar. Bagaimana saya meningkatkan kualitasnya? + Solusi saat ini untuk menampilkan gambar berfungsi dengan baik dalam sebagian besar kasus, namun jika anda ingin kualitas gambar yang lebih baik, anda bisa mengaktifkan \"Tampilkan gambar dalam kualitas tertinggi\" di pengaturan aplikasi, pada bagian \"Zum gambar mendalam\". + Saya punya berkas/folder tersembunyi. Bagaimana cara memunculkannya? + Anda bisa memilih menu \"Tampilkan sementara berkas tersembunyi\" di layar utama, atau \"Tampilkan berkas tersembunyi\" di pengaturan aplikasi untuk menampilkannya. Jika anda tidak ingin menyembunyikannya, tekan lama dan pilih \"Jangan sembunyikan\". Folder disembunyikan dengan menambahkan berkas \".nomedia\" di dalamnya, anda bisa menghapus berkas tersebut dengan aplikasi pengelola berkas. + Kenapa aplikasi menggunakan sangat banyak ruang kosong? + Cache aplikasi bisa mencapai 250MB, ini untuk menjamin pemuatan gambar yang lebih cepat. Jika aplikasi menggunakan lebih banyak lagi ruang kosong, sangat memungkinkan anda memiliki item di dalam Keranjang Sampah. Berkas tersebut akan menambah ukuran aplikasi. Anda bisa mengosongkan Keranjang sampah dengan cara membukanya dan menghapus semua berkas, atau dari pengaturan aplikasi. Semua berkas di dalam keranjang sampah akan otomatis dihapus setelah 30 hari. + + + + Simple Gallery Pro - Pengelola & Penyunting Foto + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro adalah aplikasi galeri luring yang sangat mudah diubahsuai. Mengelola & menyunting foto anda, memulihkan berkas yang terhapus dari keranjang sampah, melindungi & menyembunyikan berkas, dan menampilkan banyak format foto & video, diantaranya RAW, SVG dan masih banyak lainnya. + + Aplikasi sama sekali tidak berisi iklan dan tidak membutuhkan perizinan yang tidak perlu. Dan karena aplikasi juga tidak membutuhkan akses internet, privasi anda terlindungi. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FITUR + ------------------------------------------------- + + • Galeri luring tanpa iklan dan popup + • Penyunting galeri foto sederhana – pangkas, putar, ubah ukuran, gambar, filter & masih banyak lagi + • Tidak membutuhkan akses internet, memberi anda jaminan keamanan dan privasi + • Tidak membutuhkan izin yang tidak diperlukan + • Cepat mencari gambar, video & berkas + • Buka & lihat berbagai macam jenis foto dan video (RAW, SVG, panorama, dll) + • Gestur untuk mempermudah penyuntingan & mengelola berkas + • Banyak cara untuk menambahkan filter, mengelompokkan & mengurutkan berkas + • Sesuaikan tampilan Simple Gallery Pro + • Tersedia dalam 32 bahasa + • Tandai berkas sebagai favorit untuk akses cepat + • Lindungi foto & video anda dengan pola, pin atau sidik jari + • Gunakan pin, pattern & sidik jari untuk mencegah aplikasi dibuka + • Pulihkan foto & video yang dihapus dari keranjang sampah + • Ubah visibilitas berkas untuk menyembunyikan foto & video + • Buat slideshow khusus dari berkas anda + • Lihat informasi detail berkas (resolusi, EXIF, dll) + • Simple Gallery Pro bersumber terbuka + … dan masih banyak lagi! + + PENYUNTING GALERI FOTO + Simple Gallery Pro mempermudah anda menyunting foto. Pangkas, balik, putar dan ubah ukuran gambar anda. Jika anda merasa kreatif anda bisa menambahkan filter dan menggambar pada foto anda! + + MENDUKUNG BANYAK TIPE BERKAS + Tidak seperti penampil galeri & pengelola foto lainnya, Simple Gallery Pro mendukung sangat banyak tipe berkas yang berbeda diantaranya JPEG, PNG, MP4, MKV, RAW, SVG, foto & video Panorama, dan masih banyak lagi. + + PENGELOLA GALERI YANG MUDAH DISESUAIKAN + Dari UI sampai tombol tindakan pada bilah perkakas bawah, Simple Gallery Pro sangat mudah disesuaikan dengan keinginan anda. Tidak ada galeri lainnya yang memiliki kemampuan seperti ini! Karena bersumber terbuka, tersedia juga dalam 32 bahasa! + + PULIHKAN FOTO & VIDEO YANG TERHAPUS + Secara tidak sengaja menghapus foto atau video? Jangan khawatir! Simple Gallery Pro juga menyertakan fitur Keranjang Sampah yang mempermudah anda memulihkan foto & video yang terhapus. + + MELINDUNGI & MENYEMBUNYIKAN FOTO, VIDEO & BERKAS + Menggunakan pin, pola atau pemindai sidik jari perangkat, anda bisa melindungi dan menyembunyikan foto, video & seluruh album. Anda bisa melindungi aplikasinya atau mengunci fungsi-fungsi yang lebih spesifik di dalam aplikasi. Contohnya, anda tidak bisa menghapus berkas tanpa memindai sidik jari, membantu melindungi anda menghapus berkas secara tidak sengaja. + + Lihat semua aplikasi Simple Tools di sini: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 5087d0362..1908edd11 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,45 +1,69 @@ - Simple Gallery + Semplice Galleria Galleria Modifica Apri fotocamera - Apri con - Nessun app valida trovata (nascosta) + (esclusa) Blocca cartella Sblocca cartella + Fissa in alto Mostra tutti i contenuti - Tutti i media + Tutte le cartelle Visualizza a cartelle Altra cartella Mostra sulla mappa Posizione sconosciuta - Nessuna app con mappe trovata - Nessuna app fotocamera trovata - Aumenta numero colonne - Riduci numero colonne - Mostra temporaneamente nascosti - Change cover image - Select photo - Use default + Volume + Luminosità + Blocca orientamento + Sblocca orientamento + Cambia orientamento + Forza verticale + Forza orizzontale + Usa l\'orientamento predefinito + Correggi valore Data acquisizione + Correzione in corso… + Date aggiornate correttamente + Nessun valore trovato per data creazione + Condividi una versione ridimensionata + Hey,\n\nhai aggiornato dalla vecchia versione gratuita. Puoi disinstallare le vecchia versione, che ha un pulsante \'Aggiorna a Pro\' in alto nelle impostazioni.\n\nNon potrai recuperare gli elementi dal cestino, gli elementi marcati come preferiti e dovrai anche reimpostare le impostazioni dell\'app.\n\nGrazie! + Passa alla ricerca file su tutte le cartelle visibili + Set as default folder + Unset as default folder + + + Filtra i file + Immagini + Video + GIF + Immagini RAW + SVG + Ritratti + Nessun file trovato con il filtro selezionato. + Cambia filtro - Questa funzione nasconde la cartella aggiungendo un file \'.nomedia\' all\'interno, nasconderà anche tutte le sottocartelle. Puoi vederle attivando l\'opzione \'Mostra cartelle nascoste\' nelle impostazioni. Continuare? + Questa funzione nasconde la cartella aggiungendo un file \'.nomedia\' all\'interno, nasconderà anche tutte le sottocartelle. Si possono vedere attivando l\'opzione \'Mostra cartelle nascoste\' nelle impostazioni. Continuare? Escludi Cartelle escluse - Gestisci cartelle escluse - Questo escluderà la selezione e le relative sottocartelle solo da Simple Gallery. Puoi gestire le cartelle escluse nelle impostazioni. + Gestisci le cartelle escluse + Questo escluderà la selezione e le relative sottocartelle solo da Semplice Galleria. Si possono gestire le cartelle escluse nelle impostazioni. Vuoi invece escluderne una superiore? - L\'esclusione delle cartelle e delle sottocartelle le renderà nascoste solo in Simple Gallery, saranno ancora visibili in altre applicazioni.\\n\\nSe desideri nasconderle anche nelle altre app, usa la funzione Nascondi. + L\'esclusione delle cartelle e delle sottocartelle le renderà nascoste solo in Semplice Galleria, saranno ancora visibili in altre applicazioni.\n\nSe si desidera nasconderle anche nelle altre app, usa la funzione Nascondi. Rimuovi tutte Rimuovere tutte le cartelle dalla lista delle esclusioni? Ciò non eliminerà le cartelle. + Cartelle nascoste + Gestisci le cartelle nascoste + Non si ha alcuna cartella nascosta con un file \".nomedia\". Includi cartelle - Gestisci cartelle incluse + Gestisci le cartelle incluse Aggiungi cartella - Se hai alcune cartelle che contengono media, ma non sono state riconosciute dall\'app, puoi aggiungerle manualmente qui. + Se si hanno alcune cartelle che contengono media, ma non sono state riconosciute dall\'app, si possono aggiungerle manualmente qui. + Nessun file multimediale trovato. Puoi risolvere aggiungendo a mano le cartelle contenenti i file multimediali. Ridimensiona @@ -51,14 +75,21 @@ Editor - Salva Ruota - Percorso - Percorso immagine non valido - Modifica immagine fallita + Percorso dell\'immagine non valido + Invalid video path + Modifica dell\'immagine fallita + Video editing failed + Modifica immagine annullata + Video editing cancelled + File modificato correttamente + Image edited successfully + Video edited successfully Modifica immagine con: - Editor immagini non trovato - Posizione file sconosciuta + Edit video with: + Editor delle immagini non trovato + No video editor found + Posizione del file sconosciuta Impossibile sovrascrivere il file originale Ruota a sinistra Ruota a destra @@ -66,51 +97,327 @@ Capovolgi Capovolgi orizzontalmente Capovolgi verticalmente - Errore memoria esaurita + Libero + Altro Sfondo semplice Imposta come sfondo Impostazione sfondo non riuscita Imposta come sfondo con: - Non sono disponibili app compatibili Impostazione sfondo… Sfondo impostato correttamente Proporzioni ritratto Proporzioni panorama + Schermata principale + Schermata di blocco + Entrambe le schermate + + + Presentazione + Intervallo (secondi): + Includi foto + Includi video + Includi GIF + Ordine sparso + Scorri al contrario + Ripeti presentazione + Animazione + Nessuna + Dissolvenza + Diapositiva + La presentazione è terminata + Nessun file trovato per la presentazione + + + Raggruppa sottocartelle dirette + + + Raggruppa per + Non raggruppare i file + Cartella + Ultima modifica + Ultima modifica (quotidiano) + Ultima modifica (mensile) + Data creazione + Data creazione (quotidiano) + Data creazione (mensile) + Tipo di file + Estensione + Notare che il raggruppamento e l\'ordinamento sono due campi indipendenti + + + Cartella mostrata nel widget: + Mostra il nome della cartella - Mostra cartelle nascoste - Riproduci video automaticamente + Riproduci automaticamente i video + Ricorda l\'ultimo stato di riproduzione dei video Visibilità nome del file - Mostra tipo di media - Solo immagini - Solo video - Gifs only - Images, videos, gifs - Immagini e video Ripeti i video - Anima le gif in miniatura - Luminosità max durante visualizzazione + Anima le GIF in miniatura + Luminosità max durante la visualizzazione Ritaglia le miniature in quadrati - Ruota a schermo intero per + Mostra la durata del video + Ruota schermo per Impostazione di sistema Rotazione dispositivo Proporzioni - Sfondo scuro a schermo intero - Scroll thumbnails horizontally + Sfondo e barra di stato neri con i file a schermo intero + Scorri le miniature orizzontalmente + Nascondi l\'interfaccia utente di sistema con i file a schermo intero + Elimina cartelle vuote dopo averne eliminato il contenuto + Controlla la luminosità delle foto con gesti verticali + Gestisci il volume e la luminosità dei video con gesti verticali + Mostra numero elementi nella cartella + Mostra informazioni estese su media a schermo intero + Gestisci le informazioni estese + Abilita zoom con un dito su media a schermo intero + Cambia media istantaneamente toccando sui lati dello schermo + Permetti zoom immagini profondo + Nascondi i dettagli estesi quando la barra di stato è nascosta + Mostra alcuni pulsanti azione in fondo allo schermo + Mostra il cestino nella schermata delle cartelle + Immagini ingrandibili a fondo + Mostra le immagini alla massima qualità possibile + Mostra il cestino come ultimo elemento nella schermata principale + Chiudi la visuale a schermo intero con un gesto verso il basso + Consenti l\'ingrandimento 1:1 con due doppi tocchi + Apri sempre i video su uno schermo separato con i nuovi movimenti orizzontali + Mostra un notch se disponibile + Permetti la rotazione delle immagini con i gesti + Priorità di caricamento file + Veloce + Compromesso + Evita di mostrare file non validi + Mostra i tipi di file immagine + Permetti l\'ingrandimento dei video con un doppio tocco + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Anteprime + Media a schermo intero + Dettagli estesi + Azioni inferiori + + + Gestisci la visibilità delle azioni + Attiva / disattiva preferito + Attiva / disattiva la visibilità dei file + + + Libera + Reimposta + Quadrato + Trasformazione + Filtro + Nessuno + Regolazione + Ombre + Esposizione + Luci + Luminosità + Contrasto + Saturazione + Chiarezza + Gamma + Neri + Bianchi + Temperatura + Nitidezza + Ripristina + Fuoco + Nessuno + Radiale + Lineare + Speculare + Gaussiano + Testo + Opzioni testo + Colore testo + Carattere + Aggiungi + Modifica + Raddrizza + Carattere + Colore + Colore sfondo + Allineamento + Porta davanti + Elimina + Il tuo testo + Pennello + Colore + Dimensione + Durezza + Porta davanti + Elimina + Colore pennello + Editor + Chiudere l\'editor? + Do you really want to discard the changes? + + No + Annulla + Accetta + Salva + Esportazione… + Esportazione %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Capovolgi O + Capovolgi V + Annulla + Ripeti + Selettore colore + Trasparente + Bianco + Grigio + Nero + Azzurro + Blu + Viola + Orchidea + Rosa + Rosso + Arancione + Oro + Giallo + Oliva + Verde + Acquamarina + Colore pipettabile + Trim + + + Come posso impostare Semplice Galleria come la galleria predefinita del dispositivo? + Si deve prima trovare l\'attuale galleria predefinita nella sezione App delle impostazioni di sistema, cercare un pulsante che dice qualcosa come \"App predefinite\", cliccarlo e poi selezionare \"Ripristina predefiniti\". + La prossima volta che proverai ad aprire un\'immagine o video si dovrebbe vedere un selettore di app, dove si può scegliere Semplice Galleria e renderla l\'app predefinita. + Ho bloccato l\'app con una password, ma l\'ho dimenticata. Cosa posso fare? + Si può risolvere in 2 modi: reinstallare l\'app, oppure trovare l\'app nelle impostazioni del dispositivo e seleziona \"Cancella dati\". Tutte le impostazioni verranno ripristinate, nessun file verrà rimosso. + Come posso fare apparire un album sempre in cima? + Si può toccare a lungo l\'album desiderato e selezionare l\'icona puntina nel menù azioni, ciò lo fisserà in cima. Si possono anche fissare varie cartelle, gli elementi fissati saranno ordinati dal metodo di ordinamento predefinito. + Come avanzo velocemente nei video? + Puoi farlo con un doppio tocco del lato dello schermo o toccando i testi di durata attuale o massima vicino alla barra. Se attivi l\'apertura di video su uno schermo separato nelle impostazioni, puoi anche usare i gesti orizzontali. + Che differenza c\'è tra nascondere ed escludere una cartella? + Escludere impedisce la visualizzazione della cartella solo in Semplice Galleria, mentre nascondere ha effetto in tutto il sistema e nasconde la cartella anche alle altre gallerie. Funziona creando un file vuoto \".nomedia\" nella cartella in questione, si possono anche rimuovere successivamente con qualsiasi gestore dei file. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Perchè vengono mostrate cartelle con copertine o adesivi di musica? + Può succedere che si vedano apparire alcuni album insoliti. Si possono escluderli facilmente toccandoli a lungo e selezionando Escludi. Nella finestra successiva si possono quindi selezionare la cartella superiore, con la possibilità di impedire la visualizzazione anche di altri album correlati. + Una cartella con immagini non viene mostrata, cosa posso fare? + Può succedere per vari motivi, ma la soluzione è semplice. Vai in Impostazioni -> Gestisci le cartelle incluse, tocca il tasto Più e naviga verso la cartella desiderata. + Che fare se voglio rendere visibili solo poche particolari cartelle? + Aggiungere una cartella nelle Cartelle Incluse non esclude automaticamente nulla. Quello che puoi fare è andare in Impostazioni → Gestisci le cartelle escluse, escludi la cartella root \"/\", poi aggiungi le cartelle desiderate in Impostazioni → Gestisci le cartelle incluse. + Ciò renderà visibili solo le cartelle selezionate, dato che sia l\'esclusione che l\'inclusione sono ricorsive e se una cartella è sia esclusa che inclusa, verrà mostrata. + Posso ritagliare le immagini con questa app? + Sì, si possono ritagliare le immagini nell\'editor, trascinando gli angoli dell\'immagine. Si può accedere all\'editor sia premendo a lungo la miniatura di un\'immagine e selezionando Modifica, oppure selezionando Modifica mentre si vede una foto a schermo intero. + Posso raggruppare in qualche modo le miniature dei file? + Certo, si può usare il menu \"Raggruppa per\" mentre si visualizzano le miniature. Il raggruppamento può avvenire per diversi criteri, incluso la data di creazione. Se si utilizza la funzione \"Mostra tutti i contenuti\" si può anche raggrupparli per cartelle. + L\'ordinamento per data acquisizione sembra non funzionare bene, come posso risolvere? + Probabilmente è causato dai file copiati da altre parti. Si può risolvere selezionando le miniature dei file e scegliendo \"Correggi valore Data acquisizione\". + Vedo curvature di colore nelle immagini. Come posso migliorarne la qualità? + L\'attuale soluzione per visualizzare immagini funziona bene nella maggior parte dei casi, ma se si vuole una qualità ancora maggiore, si può attivare \"Mostra le immagini alla massima qualità possibile\" nelle impostazioni dell\'app, nella sezione \"Immagini ingrandibili a fondo\". + Ho nascosto un file/una cartella. Come posso mostrarlo/a di nuovo? + Si può premere \"Mostra temporaneamente nascosti\" nel menu della schermata principale, oppure attivare \"Mostra gli elementi nascosti\" nelle impostazioni dell\'app per vedere l\'elemento nascosto. Per farla rimanere visibile, premere a lungo e selezionare \"Non nascondere\". Le cartelle vengono nascoste aggiungendo un file nascosto \".nomedia\" all\'interno di esse, si può eliminare il file con qualsiasi gestore di file. + Perchè l\'app occupa così tanto spazio? + La cache dell\'app può occupare massimo 250MB, serve per veloccizare il caricamento delle immagini. Se l\'app sta prendendo sempre più spazio, molto probabilmente è causato dagli elementi nel Cestino. Questi file contano nella dimensione dell\'app. Puoi svuotare il Cestino aprendolo ed eliminando tutti i file, oppure andando nelle impostazioni dell\'app. Ogni file nel Cestino viene eliminato automaticamente dopo 30 giorni. + + Semplice Galleria Pro - gestore di foto & editor - Una galleria per visualizzare foto e video senza pubblicità. + Sfoglia le tue memorie senza interruzioni con questa galleria di foto e video - Un semplice strumento per visualizzare foto e video. Gli elementi possono essere ordinati per data, dimensioni, nome sia ascendente che discendente; le foto possono essere ingrandite. I file sono mostrati in colonne multiple a seconda delle dimensioni dello schermo, puoi modificare il numero di colonne con il tocco. Possono essere rinominate, condivise, eliminate, copiate, spostate. Le immagini possono anche essere ritagliate, ruotate o impostate come sfondo direttamente dall\'app. + Semplice Galleria Pro è una galleria offline altamente personalizzabile. Organizza, modifica le tue foto, recupera file con il cestino, progetti e nascondi file e visualizza una grande varietà di formati foto e video, inclusi RAW, SVG e molti altri. - Simple Gallery è anche offerta per utilizzo di terze parti per anteprime di immagini / video, aggiunta di allegati ai client email, ecc. È perfetta per un uso quotidiano. + L\'app non contiene pubblicità o permessi non necessari. L\'app non richiede l\'accesso a Internet, la tua privacy è protetta. - Non contiene pubblicità o autorizzazioni non necessarie. È completamente opensource, offre colori personalizzabili. + ------------------------------------------------- + SEMPLICE GALLERIA PRO – FUNZIONALITÀ + ------------------------------------------------- - Questa app è solo una piccola parte di una grande serie di altre app. Puoi trovarle tutte su http://www.simplemobiletools.com + • Galleria offline senza pubblicità o popup + • Semplice editor - ritaglia, ruota, ridimensiona, disegna, filtri e altro + • Non è necessario alcun accesso Internet, per la tua privacy e sicurezza + • Non sono necessari permessi + • Cerca velocemente immagini, video e file + • Apri e visualizza diversi tipi di foto e video (RAW, SVG, panoramica etc.) + • Una varietà di gesti intuitivi per modificare e organizzare i file + • Diversi modi per filtrare, raggruppare e ordinare i file + • Personalizza l\'aspetto di Semplice Galleria Pro + • Disponibile in 32 lingue + • Segna file come preferiti per un accesso veloce + • Proteggi le tue foto e video con una sequenza, pin o impronta digitale + • Utilizza un pin, una sequenza o un\'impronta digitale per proteggere l\'app o specifiche funzioni + • Recupera foto e video eliminati dal cestino + • Alterna la visibilità dei file per nascondere foto e video + • Crea una presentazione personalizzabile dei propri file + • Visualizza informazioni dettagliate dei tuoi file (risoluzione, valori EXIF) + • Semplice Galleria Pro è open source + … e molto molto altro! + + EDITOR DI FOTO + Semplice Galleria Pro permette di modificare facilmente le proprie foto al volo. Ritaglia, rovescia e ridimensiona le tue foto. Se ti senti più creativo puoi aggiungere filtri o disegnare sulle tue foto! + + SUPPORTO PER MOLTI TIPI DI FILE + A differenza di altre gallerie e organizzatori di foto, Semplice Galleria Pro supporta una grande gamma di tipi di file differenti: JPEG, PNG, MP4, MKV, RAW, SVG, foto e video panoramici e molto altri. + + GESTORE DELLA GALLERIA ALTAMENTE MODIFICABILE + Semplice Galleria Pro è altamente personalizzabile e funziona come vuoi te, dall\'interfaccia ai pulsanti di funzione sulla barra degli strumenti. Nessun altro gestore della galleria ha questa tipologia di flessibilità! Grazie all\'essere open source, siamo disponibili in 32 linguaggi! + + RECUPERA FOTO E VIDEO ELIMINATI + Hai accidentalmente eliminato foto o video preziosi? Non preoccuparti! Semplice Galleria Pro fornisce un comodo cestino dove puoi recuperare foto e video eliminati. + + PROTEGGI E NASCONDI FOTO VIDEO E FILE + Utilizzando un pin, una sequenza o la propria impronta digitale puoi proteggere e nascondere foto, video e interi album. Puoi proteggere l\'intera app o bloccare specifiche funzionalità dell\'app. Per esempio, non puoi eliminare un file senza una scansione dell\'impronta, aiuta a proteggere i tuoi file da rimozioni accidentali. + + Controlla le altre applicazioni qui: + https://www.simplemobiletools.com + + Sito internet di Semplice Galleria Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + 表示する形式 + 画像 + ビデオ + GIF + RAW + SVG + ポートレイト + 条件に該当するメディアがありません。 + 絞り込み条件を変更 - This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden folders\' option in Settings. Continue? - Exclude - Excluded folders - Manage excluded folders - This will exclude the selection together with its subfolders from Simple Gallery only. You can manage excluded folders in Settings. - Exclude a parent instead? - Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\\n\\nIf you want to hide them from other apps too, use the Hide function. - Remove all - Remove all folders from the list of excluded? This will not delete the folders. + 対象のフォルダに「.nomedia」ファイルを作成し、フォルダを非表示にします。そのフォルダの中にあるすべてのサブフォルダも非表示となります。非表示のフォルダを見るには、「設定」の中にある「非表示のフォルダを表示」で切り替えてください。このフォルダを非表示にしますか? + 除外する + 除外フォルダ + 除外フォルダの管理 + 選択したフォルダとそのサブフォルダを、Simple Galleyの一覧から除外します。除外したフォルダは「設定」で確認できます。 + 親フォルダを選択して除外することもできます。 + フォルダを除外すると、サブフォルダも含めSimple Galleyの一覧から除外します。他のアプリでは引き続き表示されます。\n\n他のアプリでも非表示にしたい場合は、「非表示」機能を使用してください。 + すべて解除 + 除外するフォルダの登録をすべて解除しますか? フォルダ自体は削除されません。 + 非表示フォルダ + 非表示フォルダの管理 + \".nomedia\"で隠されたフォルダはありません。 - Included folders - Manage included folders - Add folder - If you have some folders which contain media, but were not recognized by the app, you can add them manually here. + 追加フォルダ + 追加フォルダの管理 + フォルダを追加 + メディア入りのフォルダがアプリで認識されない場合は手動で追加します。 + メディアファイルが見つかりません。メディアファイルを手動で追加することにより解決できます。 - Resize - Resize selection and save - Width - Height - Keep aspect ratio - Please enter a valid resolution + リサイズ + 選択領域をリサイズして保存 + + 高さ + 縦横比を固定 + 解像度を正しく入力してください - エディター - 保存 + 画像編集 回転 - Path 無効な画像パス + 無効な動画パス 画像の編集に失敗しました + 動画の編集に失敗しました + 画像の編集がキャンセルされました + 動画の編集がキャンセルされました + ファイルの編集に成功しました + 画像の編集に成功しました + 動画の編集に成功しました 画像を編集: - 画像エディターが見つかりません + 動画を編集: + 画像エディターが見つかりません + 動画エディターが見つかりません ファイルの場所が不明です 元のファイルを上書きできません - Rotate left - Rotate right - Rotate by 180º - Flip - Flip horizontally - Flip vertically - Out of memory error + 左に回転 + 右に回転 + 180度回転 + 反転 + 水平方向に反転 + 垂直方向に反転 + 任意 + その他 シンプル壁紙 - 壁紙として設定 - 壁紙としての設定に失敗しました - 壁紙として設定: - 対応できるアプリが見つかりません - 壁紙の設定… + 壁紙に設定 + 壁紙の設定に失敗しました + 壁紙に設定: + 壁紙に設定中… 壁紙を正常に設定しました - Portrait aspect ratio - Landscape aspect ratio + 縦向きの縦横比 + 横向きの縦横比 + ホーム画面 + ロック画面 + ホーム画面とロック画面 + + + スライドショー + 間隔 (秒): + 写真を含める + ビデオを含める + GIFを含める + ランダムな順序 + 逆方向に進む + スライドショーをリピート再生する + アニメーション + なし + フェード + スライド + スライドショーが終了しました + スライドショーに表示するメディアがありません + + + サブフォルダでグループ化 + + + グループ分け + 何もしない + フォルダ + 更新日時 + 更新日時 (毎日) + 更新日時 (毎月) + 撮影日時 + 撮影日時 (毎日) + 撮影日時 (毎月) + ファイル形式 + 拡張子 + グループ化とソートはそれぞれ違ったフィールドであることに注意してください + + + ウィジェットに表示するフォルダ: + フォルダ名を表示 - 非表示フォルダーを表示 - 自動的にビデオを再生 + ビデオを自動再生 + 動画の最後の再生位置を記憶する ファイル名の表示を切り替え - Show media - Images only - Videos only - Gifs only - Images, videos, gifs - Images and videos - Loop videos - Animate gifs at thumbnails - Max brightness when viewing media - Crop thumbnails into squares - Rotate fullscreen media by - System setting - Device rotation - Aspect ratio - Dark background at fullscreen media - Scroll thumbnails horizontally + ビデオを繰り返し再生 + アニメーションGIFを動かす + 再生時には明るさを最大にする + サムネイルを正方形に切り取る + 動画の長さを表示 + フルスクリーン再生の表示切り替え + システム設定に従う + 端末の向きに従う + メディアの縦横比に従う + 全画面表示では背景色を黒にする + サムネイル画面を横方向にスクロール + 全画面ではシステムUIを非表示 + 空になったフォルダは削除する + 垂直の動作で写真の明るさを制御 + 音量と明るさを縦の動作で変更 + フォルダ内のメディア数を表示する + フルスクリーンに詳細を重ねて表示する + 詳細表示を管理する + メディアを指だけでズームする + 端を押してメディアをスライド + 深いズームを許可 + ステータスバーが非表示の時は詳細を隠す + 画面の下にはアクションを表示 + フォルダ画面にごみ箱を表示 + 画像のズームを深くする + 可能な限り高品質で画像を表示 + ごみ箱をメイン画面の最後に表示 + フルスクリーン表示を下にスワイプするジェスチャーで閉じる + ダブルタップして 1:1 ズームする + 常に新しい左右のジェスチャーで動画を開く + 利用可能なノッチを表示 + ジェスチャーで画像を回転する + ファイルの優先ローディング + スピード + コンプロマイズ + 無効なファイルを非表示 + 画像ファイルの種類を表示 + ダブルタップで動画ズームを設定 + フォルダサムネイルのスタイル + File thumbnail style + Thumbnail spacing + ファイル数を別の行に表示 + ファイル数を括弧内に表示 + ファイル数を非表示 + 長いフォルダ名を1行に制限 + 四角 + 丸コーナー + + + サムネイル設定 + メディア設定 + 詳細も表示する + 画面下部のアクション + + + 表示するアクションの選択 + お気に入り + 表示/非表示の切替 + + + カスタム + リセット + 正方形 + 変形 + フィルター + なし + 調整 + シャドウ + 露出 + ハイライト + 明るさ + コントラスト + 彩度 + 明瞭度 + ガンマ + 黒レベル + 白レベル + 色温度 + シャープネス + リセット + ぼかし + なし + 円形 + 直線 + ミラー + ガウス + テキスト + テキスト オプション + 文字色 + フォント + 追加 + 編集 + まっすぐにする + フォント + + 背景色 + 配置 + 前面に + 削除 + テキストを入力 + ブラシ + + 太さ + 硬度 + 前面に + 削除 + ブラシの色 + エディター + エディターを閉じますか? + 本当に変更を破棄しますか? + はい + いいえ + キャンセル + 了解 + 保存 + エクスポート中… + エクスポート%s + ステッカー + ステッカーの色 + ステッカーオプション + 追加 + + 削除 + 前面へ + まっすぐにする + 置き換え + 不透明度 + コントラスト + 彩度 + 明るさ + アップロード済 + オーバーレイ + ノーマル + 暗め + 画面 + オーバーレイ + 明るめ + 複写 + 焼き込みカラー + ソフトライト + ハードライト + なし + ゴールデン + 光漏れ 1 + モザイク + ペーパー + + ビンテージ + 水平方向に反転 + 垂直方向に反転 + 元に戻す + やり直し + カラーピッカー + 透明 + ホワイト + グレー + ブラック + ライトブルー + ブルー + パープル + オーキッド + ピンク + レッド + オレンジ + ゴールド + イエロー + オリーブ + グリーン + アクアマリン + ピペッティング可能な色 + トリム + + + どうすればSimpleギャラリーをデバイスのデフォルトギャラリーに出来ますか? + まずデバイスの設定内のアプリセクションで現在のデフォルトのギャラリーを探し、\"デフォルトで開く\"のようなボタンを探してクリックし、そして\"デフォルトを消去\"を選択します。 + 次に画像や動画を開こうとした時に、アプリ選択が表示されるのでSimpleギャラリーを通常のアプリとして選択します。 + アプリをパスワードでロックしましたが、パスワードを忘れてしまいました。どうすればいいですか? + 解決策は2つあります。アプリを再インストールするか、デバイス設定からアプリを見つけて\"データを消去\"を選択するかです。そうすると設定がリセットされますが、メディアファイルは削除されません。 + 常にアルバムが一番上に表示されるようにするにはどうすればいいですか? + アルバムを長押しで選択し、アクションメニューからピンアイコンを選択すれば一番上に固定されます。複数のフォルダをピン留めできます。ピン留めされた項目は、デフォルトのソート方法でソートされます。 + 動画を早送りするにはどうすればよいですか? + 画面の横をダブルタップするか、検索バーの近くにある現在もしくは最大数のテキストをタップすれば可能です。別画面動画を開くことをアプリ設定で有効にしている場合、左右ジェスチャーも使用できます。 + フォルダの非表示と除外の違いは何ですか? + 除外はSimpleギャラリーでのみフォルダを非表示にし、非表示はシステム上で動作するので他のギャラリーからもフォルダを非表示にします。指定されたフォルダに空の\".nomedia\"ファイルを作成することにより動作し、任意のファイルマネージャーで削除することも出来ます。一部のデバイスでは、カメラ、スクリーンショット、ダウンロードのようなフォルダを非表示に出来ないことはご留意ください。 + 音楽のカバーアートやステッカーのフォルダが表示されるのはなぜですか? + 通常ではないアルバムが表示されることがあります。長押しして除外を選択することで簡単に除くことができます。次のダイアログで親フォルダを選択すると、他の関連アルバムも表示されなくなります。 + 画像フォルダが表示されなかったり、全てのアイテムが表示されなかったりします。どうしたらいいですか? + 理由はいくつかありますが、解決は簡単です。設定→含まれているフォルダの管理に移動し、Plusを選択し、必要なフォルダに移動します。 + 特定のフォルダのみ表示にしたい場合はどうすればいいですか? + 含まれているフォルダにフォルダを追加しても、自動的に除外されるわけではありません。設定→除外されたフォルダの管理に移動し、ルートフォルダを除外し、設定→含まれているフォルダの管理で目的のフォルダを追加すれば可能です。 + 除外と包含は両方再帰的なので、フォルダが含まれており除外もされている場合、選択したフォルダのみ表示されるのです。 + このアプリで画像のトリミングは出来ますか? + はい、編集で画像をトリミング出来ます。画像の隅をドラッグしてください。画像のサムネイルを長押しし編集を選択するか、フルスクリーンで編集を選択することにより編集できます。 + どうすればメディアファイルのサムネイルをグループ化できますか? + サムネイル表示中に\"グループ分け\"メニューを使用してください。撮影日を含めた、複数の条件でグループ化することが出来ます。\"すべてを表示\"機能を使えば、フォルダごとにグループ化することも出来ます。 + 撮影日でソートがうまく機能しないようです。どうすれば直せますか? + おそらく、ファイルがコピーされたものであることが原因でしょう。ファイルサムネイルを選択し、\"撮影日の値を修正\"を選択することにより直せます。 + 画像にバンディングが見られます。どうすれば質を変えられますか? + 現在の表示方法は大抵の場合問題は見られませんが、より高画質で表示したい場合はアプリ設定の\"画像のズームを深くする\"セクションで\"可能な限り高品質で画像を表示\"を有効にすると良いでしょう。 + 非表示にしているファイルやフォルダがあります。もう一度表示するにはどうすればいいですか? + メイン画面で\"非表示の項目を一時的に表示\"を押すか、 アプリの設定で\"非表示のアイテムを表示する\"を切り替えると非表示にしている項目を見ることができます。表示したい場合は\"再表示\"を長押ししてください。フォルダは非表示の\".nomedia\"ファイルを追加して非表示にしているので、ファイルマネージャーで削除することもできます。 + アプリが容量を沢山使用するのはなぜですか? + アプリのキャッシュがは画像の素早いローディングのため最大250MB使用します。もしそれ以上にアプリが容量を取るようであれば、ゴミ箱内のアイテムが原因となっているはずです。ゴミ箱内のファイルもアプリ容量として加算されます。ゴミ箱を開くか、アプリの設定から全てのファイルを削除できます。ゴミ箱内のアイテムは30日を過ぎると自動的に削除されます。 + + シンプルギャラリープロ-写真とビデオの管理と編集 - 写真やビデオを見るためのギャラリー。広告はありません。 + お気に入りのフォトアルバム。スマートギャラリーで写真の切り取りや編集。 - 写真やビデオを見るためのシンプルなツール。 日付、サイズ、名前で、昇順または降順にアイテムを並べ替えることができ、写真は拡大表示できます。 メディアファイルは、ディスプレイのサイズに応じて複数の列に表示されます。 名前の変更、共有、削除、コピー、移動が可能です。 画像をトリミング、回転、または壁紙としてアプリから直接設定することもできます。 + シンプルギャラリーは、高度にカスタマイズ可能なオフラインのphoto vaultおよびファイル整理アプリです。ユーザー体験で世界中で何百万人もの人々に愛されています。 フォトギャラリーを使用して、写真の編集、削除した写真の復元、写真の保護と非表示、秘密のフォトアルバムの作成、写真のトリミングを簡単に行います。またRAW、SVG、GIF、パノラマなど、さまざまな写真やビデオ形式を表示することができます。 今すぐフォトギャラリーをダウンロードして、アルバムを好きなように管理してください! - ギャラリーは、画像やビデオのプレビュー、メールクライアントで添付ファイルの追加など、サードパーティの用途にも提供されます。 毎日の使用には完璧です。 + ------------------------------------------------- + シンプルギャラリー-写真および動画の管理と編集 + ------------------------------------------------- - 広告や不要なアクセス許可は含まれていません。 完全にオープンソースで、ダークテーマも提供しています。 + 削除されたビデオを復元し、広告なしで写真をトリミングするオプションを備えた美しいモダンなフォトアルバム - このアプリは、大きな一連のアプリの一つです。 他のアプリは http://www.simplemobiletools.com で見つけることができます + 高度な写真編集–写真のトリミング、写真の編集、画像の検索。 削除されたビデオを復元できる快適で使いやすいphoto vault + + インターネットにアクセスする必要がないため、プライバシー、セキュリティ、安定性が向上-オフラインの秘密の写真アルバム + + 画像をすばやく検索、写真の非表示、編集、削除された写真・ビデオ・GIF・その他のファイルを復元 + + さまざまな種類の写真やビデオ(RAW、SVG、パノラマ、GIFなど)を開いて表示 + + 写真管理で写真を簡単に編集・整理するための使いやすいジェスチャーが多数 + + ファイルをフィルタリング、グループ化、およびソートする多くの機能 + + パターンロック、PIN、指紋を使用して写真とビデオを保護・編集 + + PIN、パターンロック、指紋を使用して、アプリの起動や特定の機能も保護 + + ゴミ箱からビデオを復元 + + photo vaultを使用してファイルの表示の切り替え、写真とビデオの非表示、写真のトリミングが可能 + + フォトギャラリー内の多数のオプションを使用して、ファイルのカスタマイズショーを作成 + + •画像検索、写真編集、高品質は写真・ビデオ・GIFを指を使って簡単にズーム + + ビデオを見やすくするために、水平・垂直方向を簡単に変更 + + ファイル整理-シンプルなモバイルフォトギャラリーでは、画像・ビデオ・GIFの名前変更、コピー/移動、表示/非表示、削除が可能 + + 手間をかけずに画像を壁紙に設定 + + 高度な写真編集 + シンプルなギャラリーファイルの整理とフォトアルバムにより、簡単すぐに写真を編集することができます。 画像の切り抜き、反転、回転、サイズ変更、または必要なフィルターの適用などがお使いいただけます。 + + 様々なファイルタイプをサポート + シンプルギャラリーは、JPEG、PNG、MP4、MKV、RAW、SVG、GIF、パノラマ写真、ビデオなど、さまざまな種類のファイルをサポートしています。 + + カスタマイズ可能なファイル管理 + シンプルギャラリーはUIから下部のツールバーの機能ボタンまで、高度にカスタマイズ可能なファイル整理機能を備えています。 + + 削除された写真と動画の復元 + 大切な写真やビデオをフォトアルバムから誤って削除したことはありませんか?シンプルなモバイルフォトギャラリーには、photo vaultアプリのおかげで削除された写真をすべて復元できる便利なオプションがあります。 + + 写真、動画、ファイルを保護して非表示に + PIN、パターンロック、またはデバイスの指紋認証を使用して、写真の保護と編集、削除されたビデオの復元、写真の切り抜き、画像の検索を行うことができます。 ファイルオーガナイザー自体を保護したり、アプリの特定の機能をロックしたりすることができます。 たとえば、指紋認証をしないとファイルの削除ができません。 + + 広告や不要な権限はありません。 完全にオープンソースで、カラーもカスタマイズ可能。 + + シンプルツールの完全なリストはこちらからご確認ください: + https://www.simplemobiletools.com + + シンプルギャラリープロのwebサイト: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + 필터 설정 + 사진 + 동영상 + GIF + RAW + SVG + 세로 사진 + 설정된 필터와 일치하는 컨텐츠가 존재하지 않습니다. + 필터 변경 + + + 현재 폴더에 \'.nomedia\' 파일을 생성하여 현재 및 모든 하위 폴더를 숨김니다. 숨김 처리된 모든 폴더는 설정의 \'숨김파일 보기\' 메뉴를 이용하여 다시 볼 수 있습니다. 계속 하시겠습니까? + 제외하기 + 제외된 폴더 + 제외된 폴더 관리 + 현재 및 모든 하위 폴더를 심플 갤러리에서 제외시킵니다. 제외된 폴더는 설정에서 관리 할 수 있습니다. + 상위 폴더를 제외 하시겠습니까? + 폴더를 제외하면 심플 갤러리에서만 폴더가 보이지 않게 됩니다. \n\n다른 앱에서도 보이지 않게 하려면 숨김기능을 사용하십시오. + 모두 제거 + 제외된 폴더를 모두 해제하시겠습니까? + 숨겨진 폴더 + 숨겨진 폴더 관리 + \".nomedia\"파일로 숨겨진 폴더가 존재하지 않습니다. + + + 포함된 폴더 + 포함된 폴더 관리 + 폴더 추가 + 앱에서 인식하지 못한 사진이나 동영상이 있는 경우 이곳에서 수동으로 추가 할 수 있습니다. \n\n이 작업은 다른 폴더에 영향을 미치지 않습니다. + 사진이나 동영상이 발견되지 않았습니다. 이는 파일을 포함하고 있는 폴더를 수동으로 추가하여 해결할 수 있습니다. + + + 크기 변경 + 크기 변경 및 저장 + 가로 + 세로 + 비율 유지 + 잘못된 비율입니다. + + + 편집 + 회전 + 유효하지 않은 사진 경로 + Invalid video path + 사진 편집 실패 + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + 사진 편집 프로그램 연결: + Edit video with: + 사진 편집 프로그램 없음 + No video editor found + 알 수 없는 파일 위치 + 원본 파일 덮어쓰기 실패 + 왼쪽으로 회전 + 오른쪽으로 회전 + 180도 회전 + 뒤집기 + 가로 뒤집기 + 세로 뒤집기 + 자유 + 기타 + + + 심플 배경화면 + 배경화면으로 설정 + 배경화면 설정 실패 + 배경화면으로 설정하기: + 배경화면 설정 중… + 배경화면이 성공적으로 설정되었습니다. + 세로보기 비율 + 가로보기 비율 + 홈 화면 + 잠금화면 + 홈 화면과 잠금화면 + + + 슬라이드 쇼 + 간격 (초): + 포함된 사진 + 포함된 동영상 + 포함된 GIF + 순서 랜덤 + 뒤로 이동 + 슬라이드 쇼 반복 + 전환 효과 + 없음 + 페이드 + 슬라이드 + 슬라이드 쇼 종료 + 슬라이드 쇼를 표시할 사진이 없습니다. + + + 상위 폴더 표시 + + + 그룹 방식 + 없음 + 폴더 + 마지막 변경 날짜 + 마지막 변경 날짜 (일간) + 마지막 변경 날짜 (월간) + 찍은 날짜 + 찍은 날짜 (일간) + 찍은 날짜 (월간) + 파일 종류 + 파일 확장자 + 그룹 방식과 정렬 방식은 다른 항목입니다. + + + 위젯에 표시된 폴더: + 폴더 이름 보이기 + + + 동영상 자동재생 + 마지막 재생 위치 기억 + 파일 이름 보기 + 동영상 반복 + 섬네일에서 GIF 애니메이션 활성화 + 전체화면 시 최대 밝기로 보기 + 섬네일을 사각형으로 자르기 + 동영상 길이 표시 + 전체화면 회전 기준 + 시스템 설정 + 화면 회전 + 사진 비율 + 전체화면 모드에서 검정 배경 사용 + 섬네일 수평 스크롤 + 전체화면 모드에서 시스템 UI 숨김 + 파일 삭제 후 빈 폴더도 삭제 + 상하 제스처로 사진 밝기 제어 + 수직 제스처로 동영상 음량 및 밝기 제어 + 폴더 파일 수 표시 + 전체화면 모드에서 세부정보 표시 + 표시할 세부정보 편집 + 전체화면 모드에서 한 손가락으로 확대 및 축소 + 측면 터치로 사진 이동 + 확대 범위 확장 + 상태 표시줄 숨김 시 세부정보 같이 숨기기 + 화면 하단에 UI 표시 + 첫화면에 휴지통 표시 + 큰 사진 + 가능한 최고 화질로 사진 표시 + 첫화면에서 휴지통을 마지막 항목으로 표시 + 아래로 스와이프하여 전체화면 나가기 + 두번 탭하여 1:1 크기로 보기 + 동영상을 따로 열어 새 수평 제스처 사용 + 가능한 경우 노치 표시 + 제스처로 사진 회전 + 파일 로딩 우선권 + 속도 + 균형 + 잘못된 파일 보이지 않기 + 사진 파일 종류 표시 + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + 섬네일 + 전체화면 + 추가 세부정보 + 하단 UI 설정 + + + 하단 UI 편집 + 즐겨찾기 + 파일 제외 + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + How can I make Simple Gallery the default device gallery? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + Simple Gallery Pro - Photo Manager & Editor + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. + + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml new file mode 100644 index 000000000..2ce6815d9 --- /dev/null +++ b/app/src/main/res/values-lt/strings.xml @@ -0,0 +1,427 @@ + + + Paprasta Galerija + Galerija + Redaguoti + Atidaryti fotoaparatą + (paslėpta) + (excluded) + Prisegti aplanką + Atsegti aplanką + Prisegti į viršų + Rodyti visų aplankų turinį + Visi aplankai + Perjungti į aplanko rodymą + Kitas aplankas + Rodyti žemėlapyje + Nežinoma vieta + Garsas + Ryškumas + Užrakinti orientaciją + Atrakinti orientaciją + Change orientation + Force portrait + Force landscape + Use default orientation + Fix Date Taken value + Fixing… + Dates fixed successfully + No Date Taken values have been found + Share a resized version + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Switch to file search across all visible folders + Set as default folder + Unset as default folder + + + Filtruoti mediją + Atvaizdai + Vaizdo įrašai + GIF\'ai + RAW images + SVGs + Portraits + Su pasirinktais filtrais nerasta medijos bylų. + Pakeisti filtrus + + + Ši funkcija slepia aplanką, pridedant į jį \'. Nomedia \' bylą, jis taip pat slėps visus subaplankus. Galite juos peržiūrėti, perjunkite parinktį "Rodyti paslėptus elementus \" skiltyje Nustatymai. Tęsti? + Išskirti + Išskirti aplankai + Tvarkyti išskirtus aplankus + Tai pašalins pasirinkimą su jo aplankais tik iš "Paprastos galerijos". Galite valdyti išskirtus aplankus skiltyje "Nustatymai". + Vietoj to išskirti tėvinį? + Išskyrus aplankus, jie bus sujungti su jų aplankais paslėpti tik "Paprastoje galerijoje", jie vis tiek bus matomi kitose programose. \ N \ nJei norite paslėpti juos ir iš kitų programų, naudokite funkciją "Slėpti". + Pašalinti visus + Pašalinti visus aplankus iš išskirtųjų sąrašo? Tai neištrins aplankų. + Paslėpti aplankai + Tvarkyti paslėptus aplankus + Atrodo, kad neturite jokių aplankų, paslėptų \". Nomedia \" bylos. + + + Įtraukti aplankai + Tvarkyti įtrauktus aplankus + Įtraukti aplanką + Jei turite tam tikrų aplankų, kuriuose yra medijos , bet kurių neneatpažįsta programėlė, galite juos pridėti rankiniu būdu. \ N \ nPridedant kai kuriuos elementus, neišskirsite jokio kito aplanko. + No media files have been found. You can solve it by adding the folders containing media files manually. + + + Keisti dydį + Pakeisti pasirinkto dydį ir išsaugoti + Plotis + Aukštis + Išlaikyti proporcijas + Prašome įvesti tinkamą raišką + + + Redaktorius + Sukti + Netinkamas atvaizdo kelias + Invalid video path + Atvaizdo redagavimas nepavyko + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + Redaguoti atvaizdą su: + Edit video with: + Nerasta atvaizdų redagavimo programėlės + No video editor found + Nežinoma bylos vieta + Negalima perrašyti šaltinio bylos + Sukti į kairę + Sukti į dešinę + Sukti 180º + Apversti + Apversti horizontaliai + Apversti vertikaliai + Free + Other + + + Paprastas darbalaukio fonas + Nustatyti kaip darbalaukio foną + Nustatyti darbalaukio fono nepavyko + Nustatyti kaip darbalaukio paveikslėlį su: + Nustatomas darbalaukio paveikslėlis… + Darbalaukio foną nustatyti pavyko + Portreto formato santykis + Peizažo formato santykis + Pagrindinis ekranas + Užrakto ekranas + Pagrindinis ir užrakto ekranas + + + Skaidrių demonstracija + Intervalas (sekundėmis): + Įtraukti nuotraukas + Įtraukti vaizdo įrašus + Itraukti GIF\'us + Atsitiktinė tvarka + Sukti atgal + Klipuoti skaidrių demonstraciją + Animation + None + Fade + Slide + Skaidrių demonstracija pasibaigė + Nerasta medijos skaidrių demonstracijai + + + Group direct subfolders + + + Group by + Do not group files + Folder + Last modified + Last modified (daily) + Last modified (monthly) + Date taken + Date taken (daily) + Date taken (monthly) + File type + Extension + Please note that grouping and sorting are 2 independent fields + + + Folder shown on the widget: + Show folder name + + + Groti vaizdo įrašus automatiškai + Remember last video playback position + Perjungti bylos pavadinimo matomumą + Klipuoti vaizdo įrašus + Animuoti GIF\'us miniatiūrose + Maksimalus ryškumas, kai medija peržiūrima viso ekrano rėžimu + Apkirpti miniatiūras kvadratu + Show video durations + Sukti viso ekrano mediją pagal + Sistemos nustatymai + Įrenginio sukimas + Padėties santykis + Juodasis fonas ir būsenos juosta per visą ekraną + Slinkti miniatiūras horizontaliai + Automatiškai slėpti sistemos vartotojo sąsają per visą ekraną + Ištrinti tuščius aplankus po jų turinio ištrynimo + Leisti valdyti nuotraukų ryškumą vertikaliais gestais + Leisti kontroliuoti vaizdo įrašo garsumą ir ryškumą vertikaliais gestais + Rodyti aplanko bylų skaičių pagrindiniame rodinyje + Rodyti išsamią informaciją per visą ekraną + Tvarkykite išsamią informaciją + Leisti vienu pirštu pritraukti viso ekrano rėžime + Leiskite akimirksniu keisti mediją spustelėdami ekrano šonuose + Allow deep zooming images + Slėpti išsamią informaciją, kai būsenos juosta yra paslėpta + Show some action buttons at the bottom of the screen + Show the Recycle Bin at the folders screen + Deep zoomable images + Show images in the highest possible quality + Show the Recycle Bin as the last item on the main screen + Allow closing the fullscreen view with a down gesture + Allow 1:1 zooming in with two double taps + Always open videos on a separate screen with new horizontal gestures + Show a notch if available + Allow rotating images with gestures + File loading priority + Speed + Compromise + Avoid showing invalid files + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Miniatiūros + Viso ekrano medija + Išsami informacija + Bottom actions + + + Manage visible bottom actions + Toggle favorite + Toggle file visibility + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + Kaip galiu padaryti paprastą galeriją kaip numatytąją įrenginio galeriją? + Pirmiausia turite rasti numatytąją galeriją savo prietaiso nustatymų skyriuje "Programėlės", ieškoti mygtuko panašaus į \ "Atidaryti pagal numatytuosius \", spustelėkite jį, tada pasirinkite \ "Išvalyti numatytuosius \". + Kitą kartą, kai bandysite atidaryti atvaizdą ar vaizdo įrašą, turėtumėte pamatyti programos parinkiklį, kuriame galite pasirinkti Paprastą galeriją ir padaryti ją numatytaja. + Aš užrakinau programėlę su slaptažodžiu, bet pamiršau jį. Ką aš galiu padaryti? + Tai galite išspręsti dviem būdais. Galite arba iš naujo įdiegti programėlę, arba rasti programėlę savo įrenginio nustatymuose ir pasirinkti \ "Išvalyti duomenis \". Jis iš naujo nustatys visus jūsų nustatymus, bet nepašalins jokių medijos bylų. + Kaip aš galiu padaryti albumą visada rodomą viršuje? + Galite ilgai paspausti norimą albumą ir pasirinkti "Prisegti" piktogramą, esančią meniu "Veiksmo meniu", viršuje. Galite prisegti kelis aplankus, prisegti elementai bus rūšiuojami pagal numatytąjį rūšiavimo metodą. + Kaip galėčiau greitai prasukti vaizdo įrašus? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Koks skirtumas tarp slėpimo ir išskyrimo iš aplanko? + Išskyrimas neleidžia rodyti aplanko tik paprastoje galerijoje, tuo tarpu slėpimas slepia aplanką iš kitų galerijų. Tai veikia, sukuriant tuščią \ ". Nomedia \" bylą tam tikrame aplanke, kurį vėliau galite pašalinti bet kuria bylų tvarkykle. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Kodėl pasirodo aplankai su muzikos viršeliu ar lipdukais? + Gali atsitikti taip, kad pamatysite keletą neįprastų albumų. Galite lengvai juos pašalinti, ilgai paspaudę juos ir pasirinkdami "Išskirti". Kitame dialoge galite pasirinkti tėvinį aplanką, greičiausiai bus išvengta kitų panašių albumų. + Aplankas su nuotraukomis nerodomas, ką aš galiu padaryti? + Tai gali būti dėl keletos priežasčių, tačiau sprendimas yra lengvas. Tiesiog eikite į Nustatymai -> Tvarkyti įtrauktus aplankus, pasirinkite "Plius" ir eikite į reikiamą aplanką. + Ką daryti, jei noriu matyti tik keletą konkrečių aplankų? + Pridėjus aplanką į "Įtraukti aplankai", automatiškai neįtraukiama nieko. Ką jūs galite padaryti, eikite į Nustatymai -> Tvarkyti išskirtus aplankus, išskirkite šakninį aplanką \ "/ \", tada pridėkite norimus aplankus, esančius Nustatymai -> Tvarkyti įtrauktas aplankas. + Tai leis matyti tik tuos pasirinktus aplankus, nes abu atmetami ir įtraukti yra rekursyvūs, ir jei aplankas yra išskirtas ir įtrauktas, jis bus rodomas. + Ar galiu apkarpyti vaizdus naudojant šią programėlę? + Taip, redaguodami vaizdus, galite juos apkarpyti, vilkdami vaizdo kampus. Galite patekti į redaktorių ilgai paspaudę vaizdo miniatiūrą ir pasirinkę "Redaguoti" arba iš viso ekrano rodinio pasirinkę "Redaguoti". + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + Simple Gallery Pro - Photo Manager & Editor + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. + + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml new file mode 100644 index 000000000..3c3622cf2 --- /dev/null +++ b/app/src/main/res/values-nb/strings.xml @@ -0,0 +1,427 @@ + + + Simple Gallery + Galleri + Rediger + Åpne kamera + (skjult) + (ekskludert) + Fest mappe + Løsne mappe + Fest til toppen + Vis alle mappers innhold + Alle mapper + Skift til mappevisning + Annen mappe + Vis på kart + Ukjent plassering + Volum + Lysstyrke + Lås skjermorientering + Lås opp skjermorientering + Endre orientering + Framtving portrett + Framtving landskap + Bruk standardorientering + Korriger Dato tatt-verdi + Korrigerer… + Datoer er korrigerte + Ingen verdier for Dato tatt er funnet + Del versjon med endret størrelse + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Skift til filsøk i alle synlige mapper + Sett som standardmappe + Ikke lenger sett som standardmappe + + + Filtrer media + Bilder + Videoer + GIF-bilder + RAW-format-bilder + SVG-bilder + Portretter + Ingen media-filer er funnet med de valgte filtrene. + Endre filtere + + + Denne funksjonen skjuler mappen ved å legge til filen \'.nomedia\' i den. Alle undermapper blir også skjult. Du kan se dem ved å aktivere alternativet \'Vis skjulte elementer\' i innstillinger. Fortsette? + Ekskluder + Ekskluderte mapper + Håndter ekskluderte mapper + Dette vil ekskludere valgte sammen med undermapper, bare fra Simple Gallery. Ekskluderte mapper kan håndteres i Innstillinger. + Ekskludere en overordnet isteden? + Ekskludering av mapper vil gjøre dem sammen med deres undermapper, skjulte bare i denne appen. De vil fortsatt være synlige i andre apper.\n\nHvis du vil skjule dem fra andre apper, bruk Skjul-funksjonen. + Fjern alle + Fjerne alle mapper fra listen av ekskluderte? Dette sletter ikke mappene. + Skjulte mapper + Håndter skjulte mapper + Ser ut til at du ikke har skjult noen mapper med en \".nomedia\"-fil. + + + Inkluderte mapper + Håndter inkluderte mapper + Legg til mappe + Hvis du har noen mapper som inneholder media, men ikke ble gjenkjent av appen, kan du legge dem til manuelt her.\n\nÅ legge til noen elementer her, ekskluderer ikke noen annen mappe. + Ingen mediafiler er funnet. Dette kan løses ved å legge til mapper som inneholder mediafiler manuelt. + + + Endre størrelse + Endre størrelse og lagre + Bredde + Høyde + Behold sideforhold + Oppfør en gyldig oppløsning + + + Redigering + Roter + Ugyldig bildebane + Ugyldig videobane + Bilderedigering feilet + Videoredigering feilet + Bilderedigering avbrutt + Videoredigering avbrutt + Fil vellykket redigert + Bilde vellykket redigert + Video vellykket redigert + Rediger bilde med: + Rediger video med: + Ingen bilderedigeringsapp funnet + Ingen videoredigeringsapp funnet + Ukjent filplassering + Kunne ikke overskrive kildefilen + Roter til venstre + Roter til høyre + Roter med 180º + Speilvend + Speilvend horisontalt + Speilvend vertikalt + Fri + Annen + + + Bakgrunnsbilde + Sett som bakgrunnsbilde + Å sette som bakgrunnsbilde feilet + Sett som bakgrunnsbilde med: + Stiller inn bakgrunnsbilde… + Bakgrunnsbilde vellykket stilt inn + Portrett-sideforhold + Landskap-sideforhold + Startskjerm + Låseskjerm + Start- og låseskjerm + + + Lysbildeshow + Intervall (sekunder): + Inkluder bilder + Inkluder videoer + Inkluder GIF-bilder + Tilfeldig rekkefølge + Avspill bakover + Gjenta lysbildeshow + Animasjon + Ingen + Fade + Slide + Lysbildeshowet er slutt + Ingen media for lysbildeshowet er funnet + + + Grupper direkte undermapper + + + Grupper etter + Ikke grupper filer + Mappe + Sist endret + Sist endret (daglig) + Sist endret (månedlig) + Dato tatt + Dato tatt (daglig) + Dato tatt (månedlig) + Filtype + Endelse + Vær oppmerksom på at gruppering og sortering er to uavhengige områder + + + Mappe vist på modulen: + Vis mappenavn + + + Avspill videoer automatisk + Husk siste videoavspillingsposisjon + Vis/skjul filnavn + Gjenta videoer + Animert GIF i minibildevisning + Maks lysstyrke ved mediavisning + Beskjær minibilder i kvadrater + Vis videolengde + Roter media etter + Systeminnstilling + Enhetsrotasjon + Sideforhold + Svart bakgrunn og statuslinje i mediavisningen + Horisontal rulling av minibilder + Skjul automatisk systemlinjer ved mediavisning + Slett tomme mapper etter sletting av deres innhold + Tillat å styre fotolysstyrke med vertikale bevegelser + Tillat å styre videovolum og lysstyrke med vertikale bevegelser + Vis mediaantallet i mapper på hovedvisningen + Vis flere detaljer i mediavisningen + Velg detaljer + Tillat en-finger-zoom i mediavisningen + Tillat å skifte media øyeblikkelig ved å trykke på kanten av skjermen + Tillat dyp bildezooming + Skjul utvidede detaljer når statuslinjen er skjult + Vis noen handlingsknapper nederst på skjermen + Vis papirkurven på mappeskjermen + Dyp zoombare bilder + Vis bilder i høyest mulig kvalitet + Vis papirkurven som siste element på hovedskjermen + Tillat lukking av mediavisningen med en nedoverbevegelse + Tillat å zoome 1:1 med to dobbeltrykk + Åpne alltid videoer på en separat skjerm med nye horisontale bevegelser + Vis flik hvis tilgjengelig + Tillat rotering av bilder med fingerbevegelser + Fillastingsprioritet + Hastighet + Kompromiss + Unngå visning av ugyldige filer + Vis bildefiltyper + Tillat zooming av videoer ved å dobbelttrykke dem + Minibildestil for mapper + File thumbnail style + Thumbnail spacing + Vis antall filer på en separat linje + Vis antall filer i parentes + Ikke vis antall filer + Begrens lange mappetitler til 1 linje + Firkantet + Avrundede hjørner + + + Minibilder + Mediavisning + Utvidede detaljer + Handlinger nederst + + + Behandle nedre handlingsknapper + Favoritt av/på + Filsynlighet av/på + + + Egendefinert + Tilbakestill + Kvadrat + Transformer + Filter + Ingen + Juster + Skygger + Eksponering + Highlights + Lysstyrke + Kontrast + Metning + Klarhet + Gamma + Blacks + Whites + Temperatur + Skarphet + Tilbakestill + Fokus + Ingen + Radial + Lineær + Speilet + Gaussisk + Tekst + Tekstvalg + Tekstfarge + Skrift + Legg til + Rediger + Rette + Skrift + Farge + Bakg.farge + Justering + Til front + Slett + Din tekst + Pensel + Farge + Størrelse + Hardhet + Til front + Slett + Penselfarge + Redigering + Lukke Redigering? + Vil du forkaste endringene? + Ja + Nei + Avbryt + Aksepter + Lagre + Eksporterer… + Eksporterer %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Speilvend H + Speilvend V + Angre + Annuler angre + Fargevelger + Gjennomsiktig + Hvit + Grå + Svart + Lyseblå + Blå + Lilla + Orkide + Rosa + Rød + Orange + Gull + Gul + Oliven + Grønn + Akvamarin + Pipettefarge + Trim + + + How can I make Simple Gallery the default device gallery? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + Simple Gallery Pro - Photo Manager & Editor + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. + + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-ne/strings.xml b/app/src/main/res/values-ne/strings.xml new file mode 100644 index 000000000..26f0cd5a0 --- /dev/null +++ b/app/src/main/res/values-ne/strings.xml @@ -0,0 +1,427 @@ + + + सजिलो ग्यालरी + ग्यालरी + सम्पादन + क्यामेरा खोल्नुहोस + (लुकेको) + (excluded) + फोल्डरलाई पिन गर्नुहोस + फोल्डरलाई पिन गर्नुहोस + सबैभन्दा माथि पिन गर्नुहोस + फोल्डरको सबै बिषयबस्तु देखाउनुहोस + सबै फोल्डरहरु + फोल्डर भ्यू + अन्य फोल्डर + नक्सामा देखाउनुहोस + थाहानभएको ठाउ + भोलुम + उज्यालोपन + Lock orientation + Unlock orientation + Change orientation + Force portrait + Force landscape + Use default orientation + Fix Date Taken value + Fixing… + Dates fixed successfully + No Date Taken values have been found + Share a resized version + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Switch to file search across all visible folders + Set as default folder + Unset as default folder + + + Filter media + तस्बिरहरु + भिडियोहरु + GIFs + RAW ईमेजहरु + SVGs + Portraits + No media files have been found with the selected filters. + Change filters + + + This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden items\' option in Settings. Continue? + Exclude + Excluded folders + Manage excluded folders + This will exclude the selection together with its subfolders from Simple Gallery only. You can manage excluded folders in Settings. + Exclude a parent instead? + Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\n\nIf you want to hide them from other apps too, use the Hide function. + Remove all + Remove all folders from the list of excluded? This will not delete the folders. + Hidden folders + Manage hidden folders + Seems like you don\'t have any folders hidden with a \".nomedia\" file. + + + Included folders + Manage included folders + फोल्डर थप्नुहोस + If you have some folders which contain media, but were not recognized by the app, you can add them manually here.\n\nAdding some items here will not exclude any other folder. + No media files have been found. You can solve it by adding the folders containing media files manually. + + + Resize + Resize selection and save + चाैडाई + उचाई + Keep aspect ratio + Please enter a valid resolution + + + सम्पादक + घुमाउने + Invalid image path + Invalid video path + Image editing failed + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + Edit image with: + Edit video with: + No image editor found + No video editor found + Unknown file location + Could not overwrite the source file + Rotate left + Rotate right + Rotate by 180º + Flip + Flip horizontally + Flip vertically + स्वतन्त्र + अन्य + + + सजिलो वालपेपर + वालपेपरको रुपमा स्थापित गर्नुहोस + Setting as Wallpaper failed + Set as wallpaper with: + Setting wallpaper… + Wallpaper set successfully + Portrait aspect ratio + Landscape aspect ratio + होम स्क्रिन + लक स्क्रिन + होम र लक स्क्रिन + + + स्लाईडसो + Interval (seconds): + Include photos + Include videos + Include GIFs + Random order + Move backwards + Loop slideshow + Animation + केहि होईन + फेड + स्लाईड + स्लाईडसो समाप्त भयो + No media for the slideshow have been found + + + Group direct subfolders + + + Group by + Do not group files + फोल्डर + Last modified + Last modified (daily) + Last modified (monthly) + Date taken + Date taken (daily) + Date taken (monthly) + फाईलको प्रकार + Extension + Please note that grouping and sorting are 2 independent fields + + + Folder shown on the widget: + फोल्डरको नाम देखाउनुहोस + + + भिडियोहरु आफै चल्ने + Remember last video playback position + Toggle filename visibility + भिडियो लुप + Animate GIFs at thumbnails + Max brightness when viewing fullscreen media + Crop thumbnails into squares + भिडियो अबधि देखाउने + Rotate fullscreen media by + सिस्टम सेटिङ + Device rotation + Aspect ratio + Black background at fullscreen media + Scroll thumbnails horizontally + Automatically hide system UI at fullscreen media + Delete empty folders after deleting their content + Allow controlling photo brightness with vertical gestures + Allow controlling video volume and brightness with vertical gestures + Show folder media count on the main view + Show extended details over fullscreen media + Manage extended details + Allow one finger zoom at fullscreen media + Allow instantly changing media by clicking on screen sides + Allow deep zooming images + Hide extended details when status bar is hidden + Show some action buttons at the bottom of the screen + Show the Recycle Bin at the folders screen + Deep zoomable images + Show images in the highest possible quality + Show the Recycle Bin as the last item on the main screen + Allow closing the fullscreen view with a down gesture + Allow 1:1 zooming in with two double taps + Always open videos on a separate screen with new horizontal gestures + Show a notch if available + Allow rotating images with gestures + File loading priority + गति + Compromise + Avoid showing invalid files + तस्बिरको फाईल किसिम देखाउनुहोस + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Thumbnails + Fullscreen media + Extended details + Bottom actions + + + Manage visible bottom actions + Toggle favorite + Toggle file visibility + + + Custom + Reset + Square + Transform + Filter + केहिहोईन + Adjust + Shadows + Exposure + Highlights + उज्यालोपन + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + तापक्रम + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + टेक्स + Text Options + टेक्सको रङ + फन्ट + Add + सम्पादन + Straighten + फन्ट + रङ + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + मेटाउनु + Brush Color + सम्पादक + Close Editor? + Do you really want to discard the changes? + Yes + No + रद्द + स्विकार + बचत + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + पारदर्शी + सेतो + खरानि + कालो + Light blue + निलो + Purple + Orchid + Pink + रातो + सुन्तला + सुन + पहेलो + Olive + हरियो + Aquamarin + Pipettable color + Trim + + + How can I make Simple Gallery the default device gallery? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + Simple Gallery Pro - Photo Manager & Editor + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. + + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml new file mode 100644 index 000000000..b5469e360 --- /dev/null +++ b/app/src/main/res/values-nl/strings.xml @@ -0,0 +1,427 @@ + + + Eenvoudige Galerij + Galerij + Bewerken + Camera + (verborgen) + (uitgesloten) + Map vastzetten + Map losmaken + Bovenaan vastzetten + Alles weergeven + Alle mappen + Mapweergave + Andere map + Op kaart tonen + Locatie onbekend + Volume + Helderheid + Schermoriëntatie vergrendelen + Schermoriëntatie ontgrendelen + Schermoriëntatie wijzigen + Portretmodus forceren + Landschapsmodus forceren + Standaardmodus gebruiken + Datum opname corrigeren + Corrigeren… + Datums zijn gecorrigeerd + Geen opnamedatums gevonden + Verkleinde versie delen + Het lijkt erop dat dit een upgrade is vanaf de oude gratis versie. Deze oude versie, met de knop \"Upgraden naar Pro\" bovenaan de instellingen, kan nu gedeïnstalleerd worden.\n\nDe items in de prullenbak zullen dan wel worden verwijderd, favorieten en instellingen zullen ook opnieuw moeten worden geconfigureerd. + Zoeken in alle zichtbare mappen + Als standaardmap instellen + Standaardmap herstellen + + + Media filteren + Afbeeldingen + Video\'s + GIF-bestanden + RAW-afbeeldingen + SVG-vectorafbeeldingen + Portretten + Er zijn geen bestanden gevonden met de huidige filters. + Filters aanpassen + + + Deze functie verbergt de map door het bestand ’.nomedia’ toe te voegen. Alle submappen zullen ook worden verborgen. Kies ’Verborgen mappen tonen’ in de instellingen om toch verborgen mappen te kunnen inzien. Doorgaan? + Uitsluiten + Uitgesloten mappen + Uitgesloten mappen beheren + De selectie en submappen uitsluiten van deze galerij. Uitgesloten mappen kunnen worden beheerd in de instellingen. + De map met dit item uitsluiten? + Uitsluiten zal mappen en hun submappen verbergen voor deze galerij, maar niet voor andere apps.\n\nAls u de mappen ook in andere apps wilt verbergen, kies dan voor de functie Verbergen. + Alles verwijderen + De lijst van uitgesloten mappen leegmaken. Dit zal de mappen zelf niet verwijderen. + Verborgen mappen + Verborgen mappen beheren + Er zijn geen mappen gevonden die zijn verborgen door bestand \".nomedia\". + + + Toegevoegde mappen + Toegevoegde mappen beheren + Map toevoegen + Als er mappen zijn die wel media bevatten, maar niet door de galerij worden herkend, voeg deze mappen dan hier handmatig toe.\n\nHet hier toevoegen van mappen zal andere mappen niet uitsluiten. + Er zijn geen mediabestanden gevonden. Dit kan worden opgelost door de mappen met mediabestanden handmatig toe te voegen. + + + Grootte aanpassen + Grootte aanpassen en opslaan + Breedte + Hoogte + Beeldverhouding vergrendelen + Voer geldige afmetingen in + + + Bewerken + Draaien + Ongeldig pad naar afbeelding + Ongeldig pad naar video + Fout bij bewerken van afbeelding + Videobewerking mislukt + Afbeelding bewerken is geannuleerd + Videobewerking is geannuleerd + Wijzigingen zijn opgeslagen + Bewerkte afbeelding is opgeslagen + Bewerkte video is opgeslagen + Afbeelding bewerken met: + Video bewerken met: + Geen app voor fotobewerking gevonden + Geen app voor videobewerking gevonden + Onbekende bestandslocatie + Bronbestand kan niet worden overschreven + Linksom draaien + Rechtsom draaien + 180º draaien + Kantelen + Horizontaal kantelen + Verticaal kantelen + Vrij + Anders + + + Achtergrond + Als achtergrond instellen + Achtergrond instellen mislukt + Achtergrond instellen met: + Achtergrond instellen… + Achtergrond is ingesteld + Verhouding in portretmodus + Verhouding in landschapsmodus + Startscherm + Vergrendelscherm + Start- en vergrendelingsscherm + + + Diavoorstelling + Interval (seconden): + Afbeeldingen weergeven + Video\'s weergeven + GIF-bestanden weergeven + Willekeurige volgorde + Omgekeerde volgorde + Voorstelling herhalen + Overgangseffect + Geen + Overvloeien + Verschuiven + De diavoorstelling is beëindigd + Geen media gevonden voor diavoorstelling + + + Directe submappen groeperen + + + Groeperen op + Bestanden niet groeperen + Map + Laatst gewijzigd + Laatst gewijzigd (per dag) + Laatst gewijzigd (per maand) + Datum opname + Datum opname (per dag) + Datum opname (per maand) + Bestandstype + Extensie + Groeperen en sorteren zijn twee aparte opties + + + Map tonen in de widget: + Mapnaam tonen + + + Video\'s automatisch afspelen + Laatste positie in video’s onthouden + Bestandsnamen tonen + Video\'s herhalen + GIF-bestanden afspelen in overzicht + Maximale helderheid in volledig scherm + Miniatuurvoorbeelden bijsnijden + Lengte van video’s tonen + Media in volledig scherm roteren volgens + Systeeminstelling + Oriëntatie van apparaat + Afmetingen van bestand + Zwarte achtergrond en statusbalk bij volledig scherm + Horizontaal scrollen + Statusbalk automatisch verbergen in volledig scherm + Lege mappen verwijderen na leegmaken + Helderheid voor afbeeldingen aanpassen met verticale veeggebaren + Volume en helderheid voor video’s aanpassen met verticale veeggebaren + Aantallen in mappen tonen + Uitgebreide informatie tonen in volledig scherm + Uitgebreide informatie + Met één vinger zoomen in volledig scherm + Direct naar vorige/volgende door op de zijkanten van het scherm te tikken + Verder inzoomen mogelijk maken + Uitgebreide informatie niet tonen als de statusbalk is verborgen + Enkele actieknoppen onder aan het scherm tonen + Prullenbak weergeven in de mapweergave + Afbeeldingen ver inzoomen + Afbeeldingen in de hoogst mogelijke kwaliteit weergeven + Prullenbak als laatste item tonen + Naar beneden vegen om volledig scherm af te sluiten + 1:1 zoomen na 2x dubbelklikken + Video\'s altijd in apart scherm met horizontale veeggebaren openen + Inkeping scherm tonen indien aanwezig + Afbeeldingen met veeggebaren draaien + Prioriteit bij inladen bestanden + Snelheid + Compromis + Ongeldige bestanden verbergen + Bestandstypen tonen + Bij video\'s inzoomen met dubbelklikken + Stijl voor thumbnails mappen + Stijl voor thumbnails bestanden + Afstand tussen thumbnails + Aantal bestanden op aparte regel tonen + Aantal bestanden tussen rechte haken tonen + Aantal bestanden niet tonen + Lange mapnamen tot 1 regel limiteren + Vierkant + Afgeronde hoeken + + + Miniatuurvoorbeelden + Volledig scherm + Uitgebreide informatie + Acties onder aan het scherm + + + Actieknoppen onderaan beheren + Als favoriet instellen + Bestand tonen/verbergen + + + Aangepast + Beginwaarden + Vierkant + Vervormen + Filter + Geen + Aanpassen + Schaduwen + Belichting + Hooglichten + Helderheid + Contrast + Verzadiging + Lokaal contrast + Gamma + Zwarte tinten + Witte tinten + Temperatuur + Scherpte + Beginwaarden + Focus + Geen + Radiaal + Lineair + Gespiegeld + Gaussiaans + Tekst + Tekstopties + Tekstkleur + Lettertype + Toevoegen + Bewerken + Rechttrekken + Lettertype + Kleur + Achtergrondkleur + Uitlijning + Naar voren + Verwijderen + Vul hier tekst in + Penseel + Kleur + Grootte + Hardheid + Naar voren + Verwijderen + Penseelkleur + Bewerken + Afsluiten + Veranderingen niet opslaan? + Ja + Nee + Annuleren + Accepteren + Opslaan + Exporteren… + %s exporteren. + Sticker + Stickerkleur + Stickeropties + Toevoegen + Kleur + Verwijderen + Naar voorgrond + Rechttrekken + Vervangen + Ondoorzichtigheid + Contrast + Verzadiging + Helderheid + Uploads + Overlay + Normaal + Donkerder + Raster + Bedekken + Lichter + Vermenigvuldigen + Kleur doordrukken + Zwak licht + Fel licht + Geen + Goud + Licht lekken 1 + Mozaïek + Papier + Regen + Vintage + Spiegelen H + Spiegelen V + Ongedaan maken + Opnieuw + Kleur kiezen + Transparant + Wit + Grijs + Zwart + Lichtblauw + Blauw + Paars + Orchidee + Roze + Rood + Oranje + Goud + Geel + Olijf + Groen + Aquamarijn + Met pipet kiezen + Titel afkappen + + + Hoe kan ik Eenvoudige Galerij instellen als standaard-app voor foto’s en video’s? + Zoek eerst de huidige standaard-app voor foto’s en video’s in de Android-instellingen (via \"Apps\" of "\Apps en meldingen\"). Klik bij de App-info op \"Standaardwaarden wissen\" (soms onder \"Standaard openen\"). + Bij het openen van een foto of video zal de volgende keer een keuzescherm verschijnen, waarin Eenvoudige Galerij als standaard-app kan worden ingesteld. + Ik heb de app beveiligd met een wachtwoord, maar ben het wachtwoord nu vergeten. Wat kan ik doen? + Deïnstalleer en herinstalleer de app, of ga naar de app in de Android-instellingen (via \"Apps (en meldingen)\" -> \"App-info\") en kies via \"Opslagruimte\" voor \"Gegevens wissen\". Hiermee worden alleen de instellingen van deze app gewist. + Hoe kan ik een map bovenaan vastzetten? + Druk lang op het map en kies vervolgens de punaise in het actiemenu. Als er meerdere mappen zijn vastgezet, zullen deze worden weergeven op basis van de standaardsortering. + Hoe kan ik terug- of vooruitspoelen in video’s? + Dubbelklik op de zijkant van het scherm, of tik op de cijfers die de voortgang of de lengte van de video weergeven om resp. terug of vooruit te springen. Als de instelling om video\'s in een apart scherm te openen is ingeschakeld, dan kunnen ook horizontale veeggebaren worden gebruikt. + Wat is het verschil tussen het verbergen en het uitsluiten van mappen? + Met \"Uitsluiten\" wordt het tonen van de map alleen binnen deze app voorkomen, terwijl \"Verbergen\" de map ook zal verbergen voor andere galerij-apps. Met \"Verbergen\" wordt een bestand genaamd \".nomedia\" in de te verbergen map aangemaakt (het verwijderen van dit bestand uit de map maakt het verbergen ongedaan). Op sommige apparaten is het echter niet mogelijk om specifieke mappen, zoals Camera, Screenshots en Downloads, te verbergen. + Waarom zie ik mappen met stickers of covers van muziekalbums? + Soms worden er wat ongebruikelijke afbeeldingen van andere apps getoond. Deze zijn gemakkelijk uit het overzicht te halen door lang te drukken op de map en vervolgens te kiezen voor \"Uitsluiten\". In het daaropvolgende venster is ook de bovenliggende map te kiezen; dit zou het tonen van soortgelijke ongewenste items kunnen voorkomen. + Een bepaalde map met afbeeldingen wordt niet getoond. Wat kan ik doen? + Dit kan verschillende redenen hebben, maar is eenvoudig op te lossen. Ga naar \"Instellingen\" -> \"Toegevoegde mappen beheren\", klik vervolgens op het plus-symbool en navigeer naar de gewenste map. + Wat als ik slechts een beperkt aantal mappen wil laten zien? + Het toevoegen van mappen aan de lijst bij \"Toegevoegde mappen beheren\" sluit niet automatisch alle andere mappen uit. Sluit daarom de hoofdmap \"/\" uit via \"Instellingen\" -> \"Uitgesloten mappen beheren\" en voeg vervolgens de te tonen mappen toe bij \"Instellingen\" -> \"Toegevoegde mappen beheren\". + Dit zal ervoor zorgen dat alleen de toegevoegde mappen worden getoond (mappen toevoegen of uitsluiten gebeurt recursief, maar een toegevoegde map zal altijd worden getoond). + Kan ik afbeeldingen bijsnijden met deze app? + Ja, dat kan bij \"Bewerken\" door de hoeken te verslepen. Druk lang op een afbeelding in het overzicht of ga naar het volledig scherm en klik vervolgens op \"Bewerken\". + Hoe kan ik mediabestanden groeperen? + Kies het menu-item \"Groeperen op\" om op basis van verschillende criteria te groeperen. Dit kan ook ongeacht de mappenstructuur als voor de functie \"Alles weergeven\" is gekozen. + Sorteren op Datum opname lijkt niet te werken. Hoe kan ik dit oplossen? + Waarschijnlijk zijn de bestanden gekopieerd vanaf een andere locatie. Selecteer de bestanden en kies \"Datum opname corrigeren\". + Ik zie \"color banding\" op de afbeeldingen. Hoe kan ik de kwaliteit verbeteren? + In de meeste gevallen werkt de huidige methode voor het weergeven van afbeeldingen prima, maar met de instelling \"Afbeeldingen in de hoogst mogelijke kwaliteit weergeven\" onder \"Afbeeldingen ver inzoomen\" kan een nog betere kwaliteit worden bewerkstelligd. + Ik heb een bestand of map verborgen. Hoe kan ik dit ongedaan maken? + Kies het menu-item \"Verborgen items tijdelijk tonen\", of schakel de instelling \"Verborgen items tonen\" in om het verborgen item te kunnen zien. Druk vervolgens lang op het item en kies \"Tonen\" om het verbergen ongedaan te maken. Mappen worden verborgen door het bestand \".nomedia\" in de map te plaatsen; dit bestand kan ook handmatig in een andere app worden verwijderd. + Waarom neemt de app zoveel ruimte in beslag? + De cache voor de app kan oplopen tot 250MB; dit garandeert snellere laadtijden van afbeeldingen. Indien de app nog meer ruimte inneemt, komt dat hoogstwaarschijnlijk door de items in de prullenbak. Ook deze bestanden worden meegerekend met de ingenomen ruimte. Verwijder deze items zelf vanuit de prullenbak of vanuit de instellingen van de app. Ieder item in de prullenbak zal na 30 dagen automatisch verwijderd worden. + + + + Eenvoudige Galerij Pro - Foto’s Beheren & Bewerken + + Navigeer met deze galerij zonder afleidingen door al je herinneringen + + Eenvoudige Galerij Pro is een volledig aan te passen offline galerij. Organiseer & bewerk foto’s, herstel verwijderde bestanden met de prullenbakfunctie, beveilig & verberg items en bekijk een enorme hoeveelheid aan foto- & videoformaten, waaronder RAW, SVG en nog veel meer. + + Deze privacyvriendelijke app bevat geen advertenties of onnodige machtigingen (zoals verbinden met het internet). + + ------------------------------------------------- + EENVOUDIGE GALERIJ PRO – FUNCTIES + ------------------------------------------------- + + ✔️ Offline galerij zonder advertenties en pop-ups + ✔️ Fotobewerking: bijsnijden, spiegelen, roteren, grootte aanpassen, tekenen, filters & meer + ✔️ Geen internetverbinding benodigd voor meer privacy & veiligheid + ✔️ Geen onnodige machtigingen vereist + ✔️ Beschikbaar in 32 talen + ✔️ Eenvoudige Galerij Pro is open-source + ✔️ Zoek snel naar afbeeldingen, video’s & bestanden + ✔️ Open & bekijk vele foto- & videoformaten (RAW, SVG, panorama etc.) + ✔️ Gebruik veeggebaren om bestanden snel te bewerken en te organiseren + ✔️ Kies uit een verscheidenheid aan manieren om bestanden te filteren, groeperen en sorteren + ✔️ Pas het uiterlijk van Eenvoudige Galerij Pro aan + ✔️ Markeer bestanden als favoriet om ze snel terug te vinden + ✔️ Beveilig items met een patroon, pincode or vingerafdruk + ✔️ Gebruik pincode, patroon & vingerafdruk om specifieke functies of het starten van de app te beveiligen + ✔️ Herstel verwijderde bestanden uit de prullenbak + ✔️ Toon of verberg foto’s & video’s + ✔️ Creëer diavoorstellingen + ✔️ Bekijk gedetailleerde informatie over de bestanden (resolutie, EXIF-waarden etc.) + … en nog veel meer! + + FOTO’S BEWERKEN + Eenvoudige Galerij Pro maakt het gemakkelijk om direct afbeeldingen te bewerken. Ga aan de slag met bijsnijden, spiegelen, roteren en de grootte aanpassen, of voeg filters toe en teken over de afbeelding heen! + + ONDERSTEUNING VOOR VEEL VERSCHILLENDE BESTANDSFORMATEN + Eenvoudige Galerij Pro ondersteunt, in tegenstelling tot sommige andere galerij-apps, een enorme hoeveelheid aan bestandsformaten, waaronder JPEG, PNG, MP4, MKV, RAW, SVG, Panorama-foto’s & -videos en nog veel meer. + + ALLES IS AAN TE PASSEN + Van de interface tot de knoppen op de werkbalk, Eenvoudige Galerij Pro is geheel aan te passen en kan dan ook werken naar ieders voorkeur. Geen enkele andere galerij-app bevat zulke flexibiliteit! Dankzij het open karakter en de community is de app maar liefst in 32 talen beschikbaar! + + HERSTEL VERWIJDERDE FOTO’S & VIDEO’S + Per ongeluk een foto of video verwijderd? Geen paniek! Eenvoudige Galerij Pro heeft een handige prullenbak waarmee verwijderde bestanden gemakkelijk zijn terug te halen. + + BEVEILIG & VERBERG FOTO’S, VIDEO’S & BESTANDEN + Door middel van een pincode, patroon of een vingerafdruk zijn foto’s, video’s & gehele albums te beveiligen of te verbergen. Ook de gehele app of specifieke functies binnen de galerij zijn zo te beveiligen. Voorbeeld: stel in dat bestanden alleen kunnen worden verwijderd na een vingerafdrukscan. + + Kijk ook eens naar de hele collectie apps van Simple Tools: + https://www.simplemobiletools.com + + Website van Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index e850a9af4..8ee0ef88d 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1,121 +1,425 @@ - Simple Gallery + Prosta Galeria Galeria Edytuj - Aparat - Otwórz w - Nie znaleziono danych aplikacji + Uruchom aplikację aparatu (ukryty) - Pin folder - Wypakuj folder + (wykluczony) + Przypnij folder + Odepnij folder + Przypnij na górze Pokaż wszystko Wszystkie foldery Przełącz na widok folderów Inny folder Pokaż na mapie Nieznana lokalizacja - Brak aplikacji powiązanej z mapami - Brak aplikacji powiązanej z aparatem - Zwiększ liczbę kolumn - Zmniejsz liczbę kolumn - Temporarily show hidden - Change cover image - Select photo - Use default + Głośność + Jasność + Zablokuj orientację ekranu + Odblokuj orientację ekranu + Zmień orientację ekranu + Wymuś pionową + Wymuś poziomą + Używaj systemowej + Napraw datę utworzenia + Naprawiam… + Daty zostały naprawione + Nie znaleziono wartości dat utworzenia + Udostępnij zmienioną wersję + Hej,\n\nwygląda na to że zaktualizowałeś ze starszej, darmowej wersji aplikacji. Możesz ją teraz odinstalować przyciskiej \'Upgrade to Pro\' w ustawieniach.\n\nZostaną jedynie usunięte elementy z Kosza, odznaczone Ulubione i konieczne będzie zresetowanie ustawień aplikacji.\n\nDziękujemy! + Przełącz na przeszukiwanie plików we wszystkich widocznych folderach + Set as default folder + Unset as default folder + + + Filtruj multimedia + Obrazy + Filmy + GIFy + Obrazy RAW + Obrazy SVG + Portrety + Nie znaleziono multimediów zgodnych z zastosowanymi filtrami. + Zmień filtry - Ta funkcja ukrywa folder dodając \'. \' Nomedia plik do niego, można tak ukryć wszystkie podfoldery. Można je zobaczyć poprzez przełączanie \'Pokaż ukryte foldery \' opcję w ustawieniach. Kontyntynuj? + Ta funkcja ukrywa foldery, dodając do nich pusty plik \'.nomedia\'. Aby móc je zobaczyć, należy włączyć opcję \'Pokazuj ukryte foldery\' w ustawieniach. Kontynuować? Wyklucz - Wyklucz foldery + Wykluczone foldery Zarządzaj wykluczonymi folderami - Działa na folderach Galerii. Możesz zarządzać wykluczonymi folderami w Menu/Ustawienia. - Wykluczyć folder nadżędny ? - Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\\n\\nIf you want to hide them from other apps too, use the Hide function. + Działa na folderach galerii. Możesz zarządzać wykluczonymi folderami w ustawieniach aplikacji. + Wykluczyć folder nadrzędny? + Wykluczenie folderów ukryje je tylko w niniejszej aplikacji, w innych aplikacjach będą one wciąż widoczne.\n\nJeśli chcesz je ukryć także w innych aplikacjach, użyj funkcji ukrywania. Usuń wszystko - Usuń wszystkie foldery z listy wykluczone? To nie usuwa folderów. + Usunąć wszystkie foldery z listy wykluczonych? Foldery nie zostaną fizycznie usunięte. + Ukryte foldery + Zarządzaj ukrytymi folderami + Zdaje się, że nie masz żadnych folderów z plikiem \'.nomedia\'. - Included folders - Manage included folders - Add folder - If you have some folders which contain media, but were not recognized by the app, you can add them manually here. + Dołączone foldery + Zarządzaj dołączonymi folderami + Dodaj folder + Jeśli masz jakieś foldery z multimediami, ale aplikacja ich nie wykryła, możesz je dodać ręcznie tutaj. + Nie znaleziono plików z multimediami. Możesz to naprawić poprzez dodanie folderów ręcznie. Zmień rozmiar Zmień i zapisz - Długość + Szerokość Wysokość Zachowaj proporcje - Wpisz poprawną rozdzielczość + Podaj poprawną rozdzielczość Edycja - zapisz Obróć - Ścieżka Nieprawidłowa ścieżka - Edycja obrazu nie powiodło się + Invalid video path + Edycja obrazu nie powiodła się + Video editing failed + Anulowano edycję obrazu + Video editing cancelled + Plik wyedytowany + Image edited successfully + Video edited successfully Edytuj obraz w: - Nie znaleziono edytora zdjęć + Edit video with: + Nie znalazłem edytora zdjęć + No video editor found Nieznana lokalizacja pliku Nie udało się zastąpić pliku Obróć w lewo Obróć w prawo - Obróć o 180o - Flip - Flip poziomo - Flip pionowo - Błąd pamięci + Obróć o 180 stopni + Przewróć + Przewróć w poziomie + Przewróć w pionie + Wolne + Inne Tapeta - Ustaw jako tapeta + Ustaw jako tapetę Ustawienie tapety nie powiodło się Ustaw jako tapetę w: - Brak odpowiednich ustawień - Ustawianie tapety… - Tapeta ustawiona - Portrait aspect ratio - Landscape aspect ratio + Ustawiam tapetę… + Tapeta została ustawiona + Proporcje ekranu w trybie pionowym + Proporcje ekranu w trybie poziomym + Pulpit + Ekran blokady + Pulpit i ekran blokady + + + Pokaz slajdów + Przedział (sekundy): + Dołączaj zdjęcia + Dołączaj filmy + Dołączaj GIFy + Losowa kolejność + Odwrotna kolejność + Zapętlaj + Animacja + Brak + Przenikanie + Przesuwanie + Pokaz slajdów zakończony + Nie znalazłem multimediów do pokazu slajdów + + + Grupuj bezpośrednie podfoldery + + + Grupuj według + Nie grupuj plików + Folderu + Daty ostatniej modyfikacji + Daty ostatniej modyfikacji (dniami) + Daty ostatniej modyfikacji (miesiącami) + Daty utworzenia + Data utworzenia (dniami) + Data utworzenia (miesiącami) + Typu + Rozszerzenia + Uwaga: grupowanie i sortowanie to dwa niezależne pola + + + Folder wyświetlany na widżecie: + Pokaż nazwę folderu - Pokaż ukryte foldery - Odtwarzaj pliki wideo automatycznie - Włącz widoczność nazwy pliku - Wybierz co pokazywać - Tylko Zdjęcia - Tylko Filmy - Gifs only - Images, videos, gifs - Obrazy i wideo - Pętla wideo - Animowanie gify z miniaturkami - Max brightness when viewing media - Crop thumbnails into squares - Rotate fullscreen media by - System setting - Device rotation - Aspect ratio - Dark background at fullscreen media - Scroll thumbnails horizontally + Odtwarzaj filmy automatycznie + Pamiętaj ostatni moment odtwarzania filmów + Pokazuj / ukrywaj nazwy plików + Zapętlaj odtwarzanie filmów + Animowane miniatury GIFów + Maksymalna jasność podczas wyświetlania multimediów + Przycinaj miniatury do kwadratów + Pokazuj czas trwania filmów + Obracaj pełnoekranowe multimedia według + Ustawień systemowych + Orientacji urządzenia + Proporcji + Czarne tło i pasek stanu przy widoku pełnoekranowym + Przewijaj miniatury poziomo + Ukrywaj interfejs przy pełnoekranowym podglądzie + Usuwaj puste foldery po usunięciu ich zawartości + Zezwalaj na kontrolowanie jasności zdjęcia pionowymi gestami + Zezwalaj na kontrolowanie jasności i głośności filmów pionowymi gestami + Pokazuj liczbę elementów w folderach w głównym widoku + Dodatkowe szczegóły przy podglądzie pełnoekranowym + Zarządzaj dodatkowymi szczegółami + Zezwalaj na powiększanie jednym palcem w widoku pełnoekranowym + Zezwalaj na natychmiastową zmianę multimediów po kliknięciu boków ekranu + Zezwalaj na duże powiększanie obrazów + Ukrywaj dodatkowe szczegóły, gdy pasek stanu jest ukryty + Pokazuj niektóre przyciski akcji na dole ekranu + Pokazuj kosz w widoku folderów + Duże powiększanie obrazów + Pokazuj obrazy w najwyższej możliwej rozdzielczości + Pokazuj kosz jako ostatni element na głównym ekranie + Zezwalaj na zamykanie pełnoekranowego widoku gestem pociągnięcia w dół + Zezwalaj na powiększanie 1:1 dwoma podwójnymi dotknięciami + Zawsze otwieraj filmy na osobnym ekranie z nowymi poziomymi gestami + Pokazuj wcięcie (jeśli dostępne) + Zezwalaj na obracanie obrazów gestami + Priorytet ładowania plików + Szybkość + Kompromis + Unikaj pokazywania niewłaściwych plików + Pokazuj rozszerzenia zdjęć + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Miniatury + Widok pełnoekranowy + Dodatkowe szczegóły + Przyciski na dolnym pasku + + + Zarządzaj przyciskami na dolnym pasku + Ulubione + Widoczność plików + + + Własne + Resetuj + Kwadrat + Transformacja + Filtr + Brak + Dopasuj + Cienie + Ekspozycja + Światła + Jasność + Kontrast + Nasycenie + Klarowniść + Gamma + Czerń + Biel + Temperatura + Ostrość + Resetuj + Focus + Brak + Radialny + Linearny + Lustrzany + Gaussa + Tekst + Opcje tekstu + Kolor tekstu + Czcionka + Dodaj + Edytuj + Wyprostuj + Czcionka + Kolor + Kolor tła + Wyrównanie + Do przodu + Usuń + Twój tekst + Pędzel + Kolor + Rozmiar + Twardość + Do przodu + Usuń + Kolor pędzla + Edytor + Zamknąć edytor? + Do you really want to discard the changes? + Tak + Nie + Anuluj + Zaakceptuj + Zapisz + Eksport… + Wyeksportowano %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Odbij w poziomie + Odbij w pionie + Cofnij + Ponów + Wybieracz kolorów + Przezroczysty + Biały + Szary + Czarny + Jasnoniebieski + Niebieski + Fioletowy + Orchidea + Różowy + Czerwony + Pomarańczowy + Złoty + Żółty + Oliwkowy + Zielony + Akwamaryna + Kolor pipety + Trim + + + Jak mogę ustawić tą aplikację jako domyślną aplikację galerii? + Znajdź obecną domyślną aplikację galerii w ustawieniach systemowych (sekcja \'Aplikacje\'). Na ekranie z informacjami o niej kliknij \'Otwórz domyślnie\', a następnie \'Wyczyść domyślne\'. Gdy podczas następnej próby otwarcia zdjęcia czy filmu system zapyta Cię jaką aplikacją to zrobić, wybierz Prostą Galerię i opcję zapamiętania tego wyboru. + Zablokowałem(-am) aplikację hasłem i wyleciało mi ono z głowy. Co mogę zrobić? + Masz dwie opcje: przeinstalowanie aplikacji lub wyczyszczenie jej ustawień. Niezależnie od wyboru, pliki pozostaną nienaruszone. + Jak sprawić, aby album(y) zawsze pojawiał(y) się na górze? + Przytrzymaj album(y) i wybierz ikonę przypięcia w pasku akcji. + Jak mogę przwijać filmy? + Możesz to osiągnąć dotykając dwa razu z boku ekranu, lub dotykając aktualnej lub makysmalnej długości tekstu przy pasku wyszukiwania. Jeśli włączysz w ustawieniach otwieranie video na nowym ekranie, możesz też używać gestów poziomych. + Jaka jest różnica między ukryciem, a wykluczeniem folderu? + Wykluczenie działa tylko w obrębie niniejszej aplikacji (wszędzie indziej pliki są normalnie widoczne), ukrywanie - w obrębie całego systemu (nie widać ich nigdzie), dodawany jest wtedy do folderu pusty plik \'.nomedia\', który możesz usunąć w dowolnym menedżerze plików. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Dlaczego pokazują mi się foldery z okładkami do piosenek i tym podobne rzeczy? + Aplikacja nie wie, czy dany obraz jest okładką od piosenki czy czymś innym. Aby ukryć niechciane rzeczy, przytrzymaj je i wybierz opcję \'Wyklucz\' z paska akcji. + Nie pokazuje(-ą) mi się folder(y) ze zdjęciami / filmami. Co mogę zrobić? + Wejdź do ustawień aplikacji i w sekcji z dołączonymi folderami dodaj tenże folder do listy. + Co jeśli chcę widzieć tylko wybrane foldery? + Przejdź do sekcji z wykluczonymi folderami w ustawieniach aplikacji, dodaj tam folder główny (\"/\"), a następnie dodaj pożądane foldery w sekcji z dołączonymi folderami. + Czy mogę w tej aplikacji przycinać obrazy? + Tak, możesz to zrobić w edytorze, przeciągając krawędzie obrazu. Edytor otworzysz przytrzymując miniaturę obrazu i wybierając opcję \'Edytuj\', bądź wybierając tą samą opcję w menu pełnoekranowym. + Czy mogę jakoś grupować miniatury plików? + Tak. Użyj opcji \'Grupuj według\', gdy jesteś w widoku miniatur. Grupować je możesz według wielu kryteriów, włącznie z datą ich utworzenia. Ponadto, jeśli użyjesz opcji \'Pokazuj całą zawartość folderów\', możesz je także grupować według folderów. + Sortowanie według daty utworzenia nie działa poprawnie. Dlaczego tak się dzieje i jak mogę to naprawić? + Dzieje się tak, gdyż prawdopodobnie pliki zostały skądś do urządzenia skopiowane. Naprawić to można wybierając miniatury plików, a następnie opcję \'Napraw datę utworzenia\'. + Na obrazach widzę wyraźne zmiany w kolorach. Jak mogę to naprawić? + Obecne rozwiązanie służące wyświetlaniu obrazów działa jak powinno w większości w przypadków. Jeśli jednak tak nie jest, pomocna może okazać się opcja \'Pokazuj obrazy w najwyższej możliwej jakości\' w sekcji \'Duże powiększanie obrazów\'. + Mam ukryte pliki i / lub foldery. Jak mogę zobaczyć? + Możesz to zrobić albo wybierając opcję \'Tymczasowo pokaż ukryte multimedia\' w menu na ekranie głównym, lub \'Pokazuj ukryte elementy\' w ustawieniach. Foldery są ukrywane poprzez dodanie do nich pustego, ukrytego pliku \'.nomedia\'. Usunąć go możesz dowolnym menedżerem plików. + Dlaczego aplikacja zajmuje tak dużo miejsca? + Pamięć podręczna aplikacji zajmuje do 250MB, zapewniając szybsze ładowanie obrazów. Aplikacja może zajmować więcej miejsca przez elementy w Koszu, które doliczane są do rozmiaru aplikacji. Wyczyść Kosz przez otwarcie go i usunięcie wszystkich elementów, lub z poziomu ustawień aplikacji. Każdy plik w Koszu jest też automatycznie usuwany po 30 dniach. + + Simple Gallery Pro - Photo Manager & Editor - Darmowa Galeria bez reklam do przeglądania zdjęć i filmów. + Browse your memories without any interruptions with this photo and video gallery - Prosta aplikacja galerii do oglądania zdjęć i filmów. Pliki mogą być sortowane według daty, rozmiaru, nazwy zarówno w porządku rosnącym lub malejącym, zdjęcia można powiększać. Pliki multimedialne są wyświetlane w wielu kolumnach w zależności od wielkości ekranu, można zmienić liczbę kolumn za pomocą gestów. Obrazy mogą być edytowane w galerii. Zdjęcia mogą być przycięte, można je obracać lub ustawić jako tapetę bezpośrednio z aplikacji. Kolor aplikacji mozna dowolnie ustawić według własnych preferencji. + Simple Gallery Pro to wysoce konfigurowalna galeria. Przeglądaj i edytuj swoje zdjęcia, dzięki funkcji kosza z łatwością odzyskuj przypadkowo (lub nie) ususnięte pliki, chroń je i ukrywaj dzięki różnym metodom zabezpieczeń. Nie martw się o obsługiwane formaty - wśród nich są m.in. RAW, SVG i wiele więcej. - Galeria jest również oferowane do użytkowania przez osoby trzecie do podglądu zdjęć/filmów, dodawania załączników do klientów e-mail itp, Idealne nadaje się do codziennego użytku. + Aplikacja nie zawiera reklam ani zezwoleń ponad te, których naprawdę potrzebuje. Nie musisz się także martwić o kwestie prywatności, gdyż nie potrzebuje ona dostępu do internetu. - Nie zawiera żadnych reklam ani niepotrzebnych uprawnień. Jest w pełni opensource. + ------------------------------------------------- + SIMPLE GALLERY PRO – FUNKCJE + ------------------------------------------------- - Ta aplikacja jest tylko jedą z większego szeregu naszych aplikacji. Można znaleźć resztę z nich w http://www.simplemobiletools.com + • Brak reklam i denerwujących okienek + • Edytor zdjęć, a w nim przycinanie, obracanie, zmienianie rozmiaru, rysowanie, filtry i wiele więcej + • Nie wymaga dostępu do internetu, przez co Twoje pliki są bezpieczne i widoczne tylko dla Ciebie + • Używane są tylko najpotrzebniejsze uprawnienia + • Szybkie wyszukiwanie plików + • Obsługa wielu formatów (RAW, SVG, panoramy, itd.) + • Różnorodność gestów dla prostszego korzystania z aplikacji + • Wiele sposobów na filtrowanie, grupowanie i sortowanie plików + • Wiele opcji dostosowywania wyglądu aplikacji + • Dostępna w 32 językach [ w tym po polsku :) ] + • Oznaczanie plików jako ulubione dla łatwiejszego do nich dostępu + • Ochrona wzorem, PINem, lub odciskiem palca dostępu do plików ... + • ... a nawet do całej aplikacji i/lub konkretnych jej funkcji + • Odzyskiwanie utraconych plików dzięki funkcji kosza + • Ukrywanie plików w obrębie aplikacji i/lub globalnie + • Tworzenie konfigurowalnych pokazów slajdów + • Szczegółowe informacje o plikach (rozdzielczość, dane EXIF, itd.) + • Aplikacja jest otwartoźródłowa + … i wiele więcej! + + EDYTOR ZDJĘĆ + Simple Gallery Pro ułatwi Ci szybką edycję zdjęć. Przycinaj je, przewracaj, obracaj, zmniejszaj i powiększaj. A w napływie kreatywności dodawaj filtry i narysuj coś na nich! + + WSPARCIE DLA WIELU TYPÓW PLIKÓW + W przeciwieństwie do niektórych aplikacji galerii, Simple Gallery Pro wspiera dużo formatów plików, w tym JPEG, PNG, MP4, MKV, RAW, SVG, panoramiczne filmy i zdjęcia oraz wiele więcej. + + WSZECHSTRONNOŚĆ + Od interfejsu do przycisków funkcyjnych na dolnym pasku, Simple Gallery Pro jest wysoce konfigurowalny, przez co działa i wygląda tak jak chcesz. Żadna inna aplikacja galerii nie jest pod tym względem tak wszechstronna. A dzięki naszej otwartości to wszystko dostępne jest w 32 językach (w tym po polsku :]). + + ODZYSKIWANIE PLIKÓW + Coś Ci się niechcący usunęło? A może ktoś to zrobił po złości? Żaden problem! Dzięki funkcji kosza z łatwością to odzyskasz. + + OCHRONA PLIKÓW + Poprzez PIN, wzór lub odcisk palca możesz chronić swoje pliki, ukrywając je i blokując do nich dostęp. Tak samo możesz zablokować dostęp do całej aplikacji lub jej poszczególnych funkcji. Możesz na przykład uniemożliwić usuwanie plików bez zeskanowania linii papilarnych, przez co tylko Ty będziesz mógł to zrobić. + + Sprawdź cały zestaw naszych aplikacji: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Odwiedź nasz profil na Facebooku... + https://www.facebook.com/simplemobiletools + + ... i/lub naszego subreddita: + https://www.reddit.com/r/SimpleMobileTools diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 5fd798068..f912ab6bc 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1,45 +1,69 @@ - Simple Gallery + Simple Galeria Galeria Editar Abrir câmera - Abrir com - Nenhum aplicativo encontrado -    (oculto) + (oculto) + (ignorado) Fixar pasta - Desfixar pasta - Mostrar conteúdo de todas as pastas + Desafixar pasta + Fixar no topo + Exibir conteúdo de todas as pastas Todas as pastas Alternar para a visualização de pastas Outra pasta - Mostrar no mapa + Exibir no mapa Localização desconhecida - Nenhum aplicativo de mapa encontrado - Nenhum aplicativo de câmera encontrado - Aumentar número de colunas - Reduzir número de colunas - Mostrar pastas ocultas temporariamente - Change cover image - Select photo - Use default + Volume + Brilho + Travar orientação + Destravar orientação + Alterar orientação + Forçar modo retrato + Forçar modo paisagem + Usar orientação padrão + Corrigir data da foto + Corrigindo… + Datas corrigidas com sucesso + Nenhum valor de data foi encontrado + Compartilhar uma versão redimensionada + Olá,\n\nparece que você está atualizando do antigo aplicativo gratuito. Agora você já pode desinstalar a versão antiga, que tem o botão de atualizar para a versão Pro no topo da pagina de Configurações.\n\nVocê terá os itens da Lixeira excluídos, itens favoritos desmarcados e também terá que redefinir as configurações do seu aplicativo.\n\nObrigado! + Alternar para a pesquisa de arquivos em todas as pastas visíveis + Set as default folder + Unset as default folder + + + Filtrar mídia + Imagens + Vídeos + GIFs + Imagens RAW + SVGs + Retratos + Nenhum arquivo de mídia encontrado a partir dos filtros selecionados. + Alterar filtros - Esta opção oculta uma pasta com a adição de um arquivo \'.nomedia\' dentro dela, e irá ocultar todas as subpastas que estejam dentro da mesma. Você poderá rever essas pastas com a opção \'Mostrar pastas ocultas\'. Continuar? - Excluir - Pastas excluídas - Gerenciar pastas excluídas - Esta ação irá excluir as pastas selecionadas apenas dentro deste aplicativo. Você pode gerenciar as pastas excuídas nas Configurações do aplicativo. - Excluir antes a pasta raíz? - A exlusão de uma pasta apenas oculta o seu conteúdo da galeria, pois todos os outros aplicativos poderão acessá-las.\\n\\nSe quiser ocultar de todos os aplicativos, utilize a função ocultar. + Esta opção oculta a pasta ao adicionar um arquivo \".nomedia\" dentro dela, o que também ocultará todas as suas subpastas. Você poderá voltar a exibir estas pastas com a opção \"Exibir pastas ocultas\". Continuar? + Ignorar + Pastas ignoradas + Gerenciar pastas ignoradas + Esta ação deixará de exibir as pastas selecionadas apenas dentro deste aplicativo. Você pode gerenciar as pastas ignoradas nas configurações do aplicativo. + Ignorar a pasta raiz ao invés desta? + Ignorar uma pasta apenas deixa de exibi-la nesta galeria. Ela continuará visível para outros aplicativos.\n\nSe você quiser ocultar a pasta para todos os aplicativos, utilize a função Ocultar. Remover todas - Remover todas as pastas da lista de exclusões? Esta ação não apaga as pastas. + Remover todas as pastas da lista de pastas ignoradas? Esta ação não exclui as pastas. + Pastas ocultas + Gerenciar pastas ocultas + Parece que você não tem nenhuma pasta oculta por um arquivo \".nomedia\". - Pastas incluídas - Gerenciar pastas incluídas + Pastas inclusas + Gerenciar pastas inclusas Adicionar pasta - Se possuir pastas com dados multimídia não reconhecidos pelo aplicativo, aqui você pode adicioná-las manualmente. + Se você possuir pastas contendo mídia, mas que não foram reconhecidas pelo aplicativo, aqui você pode adicioná-las manualmente. + Nenhum arquivo de mídia foi encontrado. Você pode resolver isso incluindo manualmente as pastas com arquivos de mídia. Redimensionar @@ -51,14 +75,21 @@ Editor - Salvar Girar - Caminho Caminho inválido + Invalid video path Falha na edição da imagem + Video editing failed + Edição de imagem cancelada + Video editing cancelled + Arquivo editado com sucesso + Image edited successfully + Video edited successfully Editar imagem com: - Editor não encontrado - Localização desconhecida + Edit video with: + Nenhum editor de imagem encontrado + No video editor found + Localização de arquivo desconhecida Não foi possível substituir o arquivo original Girar para a esquerda Girar para a direita @@ -66,55 +97,330 @@ Inverter Horizontalmente Verticalmente - Memória insuficiente + Livre + Outro - Simple Wallpaper - Definir como fundo de tela - Falha ao definir como fundo de tela - Definir fundo de tela com: - Aplicativo não encontrado - A definir como fundo de tela; - Fundo de tela definido com sucesso - Retrato - Paisagem + Simple Papel de Parede + Definir como papel de parede + Falha ao definir como papel de parede + Definir papel de parede com: + Definindo como papel de parede… + Papel de parede definido com sucesso + Retrato + Paisagem + Tela inicial + Tela de bloqueio + Tela inicial e de bloqueio + + + Apresentação + Intervalo (segundos): + Incluir fotos + Incluir vídeos + Incluir GIFs + Ordem aleatória + Ordem inversa + Apresentação em ciclo + Animação + Nenhuma + Esmaecer + Deslizar + Fim da apresentação + Nenhuma mídia encontrada para a apresentação + + + Agrupar subpastas diretas + + + Agrupar por + Não agrupar arquivos + Pasta + Última modificação + Última modificação (por dia) + Última modificação (por mês) + Data da foto + Data da foto (por dia) + Data da foto (por mês) + Tipo de arquivo + Extensão + Por favor, lembre-se que o agrupamento e classificação são configurados por dois campos independentes + + + Pasta exibida no widget: + Exibir nome da pasta - Mostrar pastas ocultas Reproduzir vídeos automaticamente - Mostrar/ocultar nome do arquivo - Mostrar mídia - Apenas imagens - Apenas vídeos - Gifs only - Images, videos, gifs - Imagens e vídeos + Lembrar posição da última reprodução de vídeo + Exibir/ocultar nome do arquivo Reproduzir vídeos em ciclo - Animação de gifs nas miniaturas -    Brilho máximo ao visualizar mídia + Animar GIFs nas miniaturas + Maximizar o brilho ao visualizar mídia em tela cheia Recortar miniaturas em quadrados + Exibir duração dos vídeos Critério para rotação de tela Padrão do sistema Sensor do aparelho Proporção da mídia - Fundo de tela escuro em mídia tela cheia - Scroll thumbnails horizontally + Fundo preto quando mídia estiver em tela cheia + Rolar miniaturas horizontalmente + Esconder interface do sistema quando em tela cheia + Apagar pastas vazias após excluir o seu conteúdo + Habilitar controle do brilho com gestos na vertical + Habilitar controle de volume e brilho com gestos na vertical + Exibir quantidade de arquivos em cada pasta na tela inicial + Exibir detalhes adicionais quando em tela cheia + Gerenciar detalhes adicionais + Habilitar zoom com um dedo em exibição de tela cheia + Habilitar a troca de mídia tocando nas laterais da tela + Habilitar zoom aprofundado para imagens + Ocultar detalhes adicionais quando a barra de status estiver escondida + Exibir alguns botões de ação na parte inferior da tela + Exibir a Lixeira na tela inicial + Zoom aprofundado para imagens + Exibir imagens na maior qualidade possível + Exibir a Lixeira como o último item na tela inicial + Habilitar fechar a exibição em tela cheia com um gesto para baixo + Habilitar zoom 1:1 com dois toques duplos + Sempre abrir vídeos em uma tela separada com novos gestos horizontais + Exibir o notch, se existente + Habilitar rotação de imagens com gestos + Prioridade de carregamento de arquivos + Velocidade + Meio termo + Evitar a exibição de arquivos inválidos + Exibir tipos de arquivo de imagem + Permitir zoom de vídeos com um toque duplo neles + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Miniaturas + Mídia em tela cheia + Detalhes adicionais + Barra inferior + + + Gerenciar botões visíveis + Favoritar/desfavoritar + Exibir/ocultar arquivo + + + Personalizar + Redefinir + Quadrado + Modificar + Filtro + Nenhum + Ajustar + Sombras + Exposição + Luz + Brilho + Contraste + Saturação + Claridade + Gamma + Preto + Branco + Temperatura + Nitidez + Redefinir + Foco + Nenhum + Radial + Linear + Espelhado + Gaussiano + Texto + Opções de texto + Cor do texto + Fonte + Adicionar + Editar + Endireitar + Fonte + Cor + Cor do plano de fundo + Alinhamento + Para frente + Excluir + Seu texto + Pincel + Cor + Tamanho + Dureza + Para frente + Deletar + Cor do pincel + Editor + Fechar Editor? + Do you really want to discard the changes? + Sim + Não + Cancelar + Aceitar + Salvar + Exportando… + Exportando %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Giro H + Giro V + Desfazer + Refazer + Seletor de cores + Transparente + Branco + Cinza + Preto + Azul claro + Azul + Roxo + Orquídea + Rosa + Vermelho + Laranja + Dourado + Amarelo + Oliva + Verde + Azul marinho + Cor pipetável + Trim + + + Como posso fazer do Simple Galeria a galeria padrão do dispositivo? + Primeiro, você deve encontrar o aplicativo padrão atual nas configurações do seu dispositivo, procurar um botão que diga algo como "Abrir por padrão", clicar nele e selecionar \"Limpar padrão\" + Na próxima vez que você abrir uma imagem ou vídeo, você deverá ver um seletor do aplicativo, no qual você terá a oportunidade de selecionar a Simple Galeria como o app padrão. + Eu protegi o app com senha, mas a esqueci. O que posso fazer? + Você pode resolver isto de duas formas. Você pode reinstalar o aplicativo ou ir nas configurações do seu dispositivo e selecionar a opção "Limpar armazenamento". Isto irá redefinir todas as suas configurações, não removerá nenhum arquivo de mídia. + O que posso fazer para que uma pasta sempre apareça no topo da lista? + Faça um toque longo na pasta em questão, e depois toque no ícone de alfinete na parte superior da tela, isto irá fixá-la no topo. Você também pode fixar várias pastas; os itens fixados serão classificados pelo método de ordenação padrão. + Como faço para avançar rapidamente um vídeo (fast forward)? + Você pode fazer isso tocando duas vezes na lateral da tela ou tocando nos textos atuais ou de duração máxima próximos à barra de busca. Se você ativar a abertura de vídeos em uma tela separada nas configurações do aplicativo, também poderá usar gestos horizontais. + Qual é a diferença entre ocultar e ignorar uma pasta? + Ignorar deixa de exibir a pasta apenas no Simple Galeria, enquanto Ocultar afeta todo o sistema e pode ocultar pastas de outras galerias também. A função ocultar funciona adicionando um arquivo vazio chamado \".nomedia\" na pasta em questão, arquivo este você também pode excluir com um gerenciador de arquivos, se quiser. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Porque pastas com capas de CD de música ou figurinhas aparecem na lista? + Pode acontecer de que algumas pastas incomuns apareçam. Você pode utlizar a opção Ignorar nestas pastas, adicionando-as a lista de pastas ignoradas. Uma maneira de fazer isso é realizar um toque longo em uma destas pastas, selecionar a opção Ignorar e, em seguida, selecionar a pasta pai. + Uma das minhas pastas não aparece, ou nem todos os seus itens são exibidos. O que posso fazer? + Isso pode ocorrer por diversos motivos, mas resolver o problema é fácil. Entre em Configurações --> Gerenciar pastas incluídas, toque no botão \"+\", e selecione a pasta em questão. + Como posso fazer para exibir apenas certas pastas? + Adicionar uma pasta à lista de Pastas Incluídas não remove outras pastas da exibição. O que você pode fazer é ir em Configurações -> Gerenciar pastas ignoradas, excluir a pasta raiz \"/\", e finalmente incluir somente as pastas desejadas através de Configurações -> Gerenciar pastas incluídas. + Isso tornará apenas as pastas selecionadas visíveis, já que ignorar e incluir são funçoes recursivas e, se uma pasta for ignorada e incluída ao mesmo tempo, ela será exibida. + Posso recortar imagens usando este app? + Sim, você pode recortar imagens no editor, arrastando os cantos da imagem. Você pode entrar no editor fazendo um toque longo na imagem e selecionando Editar, ou selecionando Editar a partir da visualização de tela cheia. + É possível exibir os meus arquivos de mídia em subgrupos ao invés de em uma única grande lista? + Você pode usar a opção \"Agrupar por\" para agrupar os seus arquivos de mídia por data, formato ou outros critérios. Se a opção \"Exibir conteúdo de todas as pastas\" estiver ativada, você também poderá agrupar por subpasta. + A ordenação por Data da Foto não funciona corretamente. O que posso fazer? + Isto pode ter ocorrer caso as suas fotos tenham sido copiadas de outro local. Para resolver o problema, você pode selecionar a função \"Corrigir data da foto\" no menu deste aplicativo. + Algumas imagens exibem artefatos de exibição, como bandas de cor. Como posso melhorar a qualidade da exibição? + O nosso método atual para a exibição de imagens funciona bem na grande maioria dos casos, mas caso você queira uma qualidade ainda melhor, ative a opção \"Exibir imagens na maior qualidade possível\" na seção \"Zoom aprofundado para imagens\" das configurações deste aplicativo. + Eu ocultei um arquivo/pasta. Como posso desfazer isso? + Você pode pressionar o item de menu \"Exibir ocultos temporariamente\" na tela principal, ou ativar \"Exibir itens ocultos\" nas configurações do aplicativo para ver o item oculto. Se você quiser reexibir-lo, basta pressionar e selecionar \"Exibir\". As pastas são ocultas adicionando um arquivo oculto \".nomedia\" dentro delas, arquivo este que você também pode excluir com um gerenciador de arquivos, se quiser. + Porque este app ocupa tanto espaço? + O cache deste app, que garante que as imagens sejam carregadas mais rapidamente, pode ocupar até 250MB. Se o app estiver ocupando mais espaço que isso, pode ser por que você tem muitos arquivos na sua Lixeira. Arquivos na Lixeira são removidos permanentemente após 30 dias, mas se mesmo assim sua Lixeira estiver ocupando muito espaço, você pode esvaziá-la manualmente. Para fazer isso, você pode abrir a Lixeira e excluir os arquivos dentro dela, ou você pode fazer o mesmo na tela de Configurações deste app. + + Simple Galeria Pro - Gerenciador de Imagens - Um aplicativo para ver fotos e vídeos. + Navegue por suas memórias sem distrações com esta galeria de fotos e vídeos - Uma ferramenta simples para ver fotos e vídeos. Pode organizar os itens por data, tamanho, nome e ampliar as fotografias. As pastas são mostradas em colunas, e a sua exibição dependerá do tamanho da tela, aonde o usuário poderá alterar o número de colunas através de gestos. Você pode renomear, compartilhar, apagar, copiar e mover os arquivos. Também pode recortar, girar e definir as imagens como fundo de tela. + A Simple Galeria Pro é uma galeria de imagens e vídeos altamente customizável. Organize e edite suas fotos e vídeos, recupere arquivos movidos para a lixeira, proteja e oculte seus arquivos, e visualize arquivos em uma grande variedade de formatos, incluindo RAW, SVG, e muito mais. + + Este app não inclui anúncios ou permissões desnecessárias e também não acessa a internet. Sua privacidade está protegida. - Também pode ser utilizada para pré-visualizar imagens e vídeos, ou para adicionar como anexos ao e-mail, entre outros. É perfeita para a utilização diária. + ------------------------------------------------- + SIMPLE GALERIA PRO – FUNCIONALIDADES + ------------------------------------------------- - Não contém anúncios, nem permissões desnecessárias. Disponibiliza um tema escuro, e é totalmente \'open source\'. + • Galeria offline sem anúncios ou popups + • Editor de imagens – recorte, rotacione, redimensione, desenhe, adicione filtros, e muito mais + • Não requer acesso à internet. Mais privacidade e segurança. + • Não pede permissões desnecessárias + • Pesquise por imagens, vídeos e arquivos rapidamente + • Visualize diversos formatos de arquivos de imagem ou vídeo (RAW, SVG, imagens panorâmicas, etc) + • Use uma variedade de gestos intuitivos para editar e organizar seus arquivos + • Varias maneiras de filtrar, agrupar, e ordenar seus arquivos + • Customize a aparência da Simple Galeria Pro + • Disponível em 32 idiomas + • Marque seus arquivos favoritos para acesso mais rápido + • Proteja suas imagens e vídeos com um padrão, PIN, ou impressão digital + • Recupere imagens e vídeos que foram excluídos para a lixeira + • Oculte arquivos para deixar de exibir certas imagens ou vídeos no app. + • Crie um slideshow customizado com os seus arquivos + • Exiba informações avançadas sobre seus arquivos (resolução, metadados EXIF, etc) + • A Simple Galeria Pro tem código aberto! + … e muito mais! - Este aplicativo é apenas parte de um conjunto mais vasto de aplicações. Saiba mais em http://www.simplemobiletools.com + EDITOR DE IMAGENS + Com a Simple Galeria Pro é bem fácil editar suas imagens na hora. Recorte, rotacione, e redimensione suas imagens. Se você estiver se sentindo mais criativo, você também pode desenhar ou adicionar filtros! + + SUPORTE A VÁRIOS FORMATOS DE ARQUIVO + Ao contrário de alguns outros aplicativos de organização de imagens, a Simple Galeria Pro é capaz de exibir uma grande variedade de formatos de arquivo, incluindo JPEG, PNG, MP4, MKV, RAW, SVG, fotos panorâmicas, vídeos panorâmicos e muito mais. + + GERENCIADOR DE IMAGENS ALTAMENTE CUSTOMIZÁVEL + Desde a interface de usuário, até os botões da barra de ferramentas, a Simple Galeria Pro é altamente customizável, e você pode fazer com que ela funcione exatamente do seu jeito. Nenhum outro app de galeria de imagens é tão flexível! Além disso, por ter o código aberto, a Simple Galeria está disponível em 32 idiomas! + + RECUPERE IMAGENS E VÍDEOS EXCLUÍDOS + Acidentalmente excluiu uma preciosa imagem ou vídeo? Não se preocupe! A Simple Galeria Pro vem com uma lixeira que permite facilmente recuperar fotos ou vídeos excluídos nos últimos 30 dias. + + PROTEJA E OCULTE FOTOS, VÍDEOS E ARQUIVOS + Usando um PIN, padrão, ou sua impressão digital, você pode proteger ou ocultar imagens, vídeos ou até álbuns inteiros. Você também pode proteger o app como um todo por senha, ou apenas funções específicas. Por exemplo, você pode fazer com que a Simple Galeria verifique a impressão digital antes de excluir arquivos, para ajudar a evitar que arquivos sejam excluídos. + + Dê uma olhada nos nossos outros aplicativos Simple: + https://www.simplemobiletools.com + + Site dedicado do Simple Galeria Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 89c7bfd08..4c250cd4a 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -4,42 +4,66 @@ Galeria Editar Abrir câmara - Abrir com - Nenhuma aplicação encontrada (oculta) + (excluída) Fixar pasta Desafixar pasta + Fixar no topo Mostrar conteúdo de todas as pastas Todas as pastas Alternar para a vista de pastas Outra pasta Mostrar no mapa Localização desconhecida - Não existe uma aplicação adequada - Não existe uma aplicação adequeada - Aumentar número de colunas - Reduzir número de colunas - Mostrar ocultas temporariamente - Change cover image - Select photo - Use default + Volume + Brilho + Bloquear orientação + Desbloquear orientação + Alterar orientação + Impor modo vertical + Impor modo horizontal + Usar predefinição + Corrigir data de obtenção + A corrigir… + Dados corrigidos com sucesso + Não foram encontrados dados para a data de obtenção da fotografia + Partilhar foto redimensionada + Olá,\n\nparece que você utilizou a opção de atualização existente na versão antiga. Agora já pode desinstalar essa versão antiga.\n\nApenas perderá os itens existentes na reciclagem e os favoritos não assinalados mas também terá que repor as predefinições da aplicação.\n\nObrigado! + Trocar para pesquisa de ficheiros em todas as pastas visíveis + Utilizar como pasta padrão + Deixar de utilizar como pasta padrão + + + Filtrar multimédia + Imagens + Vídeos + GIF + Imagens RAW + SVG + Retratos + Não foram encontrados ficheiros que cumpram os requisitos. + Alterar filtros - Esta opção oculta uma pasta com a adição de um ficheiro \'.nomedia\' na pasta, e irá ocultar todas as subpastas existentes. Pode ver as pastas com a opção \'Mostrar pastas ocultas\'. Continuar? + Esta opção oculta uma pasta com a adição de um ficheiro \'.nomedia\' e irá ocultar todas as sub-pastas existentes. Pode ver as pastas com a opção \'Mostrar pastas ocultas\'. Continuar? Exclusão Pastas excluídas Gerir pastas excluídas - Esta ação apenas exclui as pastas selecionadas da lista de pastas desta aplicação. Pode gerir as pastas excuídas nas Definições. + Esta ação apenas exclui as pastas selecionadas da lista de pastas desta aplicação. Pode gerir as pastas excluídas nas Definições. Excluir antes a pasta superior? - A exlusão de uma pasta apenas oculta o seu conteúdo do Simple Gallery uma vez que as outras aplicações continuarão a poder aceder-lhes.\\n\\nSe quiser ocultar também das outras aplicações, utilize a função Ocultar. + A exclusão de uma pasta apenas oculta o seu conteúdo do Simple Gallery porque as outras aplicações continuarão a poder aceder-lhes.\n\nSe quiser ocultar também das outras aplicações, utilize a função Ocultar. Remover todas Remover todas as pastas de lista de exclusões? Esta ação não apaga as pastas. + Pastas ocultas + Gerir pastas ocultas + Parece que não existem pastas ocultadas com ficheiros \".nomedia\". Pastas incluídas Gerir pastas incluídas Adicionar pasta Se possuir pastas com dados multimédia não reconhecidos pela aplicação, aqui pode adicioná-las manualmente. + Não foram encontrados ficheiros multimédia. Pode adicionar manualmente as pastas que contenham esses ficheiros. Redimensionar @@ -51,13 +75,20 @@ Editor - Guardar Rodar - Caminho Caminho inválido + Caminho de vídeo inválido Falha na edição da imagem + Falha na edição do vídeo + Edição de imagem cancelada + Edição de vídeo cancelada + Imagem editada com sucesso + Imagem editada com sucesso + Vídeo editado com sucesso Editar imagem com: - Editor não encontrado + Editar vídeo com: + Editor de imagem não encontrado + Editor de vídeo não encontrado Localização desconhecida Não foi possível substituir o ficheiro original Rodar à esquerda @@ -66,51 +97,327 @@ Inverter Horizontalmente Verticalmente - Memória insuficiente + Livre + Outro Simple Wallpaper Definir como fundo do ecrã Falha ao definir como fundo de ecrã Definir como fundo com: - Aplicação não encontrada A definir como fundo de ecrã… Fundo definido com sucesso Proporção na vertical Proporção na horizontal + Ecrã principal + Ecrã de bloqueio + Ecrã principal e ecrã de bloqueio + + + Apresentação + Intervalo (segundos): + Incluir fotos + Incluir vídeos + Incluir GIF + Ordem aleatória + Mover para trás + Apresentação em ciclo + Animação + Nenhuma + Desvanecer + Deslizar + Apresentação terminada + Não foram encontrados ficheiros para a apresentação + + + Agrupar sub-pastas + + + Agrupar por + Não agrupar ficheiros + Pasta + Última modificação + Última modificação (diária) + Última modificação (mensal) + Data de obtenção + Data de obtenção (diária) + Data de obtenção (mensal) + Tipo de ficheiro + Extensão + Tenha em atenção de que agrupamento e ordenação são campos independentes + + + Pasta mostrada no widget: + Mostrar nome da pasta - Mostrar pastas ocultas Reproduzir vídeos automaticamente + Memorizar posição da reprodução Mostrar/ocultar nome do ficheiro - Mostrar multimédia - Apenas imagens - Apenas vídeos - Apenas gifs - Imagens, vídeos e gifs - Imagens e vídeos Vídeos em ciclo - Animação de gifs nas miniaturas + Animação de GIF nas miniaturas Brilho máximo permitido Recortar miniaturas em quadrados + Mostrar duração do vídeo Rodar em ecrã completo por Definições do sistema Rotação do dispositivo Proporção - Usar sempre um fundo escuro se em ecrã completo - Scroll thumbnails horizontally + Fundo escuro e barra de estado no modo de ecrã completo + Deslocação horizontal de miniaturas + Ocultar interface do sistema se em ecrã completo + Apagar pastas vazias depois de remover o seu conteúdo + Permitir controlo do brilho das fotos com gestos verticais + Permitir controlo do volume e do brilho dos vídeos através de gestos verticais + Mostrar número de ficheiros na vista principal + Mostrar detalhes se em ecrã completo + Gerir detalhes exibidos + Permitir ampliação com um dedo se em ecrã completo + Permitir troca imediata de ficheiro ao tocar nas margens do ecrã + Permitir ampliação profunda de imagens + Ocultar detalhes extra se a barra de estado estiver oculta + Mostrar alguns botões de ação na base do ecrã + Mostrar reciclagem no ecrã de pastas + Ampliação de imagens + Mostrar fotos com a melhor qualidade possível + Mostrar reciclagem como o último item do ecrã principal + Sair de ecrã completo com um gesto para baixo + Permitir ampliação 1:1 com dois toques + Abrir vídeos em ecrã distinto com os novos toques horizontais + Mostrar \"notch\", se disponível + Permitir rotação de imagens com gestos + Prioridade de carregamento dos ficheiros + Velocidade + Qualidade + Não mostrar ficheiros inválidos + Mostrar o tipo de imagem + Permitir ampliação de vídeos com dois toques + Estilo de miniatura das pastas + File thumbnail style + Thumbnail spacing + Mostrar número de ficherios em linha distinta + Mostrar número de ficheiros em chavetas + Não mostrar número de ficheiros + Restringir título das pastas a 1 linha + Quadrado + Cantos arredondados + + + Miniaturas + Multimédia em ecrã completo + Detalhes extra + Ações em baixo + + + Gerir botões de ação + Alternar favoritos + Alternar visibilidade dos ficheiros + + + Personalizado + Repor + Quadrado + Transformar + Filtrar + Nenhum + Ajustar + Sombras + Exposição + Realce + Brilho + Contraste + Saturação + Claridade + Gama + Pretos + Brancos + Temperatura + Nitidez + Repor + Foco + Nenhum + Radial + Linear + Espelhado + Gaussiano + Texto + Opções de texto + Cor do texto + Tipo de letra + Adicionar + Editar + Endireitar + Tipo de letra + Cor + Cor de fundo + Alinhamento + Para a frente + Apagar + O seu texto + Pincel + Cor + Tamanho + Espessura + Para a frente + Apagar + Cor do pincel + Editor + Fechar editor? + Tem a certeza de que deseja descartar as alterações? + Sim + Não + Cancelar + Aceitar + Guardar + A exportar… + A exportar %s. + Sticker + Cor do sticker + Opções do sticker + Adicionar + Cor + Apagar + Para a frente + Endireitar + Substituir + Opacidade + Contraste + Saturação + Brilho + Carregamentos + Sobreposição + Normal + Escurecer + Ecrã + Sobreposição + Clarear + Multiplicar + Color Burn + Luz suave + Luz forte + Nenhuma + Dourada + Lightleak 1 + Mosaico + Papel + Chuva + Vintage + Inversão horizontal + Inversão vertical + Desfazer + Refazer + Seletor de cores + Transparente + Branco + Cinzento + Preto + Azul claro + Azul + Púrpura + Orquídea + Cor de rosa + Vermelho + Cor de laranja + Dourado + Amarelo + Oliva + Verde + Aquamarino + Pipeta de cores + Trim + + + How can I make Simple Gallery the default device gallery? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + Simple Gallery Pro - Editor e gestor de fotos - Uma aplicação para ver fotografias e vídeos. + Explore as suas memórias sem interrupções com esta aplicação - Uma ferramenta simples para ver fotos e vídeos. Pode organizar os itens por data, tamanho, nome e ampliar as fotografias. Os ficheiros multimédia são mostrados em colunas e a sua exibição está dependente do tamanho do ecrã, podendo alterar o número de colunas através de gestos. Pode renomear, partilhar, apagar, copiar e mover as fotografias. Também pode recortar, rodar e definir as imagens como fundo do ecrã a partir da aplicação. + Simple Gallery Pro é uma aplicação local para gerir fotos e vídeos. Pode organizar e editar as suas fotos, recuperar ficheiros através da reciclagem, proteger e ocultar ficheiros e ver imagens e vídeos disponíveis em vários formatos tais como RAW, SVG e muito mais. - Também pode ser utilizada para pré-visualizar imagens e vídeos ou para adicionar como anexos ao e-mail, entre outros. É perfeita para a utilização diária. + A aplicação não tem anúncios nem permissões desnecessárias. Uma vez que também não precisa de aceder à Internet, os seus ficheiros estão protegidos. - Não contém anúncios nem permissões desnecessárias. Disponibiliza um tema escuro e é totalmente \'open source\'. + ------------------------------------------------- + SIMPLE GALLERY PRO – FUNCIONALIDADES + ------------------------------------------------- - Esta aplicação é apenas parte de um conjunto mais vasto de aplicações. Saiba mais em http://www.simplemobiletools.com + • Galeria local sem quaisquer anúncios + • Editor de fotos – recorte, rotação, redimensionamento, filtros e mais + • Não requer acesso à Internet, garantindo a sua privacidade e segurança + • Não requer permissões desnecessárias + • Pesquisa rápida de imagens, vídeos e ficheiros + • Com suporte a diversos tipos de ficheiros (RAW, SVG, panorâmica, etc) + • Pode utilizar gestos para editar e organizar ficheiros + • Diversas opções para filtrar, agrupar e ordenar ficheiros + • Personalização da aparência da aplicação + • Disponível em 32 idiomas + • Marcar ficheiros como favoritos para acesso rápido + • Proteção de fotos e vídeos com recurso a padrões, PIN ou impressão digital + • Também pode utilizar padrões PIN e impressão digital para impedir a abertura da aplicação ou para restringir ações + • Recuperação de fotos e vídeos apagados através da reciclagem + • Alternar visibilidade dos ficheiros para ocultar/mostrar fotos e vídeos + • Criação de apresentações personalizadas + • Ver informações detalhadas dos ficheiros (resolução, dados EXIF, etc) + • Simple Gallery Pro é open source + … e muito mais! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Consulte todas as aplicações Simple Tools aqui: + https://www.simplemobiletools.com + + Sítio web de Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + Фильтр медиа + Изображения + Видео + GIF + RAW + SVG + Портреты + При заданных фильтрах медиафайлы не найдены + Изменить фильтры - Эта опция скрывает папку, добавляя в неё файл \'.nomedia\'; будут скрыты все подпапки. Можно показывать их, переключая \'Показать скрытые папки\' в настройках. Продолжить? + Эта функция скрывает папку, добавляя в неё файл \".nomedia\". Также будут скрыты все подпапки. Можно показать их, переключая \"Показывать скрытые папки\" в настройках. Продолжить? Исключить Исключённые папки Управление исключёнными папками - Эта опция исключит выбранные папки вместе с подпапками только для Simple Gallery. Можно управлять исключёнными папками из настроек. - Исключить только родительский каталог? - Исключая папки, вы сделаете их скрытыми вместе с подпапками в Simple Gallery, но они будут видны в других приложениях. Если вы хотите скрыть их в других приложениях, используйте функцию Скрыть. + Эта функция исключит выбранные папки вместе с подпапками только для Simple Gallery. Можно управлять исключёнными папками из настроек. + Исключить только родительскую папку? + Исключая папки, вы сделаете их скрытыми вместе с подпапками в Simple Gallery, но они будут видны в других приложениях. Если вы хотите скрыть их и в других приложениях, используйте функцию \"Скрыть\". Удалить всё - Очистить список исключённых? Сами папки не будут удалены. + Очистить список исключённых папок? Сами папки не будут удалены. + Скрытые папки + Управление скрытыми папками + Похоже, у вас нет папок, скрытых файлом \".nomedia\". - Включенные папки - Управление включенными папками - Добавление папки - Если у вас есть папки содержащие медиа, но не распознанные Simple Gallery. Вы можете добавить их вручную. + Включённые папки + Управление включёнными папками + Добавить папку + Если у вас есть папки, содержащие медиафайлы, но не распознанные приложением, вы можете добавить их вручную.\n\nДобавление папки не приводит к исключению каких-либо других. + Медиафайлы не найдены. Вы можете исправить данную проблему, добавив папки с медиафайлами вручную. Изменить размер - Изменить выбранное и сохранить + Изменить и сохранить Ширина Высота - Сохранить соотношение сторон + Сохранять соотношение сторон Указано недопустимое разрешение Редактор - Сохранить Поворот - Путь Недопустимый путь изображения - Редактирование изображения не удалось - Редактирование изображений в: - Ни одного редактора изображений не найдено + Недопустимый путь видео + Редактирование изображения не выполнено + Редактирование видео не выполнено + Редактирование изображения отменено + Редактирование видео отменено + Файл успешно отредактирован + Изображение успешно отредактировано + Видео успешно отредактировано + Редактировать изображение в: + Редактировать видео в: + Редактор изображений не найден + Редактор видео не найден Неизвестное местоположение файла Не удалось перезаписать исходный файл Повернуть влево Повернуть вправо - Перевернуть на 180º + Перевернуть на 180° Отразить По горизонтали По вертикали - Память переполнена + Свободно + Другое Простые обои Установить в качестве обоев - Установить не удалось - Установить в качестве обоев в: - Приложение не найдено - Установка обоев… - Обои успешно установлены - Формат изображения - Пейзажное соотношение сторон + Не удалось установить + Установить в качестве обоев с помощью + Установка обоев… + Обои установлены + Портретная ориентация + Альбомная ориентация + Домашний экран + Экран блокировки + Домашний экран и экран блокировки + + + Слайдшоу + Интервал (в секундах): + Изображения + Видео + GIF + В случайном порядке + В обратном порядке + Зациклить + Тип анимации + Нет + Затухание + Сдвиг + Слайдшоу завершено + Не найдено медиафайлов для слайдшоу + + + Объединять вложенные папки + + + Группировать по… + Не группировать + Папка + Последнее изменение + Последнее изменение (день) + Последнее изменение (месяц) + Дата съёмки + Дата съёмки (день) + Дата съёмки (месяц) + Тип файла + Расширение + Обратите внимание, что группировка и сортировка — это два независимых поля + + + Папка, отображаемая в виджете: + Показывать имя папки - Показать скрытые папки Воспроизводить видео автоматически + Запоминать позицию воспроизведения последнего видео Переключить отображение имени файла - Отображать - Только изображения - Только видео - Gifs only - Images, videos, gifs - Изображения и видео - Повторять видео - Анимировать эскизы gif-файлов + Зациклить видео + Анимировать миниатюры GIF Максимальная яркость при просмотре файлов - Нарезать миниатюры в квадраты - Полноэкранный поворот + Нарезать миниатюры на квадраты + Показывать длительность видео + Поворот экрана при просмотре изображения Системные настройки - Поворот устройства - Соотношение сторон - Dark background at fullscreen media - Scroll thumbnails horizontally + При повороте устройства + По размеру изображения + Чёрные фон и строка состояния при просмотре изображения + Прокрутка миниатюр по горизонтали + Автоматически скрывать системный интерфейс при просмотре изображения + Удалять пустые папки после удаления их содержимого + Управление яркостью при просмотре изображения вертикальными жестами + Управление громкостью и яркостью при просмотре видео вертикальными жестами + Показывать количество файлов в папках + Показывать сведения о файле + Выбор сведений о файле + Масштабирование одним пальцем при просмотре изображения + Переключение медиафайлов нажатием по краю экрана + Включить полное масштабирование + Скрывать сведения о файле при скрытой строке состояния + Показывать кнопки действий в нижней части экрана + Показывать корзину вместе с папками + Масштабирование изображения + Показывать изображения с максимально высоким качеством + Показывать корзину как последний элемент на главном экране + Отключать полноэкранный режим жестом вниз + Масштабирование двумя двойными нажатиями + Воспроизводить видео на отдельном экране с управлением позицией горизонтальными жестами + Показывать метку при наличии + Поворачивать изображения жестами + Приоритет загрузки файла + Скорость + Компромисс + Пропускать файлы с ошибками + Показывать типы файлов изображений + Масштабировать видео двойным нажатием по нему + Вид миниатюр папок + Вид миниатюр файлов + Промежуток между миниатюрами + Показывать количество файлов отдельной строкой + Показывать количество файлов в скобках + Не показывать количество файлов + Длинное название папки одной строкой + Квадраты + Закруглённые углы + + + Миниатюры + Просмотр медиафайлов + Подробности + Нижние кнопки действий + + + Настройка нижних кнопок действий + Переключение избранного + Переключение видимости файлов + + + Свободная + Сброс + Квадрат + Трансформация + Фильтры + Нет + Эффекты + Тени + Экспозиция + Подсветка + Яркость + Контраст + Насыщенность + Чёткость + Гамма + Затемнение + Высветление + Температура + Резкость + Сброс + Фокус + Нет + Радиус + Линия + Зеркало + Гаусс + Текст + Параметры + Цвет + Шрифт + Добавить + Изменить + Распрямить + Шрифт + Цвет + Фон + Выравнивание + Спереди + Удалить + Ваш текст + Кисть + Цвет + Размер + Чёткость + Спереди + Удалить + Цвет кисти + Редактор + Закрыть редактор? + Отменить все изменения? + Да + Нет + Отмена + Принять + Сохранить + Экспортирование… + Экспортирование %s. + Наклейка + Цвет наклейки + Свойства наклейки + Добавить + Цвет + Удалить + Сверху + Выпрямить + Заменить + Непрозрачность + Контраст + Насыщенность + Яркость + Загрузки + Наложение + Обычное + Затенение + Экран + Перекрытие + Высветление + Умножение + Цветовой прожиг + Мягкий свет + Жёсткий свет + Нет + Золото + Засветка 1 + Мозаика + Бумага + Дождь + Старина + Г-поворот + В-поворот + Отмена + Повтор + Выбор цвета + Прозрачный + Белый + Серый + Чёрный + Светло-синий + Синий + Фиолетовый + Светло-лиловый + Розовый + Красный + Оранжевый + Золотистый + Жёлтый + Оливковый + Зелёный + Аквамарин + Цвет заливки + Обрезка + + + Как сделать Simple Gallery галереей по умолчанию? + Сначала вы должны найти галерею по умолчанию в разделе \"Приложения\" настроек вашего устройства; найдите кнопку, названную \"Открыть по умолчанию\",затем нажмите на неё и выберите \"Очистить значения по умолчанию\". + В следующий раз, когда попробуете открыть изображение или видео, вы увидите выбор приложений, где сможете выбрать Simple Gallery и сделать её приложением по умолчанию. + Я заблокировал приложение паролем, но забыл его. Что мне теперь делать? + Это можно решить двумя способами. Вы можете переустановить приложение или найти его в настройках вашего устройства и выбрать пункт \"Очистить данные\", что сбросит все настройки приложения, но не удалит медиафайлы. + Как я могу сделать альбом всегда отображающимся сверху? + Вы можете длительным нажатием на желаемый альбом открыть меню действий в нём выбрать пункт \"Закрепить\". Можно закрепить несколько альбомов (папок); прикреплённые элементы будут отсортированы по методу сортировки по умолчанию. + Как ускорить перемотку видео? + Можно дважды нажать на кромку экрана или нажать на цифры текущего положения или максимальной длительности видео рядом с панелью поиска. Если в настройках приложения включено воспроизведение видео на отдельном экране, то также можно использовать горизонтальные жесты. + В чём разница между скрытием и исключением папки? + Исключение запрещает отображение папки только в Simple Gallery, в то время как скрытие работает системно и скрывает папку из других галерей. Это достигается путём создания пустого файла \".nomedia\" в данной папке, который впоследствии можно удалить любым файловым менеджером. Обратите внимание, что некоторые устройства не позволяют скрывать папки, такие как \"Camera\", \"Screenshot\" и \"Download\". + Почему отображаются папки с музыкальными обложками? + Может случиться так, что вы увидите необычные альбомы. Вы можете легко убрать их, долго нажав на них и выбрав пункт \"Исключить\". В следующем диалоговом окне вы сможете выбрать родительскую папку, что, скорее всего, также предотвратит появление других похожих альбомов. + Папка с изображениями не отображается, что можно сделать? + Это может происходить по нескольким причинам, но легко решаемо. Просто зайдите в \"Настройки\" → \"Управление включёнными папками\", нажмите \"плюс\" и перейдите в нужную папку. + Что делать, если я хочу видеть только несколько конкретных папок? + Добавление папки во включённые не исключает автоматически остальные. Что вы можете сделать это через \"Настройки\" → \"Управление исключёнными папками\". Исключите корневую папку \"/\", затем добавьте нужные папки в \"Настройки\" → \"Управление включёнными папками\". + Это сделает видимыми только выбранные папки, так как исключение и включение являются рекурсивными, и если папка исключена и включена, то она будет отображаться. + Можно ли обрезать изображения с помощью данного приложения? + Да, вы можете обрезать изображения в редакторе, перетаскивая за углы. К редактированию можно перейти, выбрав соответствующий пункт в меню, открывающемуся длительным нажатием на миниатюру или изображение в полноэкранном режиме. + Могу ли я как-то сгруппировать миниатюры медиафайлов? + Конечно, просто используйте пункт меню \"Группировать по…\" во время просмотра миниатюр. Вы можете группировать файлы по нескольким критериям, включая дату съёмки. Если вы используете функцию \"Отобразить все медиафайлы\", то также можете группировать их по папкам. + Сортировка по дате, похоже, не работает должным образом. Как это можно исправить? + Скорее всего, проблема вызвано тем, что файлы были откуда-то скопированы. Это можно исправить, выделив миниатюры файлов и выбрав \"Исправить дату\". + Я вижу какие-то цветовые полосы на изображениях. Как я могу улучшить качество? + Используемый в настоящее время метод вывода изображений работает отлично в подавляющем большинстве случаев, но если вы хотите получить ещё более высокое качество изображения, можете включить \"Показывать изображения с максимально высоким качеством\" в настройках приложения в разделе \"Масштабируемые изображения\". + Я скрыл файл/папку. Как я могу его увидеть его снова? + Вы можете либо нажать кнопку \"Временно показать скрытые элементы\" на главном экране, либо переключить \"Показывать скрытые папки\" в настройках приложения, чтобы его увидеть. Если вы хотите, чтобы скрытый элемент отображался, используйте длительное нажатие и выберите \"Показать\". Папки скрываются добавлением в них файла \".nomedia\", который можно удалить любым файловым менеджером. + Почему приложение занимает так много места? + Кеш приложения может занимать до 250 МБ, это обеспечивает более быструю загрузку изображений. Если приложение занимает ещё больше места, это, скорее всего, связано с тем, что в корзине находятся удалённые элементы. Эти файлы прибавляются к размеру приложения. Вы можете очистить корзину или открыв её, или из настроек приложения. Любой файл в корзине автоматически удаляется через 30 дней. + + Галерея Pro - управление изображениями - Галерея для просмотра изображений и видео. Без рекламы. + Просматривайте свои воспоминания без перерывов с этой фото- и видеогалереей - Простое приложение для просмотра изображений и видеозаписей. Отображаемые файлы могут быть отсортированы как по возрастанию, так и по убыванию даты, размера или имени. Фотографии можно масштабировать. В зависимости от размера экрана, медиафайлы располагаются в несколько столбцов, можно изменять число столбцов щипком двумя пальцами. Можно переименовывать, удалять, копировать, перемещать и делиться файлами из галереи. В приложении есть возможность обрезать и поворачивать изображения или устанавливать их в качестве обоев. + Simple Gallery Pro — настраиваемая автономная галерея. Организуйте и редактируйте свои фотографии; восстанавливайте удалённые файлы при помощи функции "корзина"; защищайте и скрывайте личные файлы; просматривайте множество различных фото- и видеоформатов, включая RAW, SVG и многие другие. - Галерея идеальна для повседневных задач (предпросмотр фото/видео, добавление вложений в почтовых клиентах и т.д.). + Приложение не содержит рекламы и ненужных разрешений. Поскольку приложение не требует доступа в интернет, ваша конфиденциальность защищена. - Это приложение не будет показывать рекламу или запрашивать ненужные разрешения. У него полностью открытый исходный код и настраиваемые цвета оформления. + ------------------------------------------------- + SIMPLE GALLERY PRO – ВОЗМОЖНОСТИ + ------------------------------------------------- - Simple Gallery - это приложение из серии Simple Mobile Tools. Остальные приложения из этой серии можно найти здесь: http://www.simplemobiletools.com + • Автономная галерея без рекламы или всплывающих окон + • Встроенный редактор изображение - обрезка, поворот, изменение размера, рисование, фильтры и другое + • Не требуется доступ в интернет, что даёт вам больше конфиденциальности и защищённости + • Не требуется никаких ненужных разрешений + • Быстрый поиск изображений, видео и файлов + • Открывает множество различных типов изображений и видео (RAW, SVG, панорамы и т.д.) + • Различные интуитивно понятные жесты, чтобы легко редактировать и организовывать файлы + • Множество способов фильтрации, группировки и сортировки файлов + • Настройка внешнего вида в Simple Gallery Pro + • Доступна на 32 языках + • Пометка файлов как избранных для быстрого доступа + • Защищает ваши фотографии и видео с помощью графического ключа, PIN-кода или отпечатка пальца + • Также можно использовать ключ, PIN или отпечаток для защиты запуска приложения или конкретных функций + • Восстановление удалённых фотографий и видео из корзины + • Переключение видимости файлов для скрытия фотографий и видео + • Создание настраиваемых слайдшоу из ваших файлов + • Просмотр подробной информации о файлах (разрешение, значения EXIF и т.д.) + • Simple Gallery Pro с открытым исходным кодом + …и многое многое другое! + + РЕДАКТОР ИЗОБРАЖЕНИЙ + Simple Gallery Pro позволяет легко редактировать изображения на ходу. Можно обрезать, перевернуть, повернуть и изменить размер ваших фотографий. Если вы чувствуете себя немного более творчески, то можете добавить фильтры и рисовать на своих фотографиях! + + ПОДДЕРЖКА МНОЖЕСТВА ТИПОВ ФАЙЛОВ + В отличие от некоторых других галерей и организаторов изображений, Simple Gallery Pro поддерживает огромный набор различных типов файлов, включая JPEG, PNG, MP4, MKV, RAW, SVG, панорамные фотографии, панорамные видео и многие другие. + + НАСТРАИВАЕМЫЙ МЕНЕДЖЕР ГАЛЕРЕИ + От пользовательского интерфейса до функциональных кнопок на нижней панели инструментов, Simple Gallery Pro отличается высокой настраиваемостью и работает так, как вы хотите. Никакой другой менеджер галереи не обладает такой гибкостью! Благодаря открытому исходному коду, мы также доступны на 32 языках! + + ВОССТАНОВЛЕНИЕ УДАЛЁННЫХ ФОТОГРАФИЙ И ВИДЕО + Случайно удалили драгоценное фото или видео? Не беспокойтесь! Simple Gallery Pro предлагает удобную корзину, из которой можно легко восстановить удалённые фотографии и видео. + + ЗАЩИТА И СКРЫТИЕ ФОТОГРАФИЙ, ВИДЕО И ФАЙЛОВ + Используя PIN-код, графический ключ или сканер отпечатков пальцев своего устройства, вы можете защитить и скрыть фотографии, видео и целые альбомы. Вы можете защитить как всё приложение, так и заблокировать отдельные его функции. Например, вы не сможете удалить файл без подтверждения отпечатком пальца, что поможет защитить файлы от случайного удаления. + + Ознакомьтесь с полным набором инструментов серии Simple здесь: + https://www.simplemobiletools.com + + Сайт приложения Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index cf7f93f41..ddc2fa82b 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -4,25 +4,45 @@ Galéria Upraviť Otvoriť fotoaparát - Otvoriť pomocou - Nenašla sa žiadna vhodná aplikácia (skryté) + (vylúčené) Pripnúť priečinok Odopnúť priečinok + Pripnúť na vrch Zobraziť obsah všetkých priečinkov Všetky priečinky Prepnúť na zobrazenie priečinkov Iný priečinok Zobraziť na mape Neznáma poloha - Nenašla sa žiadna mapová aplikácia - Nenašla sa žiadna aplikácia pre fotoaparát - Zvýšiť počet stĺpcov - Znížiť počet stĺpcov - Dočasne zobraziť skryté - Zmeniť obal albumu - Zvoliť foto - Použiť predvolený + Hlasitosť + Jas + Uzamknúť otočenie obrazovky + Odomknúť otočenie obrazovky + Zmeniť otočenie obrazovky + Vynútiť orientáciu na výšku + Vynútiť orientáciu na šírku + Použiť predvolenú orientáciu + Opraviť dátum vytvorenia + Opravuje sa… + Dátumy vytvorenia boli úspešne opravené + Nenašli sa žiadne dátumy vytvorenia + Zdieľať verziu so zmenenou veľkosťou + Zdravím,\n\nvyzerá to tak, že ste zo starej bezplatnej apky prešlie na novú, platenú. Starú apku, ktorá má na vrchu nastavení tlačidlo \'Stiahnuť Pro verziu\', môžete už odinštalovať.\n\nStratíte tým iba súbory v odpadkovom koši, obľúbené položky budú odznačené a tiež si budete musieť opäť nastaviť položky v nastaveniach aplikácie.\n\nVďaka! + Prepnúť na vyhľadávanie súborov vo všetkých viditeľných priečinkoch + Nastaviť ako predvolený priečinok + Odobrať predvolený priečinok + + + Filter médií + Obrázky + Videá + GIF + RAW obrázky + SVG + Portréty + So zvolenými filtrami sa nenašli žiadne média súbory. + Zmeniť filtre Táto funkcia skryje priečinok pridaním súboru \'.nomedia\', skryté budú aj podpriečinky. Môžete ich vidieť zvolením možnosti \'Zobraziť skryté priečinky\' v nastaveniach. Pokračovať? @@ -31,15 +51,19 @@ Spravovať vylúčené priečinky Táto funkcia vylúči výber a jeho podpriečinky iba z Jednoduchej galérie. Spravovať vylúčené priečinky je možné v nastaveniach. Chcete ukryť rodičovský priečinok? - Vylúčené priečinky budú spolu s podpriečinkami ukryté iba pred Jednoduchou Galériou, ostatné aplikácie ich budú stále vidieť.\\n\\nAk ich chcete ukryť aj pred ostatnými aplikáciami, použite funkciu Skryť. + Vylúčené priečinky budú spolu s podpriečinkami ukryté iba pred Jednoduchou Galériou, ostatné aplikácie ich budú stále vidieť.\n\nAk ich chcete ukryť aj pred ostatnými aplikáciami, použite funkciu Skryť. Odstrániť všetky Odstrániť všetky priečinky zo zoznamu vylúčených? Táto operácia neodstráni samotný obsah priečinkov. + Skryté priečinky + Spravovať skryté priečinky + Zdá sa, že nemáte žiadne priečinky skryté pomocou súboru \".nomedia\". Pridané priečinky Spravovať pridané priečinky Pridať priečinok - Ak máte nejaké priečinky obsahujúce médiá, ale neboli rozpoznané aplikáciou, môžete ich tu manuálne pridať. + Ak máte nejaké priečinky obsahujúce médiá, ale neboli rozpoznané aplikáciou, môžete ich tu manuálne pridať.\n\nPridanie nových položiek sem nevylúči žiadny iný priečinok. + Nenašli sa žiadne súbory s médiami. Viete to napraviť manuálnym pridaním priečinkov, ktoré obsahujú médiá. Zmeniť veľkosť @@ -51,13 +75,20 @@ Editor - Uložiť Otočiť - Cesta - Neplatná cesta - Úprava súboru zlyhala - Upraviť súbor s: - Nebol nájdený žiadny editor + Neplatná cesta obrázka + Neplatná cesta videa + Úprava obrázka zlyhala + Úprava videa zlyhala + Úprava obrázka bola zrušená + Úprava videa bola zrušená + Súbor bol úspešne upravený + Obrázok bol úspešne upravený + Video bolo úspešne upravené + Upraviť obrázok s: + Upraviť video s: + Nebol nájdený žiadny editor obrázkov + Nebol nájdený žiadny editor videí Neznáme umiestnenie súboru Nepodarilo sa prepísať zdrojový súbor Otočiť vľavo @@ -66,51 +97,327 @@ Preklopiť Preklopiť vodorovne Preklopiť zvisle - Došlo k chybe s nedostatkom pamäte + Voľný + Iný Jednoduchá tapeta Nastaviť ako tapetu Nastavovanie ako tapeta zlyhalo Nastaviť ako tapetu s: - Nenašla sa žiadna vhodná aplikácia No app capable of it has been found Nastavuje sa tapeta… Tapeta bola úspešne zmenená Orientácia nastojato Orientácia naležato + Plocha + Obrazovka uzamknutia + Plocha a obrazovka uzamknutia + + + Prezentácia + Interval (sekundy): + Zahrnúť fotky + Zahrnúť videá + Zahrnúť GIFy + Náhodné poradie + Ísť opačným smerom + Automaticky reštartovať prezentáciu + Animácia: + Žiadna + Prelínanie + Posúvanie + Prezentácia skončila + Pre prezentáciu sa nenašli žiadne vhodné súbory + + + Zlúčiť priame podpriečinky + + + Zoskupiť podľa + Nezoskupovať súbory + Priečinka + Dátumu poslednej úpravy + Dátumu poslednej úpravy (denne) + Dátumu poslednej úpravy (mesačne) + Dátumu vytvorenia + Dátumu vytvorenia (denne) + Dátumu vytvorenia (mesačne) + Typu súboru + Prípony + Prosím vedzte, že radenie a zoskupovanie súborov sú 2 nezávislé hodnoty + + + Priečinok zobrazený vo widgete: + Zobraziť názov priečinka - Zobraziť skryté médiá Spúšťať videá automaticky + Zapamätať si pozíciu posledného prehraného videa Prepnúť viditeľnosť názvov súborov - Zobraziť médiá - Iba obrázky - Iba videá - Iba gifká - Obrázky, videá, gifká - Obrázky aj videá Automaticky reštartovať videá - Animovať gif súbory pri náhľade + Animovať GIF súbory pri náhľade Maximálny jas pri prezeraní médií Orezať náhľady na štvorce + Zobraziť dĺžku videí Otáčať obrazovku podľa Rotate fullscreen media by Systémového nastavenia Otočenia zariadenia Pomeru strán - Tmavé pozadie pri médiách na celú obrazovku + Čierne pozadie pri médiách na celú obrazovku Prehliadať miniatúry vodorovne + Automaticky skrývať systémové lišty pri celoobrazovkových médiách + Odstrániť prázdne priečinky po vymazaní ich obsahu + Povoliť ovládanie jasu vertikálnymi ťahmi + Povoliť ovládanie hlasitosti a jasu videí vertikálnymi ťahmi + Zobraziť počet médií v priečinku na hlavnej obrazovke + Zobraziť rozšírené vlastnosti ponad celoobrazovkové médiá + Spravovať rozšírené vlastnosti + Povoliť približovanie jedným prstom v celoobrazovkovom režime + Povoliť instantné prepínanie médií kliknutím na okraj obrazovky + Povoliť hlboké približovanie obrázkov + Skryť rozšírené vlastnosti ak je skrytá stavová lišta + Zobraziť niektoré akčné tlačidlá na spodku obrazovky + Zobraziť odpadkový kôš na obrazovke s priečinkami + Hlboko priblížiteľné obrázky + Zobrazovať obrázky v najlepšej možnej kvalite + Zobraziť odpadkový kôš ako poslednú položku na hlavnej obrazovke + Povoliť zatváranie celoobrazovkového režimu potiahnutím prsta dole + Povoliť 1:1 priblíženie dvojnásobným dvojklikom + Vždy otvárať videá na novej obrazovke s novými vodorovnými gestami + Zobraziť výrez obrazovky, ak je dostupný + Povoliť otáčanie obrázkov gestami + Priorita pri načítavaní súborov + Rýchlosť + Kompromis + Nezobrazovať neplatné súbory + Zobraziť typ obrázkových súborov + Povoliť približovanie videí dvojitým ťuknutím + Štýl miniatúr priečinkov + Štýl miniatúr súborov + Medzery medzi miniatúrami + Zobraziť počet súborov na samostatnom riadku + Zobraziť počet súborov v zátvorkách + Nezobrazovať počet súborov + Obmedziť názvy dlhých priečinkov na 1 riadok + Štvorec + Zaoblené rohy + + + Náhľady + Celoobrazovkový režim + Rozšírené vlastnosti + Spodné akčné tlačidlá + + + Upraviť viditeľné spodné akčné tlačidlá + Prepnutie obľúbenosti + Prepnutie viditeľnosti súboru + + + Vlastný + Reset + Štvorec + Transformovať + Filtre + Žiadny + Doladenie + Tiene + Expozícia + Zdôraznenie + Jas + Kontrast + Sýtosť + Jasnosť + Gamma + Čierne + Biele + Teplota + Ostrosť + Reset + Zaostrenie + Žiadne + Lúčovité + Lineárne + Zrkadlovité + Gaussian + Text + Nastavenia textu + Farba textu + Písmo + Pridať + Upraviť + Narovnať + Písmo + Farba + Pozadie + Zarovnanie + Dopredu + Vymazať + Váš text + Štetec + Farba + Veľkosť + Tvrdosť + Dopredu + Vymazať + Farba štetca + Editor + Zavrieť editor? + Ozaj chcete zahodiť úpravy? + Áno + Nie + Zrušiť + Prijať + Uložiť + Exportuje sa… + Exportovanie %s. + Nálepky + Farba nálepky + Možnosti nálepiek + Pridať + Farba + Odstrániť + Presunúť do popredia + Vyrovnať + Nahradiť + Nepriehľadnosť + Kontrast + Sýtosť + Jas + Nahrávky + Prekrytie + Žiadne + Stmavenie + Screen + Prekrytie + Osvetlenie + Znásobenie + Vypálenie farby + Jemné svetlo + Tvrdé svetlo + Žiadne + Zlaté + Únik svetla + Mozaika + Papier + Dážď + Vintage + Preklopiť H + Preklopiť V + Späť + Opäť spraviť + Výber farieb + Priesvitná + Biela + Sivá + Čierna + Svetlomodrá + Modrá + Fialová + Orchidea + Ružová + Červená + Oranžová + Zlatá + Žltá + Olivová + Zelená + Aquamarin + Pipetovateľná farba + Obrezať + + + Ako viem spraviť Jednoduchú Galériu predvolenou galériou zariadenia? + Najprv musíte nájsť v nastaveniach zariadenia,sekcii Aplikácie, súčasnú predvolenú galériu, zvoliť tlačidlo s textom v zmysle \"Nastavenie predvoleného otvárania\" a následne \"Vymazať predvolené nastavenia\". + Ak potom skúsite otvoriť obrázok alebo video, zobrazí sa zoznam aplikácií, kde viete zvoliť Jednoduchú Galériu a nastaviť ju ako predvolenú. + Uzamkol som apku heslom, ale zabudol som ho. Čo môžem spraviť? + Viete to vyriešǐť 2 spôsobmi. Môžete apku buď preinštalovať, alebo ju nájsť v nastaveniach zariadenia a zvoliť \"Vymazať údaje\". Vymaže to iba nastavenia, nie súbory. + Ako môžem dosiahnuť, aby bol daný album stále zobrazený prvý? + Môžete označiť daný priečinok dlhým podržaním a zvoliť tlačidlo s obrázkom pripinačky, to ho pripne na vrch. Môžete pripnúť aj viacero priečinkov, budú zoradené podľa zvoleného radenia. + Ako viem posunúť video rýchlo vpred? + Viete to dosiahnuť dvojitým kliknutím na boky obrazovky, alebo kliknutím na texty súčasnej, alebo maximálnej dĺžky videa, ktoré sú vedľa indikátora súčasného progresu. Ak v nastaveniach apky zapnete možnosť spúšťania videí na novej obrazovke, budete môcť použiť aj vodorovné gestá. + Aký je rozdiel medzi Skrytím a Vylúčením priečinka? + Kým vylúčenie predíde zobrazeniu priečinka iba vrámci Jednoduchej Galérie, skrytie ho ukryje vrámci celého systému, teda to ovplyvní aj ostatné galérie. Skrytie funguje pomocou vytvorenia prázdneho \".nomedia\" súboru v danom priečinku, ktorý viete vymazať aj nejakým správcom súborov. Vedzte ale, že na niektorých zariadeniach nie je možné ukryť priečinky ako Kamera, Screenshoty a Stiahnuté. + Prečo sa mi zobrazujú priečinky s obalmi hudobných albumov, alebo nálepkami? + Môže sa stať, že sa vám zobrazia aj nezvyčajné priečinky. Viete ich ale jednoducho ukryť pomocou ich zvolenia dlhým podržaním a zvolením Vylúčiť. Ak na nasledovnom dialógu zvolíte vylúčenie rodičovského priečinka, pravdepodobne budú vylúčené aj ostatné, podobné priečinky. + Priečinok s fotkami sa mi nezobrazuje, alebo priečinok nezobrazuje všetky položky. Čo s tým môžem urobiť? + Môže to mať viacero dôvodov, riešenie je ale jednoduché. Choďte do Nastavenia -> Spravovať pridané priečinky, zvoľte Plus a zvoľte vytúžený priečinok. + Čo v prípade, ak chcem mať zobrazených iba pár priečinkov? + Pridanie priečinka medzi Pridané Priečinky automaticky nevylúči ostatné. Môžete ale ísť do Nastavenia -> Spravovať vylúčené priečinky a zvoliť Koreňový priečinok \"/\", následne pridať žiadané priečinky v Nastavenia -> Spravovať pridané priečinky. + To spôsobí, že budú zobrazené iba vyžiadané priečinky, keďže aj vylúčenie, aj pridanie fungujú rekurzívne a ak je priečinok vylúčený, aj pridaný, bude viditeľný. + Dá sa s touto aplikáciou orezať obrázky? + Áno, orezanie je možné v editore, potiahnutím rohov obrázkov. Do editoru sa môžete dostať buď dlhým podržaním náhľadu obrázku a zvolením menu položky Upraviť, alebo zvolením Upraviť pri celoobrazovkovom režime. + Viem nejakým spôsobom zoskupiť náhľady súborov? + Áno, použitím funkcie \"Zoskupiť podľa\" na menu obrazovky s náhľadmi. Zoskupenie je možné na základe rozličných kritérií vrátane Dátumu vytvorenia. Ak použijete funkciu \"Zobraziť obsah všetkých priečinkov\", viete ich zoskupiť aj podľa priečinkov. + Radenie podľa dátumu vytvorenia nefunguje správne, ako ho viem opraviť? + Je to pravdepodobne spôsobené kopírovaním súborov. Viete to opraviť označením jednotlivých náhľadov súborov a zvoliť \"Opraviť dátum vytvorenia\". + Na obrázkoch vidno nejaké farebné pásy. Ako viem zlepšiť kvalitu obrázkov? + Súčasné riešenie funguje správne v drvivej väčšine prípadov, ak ale chcete zobraziť obrázky v lepšej kvalite, môžete povoliť možnosť \"Zobraziť obrázky v najlepšej možnej kvalite\" v nastaveniach aplikácie, v sekcií \"Hlboko priblížiteľné obrázky\". + Skryl som súbor/priečinok, ako ho viem odkryť? + Môžete buď použiť menu tlačidlo \"Dočasne zobraziť skryté položky\" na hlavnej obrazovke, alebo v nastaveniach aplikácie zapnúť možnosť \"Zobraziť skryté položky\", tým sa skryté položky zobrazia. Ak ich chcete odkryť, stačí ich dlho podržať a zvoliť možnosť \"Odkryť\". Priečinky sú skrývané pridaním skrytého súboru \".nomedia\", ten viete vymazať aj ľubovoľným správcom súborov. + Prečo apka zaberá toľko miesta? + Cache apky môže mať maximálne 250MB, zabezpečuje to rýchlejšie načítanie obrázkov. Ak je apka ešte väčšia, bude to pravdepodobne spôsobené množstvom súborov v Odpadkovom koši. Dané súbory sa počítajú do veľkosti apky. Ak ich chcete vymazať, môžete kôš buď otvoriť a položky vymazať manuálne, alebo ho môžete vysypať v nastaveniach apky. Položky v koši sú automaticky mazané po 30 dňoch. + + Jednoduchá galéria Pro - Foto organizér a editor - Galéria na prezeranie obrázkov a videí bez reklám. + Prehliadajte svoje spomienky bez prerušenia s touto foto a video galériou. - Jednoduchá nástroj použiteľný na prezeranie obrázkov a videí. Položky môžu byť zoradené podľa dátumu, veľkosti, názvu oboma smermi, obrázky je možné aj priblížiť. Položky sú zobrazované vo viacerých stĺpcoch v závislosti od veľkosti displeja, počet stĺpcov je možné meniť pomocou gesta prstami. Súbory môžete premenovať, zdieľať, mazať, kopírovať, premiestňovaŤ. Obrázky môžete orezať, otočiť, alebo nastaviť ako tapeta priamo v aplikácií. + Jednoduchá Galéria Pro je vysoko prispôsobiteľná offline galéria. Organizujte a upravujte vaše fotky, obnovujte vymazané súbory pomocou odpadkového koša, ochraňujte a skrývajte ich, alebo prehliadajte množstvo rôznych foto a video formátov vrátane RAW, SVG a mnoho ďalších. - Galéria je tiež poskytovaná pre použitie treťou stranou pre prehliadanie fotiek a videí, pridávanie príloh v emailových klientoch. Je perfektná na každodenné použitie. + Apka neobsahuje žiadne reklamy, ani nepotrebné oprávnenia. Keďže apka nemá prístup na internet, vaše súkromie je chránené. - Neobsahuje žiadne reklamy a nepotrebné oprávnenia. Je opensource, poskytuje možnosť zmeny farieb. + ------------------------------------------------- + JEDNODUCHÁ GALÉRIA PRO - FUNKCIE + ------------------------------------------------- - Táto aplikácia je iba jednou zo skupiny aplikácií. Ostatné viete nájsť na http://www.simplemobiletools.com + • Offline galéria bez reklám a vyskakujúcich okien + • Editor Jednoduchej Galérie - orezávajte, otáčajte, meňte veľkosť, kreslite, používajte filtre a viac + • Nie je potrebný prístup na internet, vďaka čomu máte viac súkromia a bezpečia + • Nie sú potrebné žiadne podozrivé oprávnenia + • Rýchlo prehliadajte obrázky a videá + • Otvárajte mnoho rôznych typov fotiek a videí (RAW, SVG, panoramatické atď) + • Množstvo intuitívnych gest pre jednoduchú úpravu a organizovanie súborov + • Veľa rôznych spôsobov filtrovania, zoskupovania a radenia súborov + • Zmeňte si vzhľad Jednoduchej Galérie + • Dostupná v 32 jazykoch + • Označte obľúbené súbory pre rýchly prístup + • Chráňte vaše obrázky a videá vzorom, PIN kódom alebo odtlačkom prstov + • Použite PIN kód, vzor, alebo odtlačky prstov na zabezpečenie spúšťania apky, alebo niektorých funkcií + • Obnovte vymazané fotky a videá pomocou odpadkového koša + • Prepnite viditeľnosť súborov pre ich ochranu + • Vytvorte zo súborov prezentáciu + • Prezrite si detailné informácie o súboroch (rozlíšenie, EXIF hodnoty atď) + • Jednoduchá Galéria Pro má otvorený zdrojový kód + … a mnoho mnoho ďalšieho! + + EDITOR OBRÁZKOV + Jednoduchá Galéria Pro umožňuje ľahkú úpravu obrázkov. Orezávajte, preklápajte, alebo meňte ich veľkosť. Ak sa cítite kreatívne, môžete aj aplikovať filtre, alebo na nich kresliť! + + PODPORA PRE MNOŽSTVO TYPOV SÚBOROV + Na rozdiel od niektorých galérií podporuje Jednoduchá Galéria množstvo rôznych druhov súborov vrátane JPEG, PNG, MP4, MKV, RAW, SVG, panoramatických fotografií a videí. + + VYSOKO UPRAVITEĽNÝ SPRÁVCA SÚBOROV + Od vzhľadu po funkcie na spodej lište, Jednoduchá Galéria je upraviteľná a bude fungovať presne tak, ako chcete. Žiadna iná galéria nie je taká flexibilná! Vďaka otvorenému zdrojovému kódu je apka dostupná v 32 jazykoch! + + OBNOVTE VYMAZANÉ FOTKY A VIDEÁ + Vymazali ste náhodou dôležitú fotku, alebo video? Jednoduchá Galéria Pro obsahuje užitočný odpadkový kôš, vďaka ktorému viete jednoducho obnoviť vymazané súbory. + + OCHRÁŇTE A SKRÝVAJTE FOTOGRAFIE A VIDEÁ + Použitím PIN kódu, vzoru, alebo odtlačkov prstov viete vaše súbory ochrániť, alebo ukryť. Taktiež viete ochrániť aj buď spúšťanie apky ako takej, alebo niektoré jej funkcie. Napríklad tým viete zabrániť náhodnému vymazaniu súborov bez vašich odtlačkov prstov. + + Pozrite si celú sadu aplikácií na: + https://www.simplemobiletools.com + + Vlastná stránka Jednoduchej Galérie Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + Filtriranje datotek + Slike + Videoposnetki + GIFi + RAW slike + SVGji + Portraits + Na podlagi izbranih filtrov ne najdem nobenih medijskih datotek. + Spremeni filtre + + + Ta funkcija skrije mapo tako, da vanjo doda \'.nomedia\' datoteko, kar skrije tudi vse podmape. Ponovno jih lahko prikažete z uporabo možnosti \'Prikaži skrite elemente\' v Nastavitvah. Nadaljujem? + Izključi + Izključene mape + Urejaj izključene mape + Izbira bo skupaj s podmapami izključena zgolj iz te Galerije. Izključene mape lahko urejate v Nastavitvah. + Namesto tega izključim nadredno mapo? + Izključevanje map jih bo skupaj s podmapami skrilo zgolj v tej Galeriji, v ostalih aplikacijah bodo normalno vidne.\n\nČe jih želite skriti tudi v drugih aplikacijah, uporabite funkcijo Skrij. + Odstrani vse + Odstranim vse mape iz seznama izključenih? Mape ne bodo izbrisane. + Skrite mape + Urejaj skrite mape + Kot kaže skrite mape z \".nomedia\" datoteko ne obstajajo. + + + Vključene mape + Urejaj vključene mape + Dodaj mapo + Če imate mape, ki vsebujejo medijske datoteke, ki jih aplikacija ni prepoznala, jih lahko ročno dodate tukaj.\n\nDodajanje novih elementov ne bo izključilo drugih. + No media files have been found. You can solve it by adding the folders containing media files manually. + + + Spremeni velikost + Spremeni velikost izbora in shrani + Širina + Višina + Obdrži razmerje stranic + Vnesite veljavno ločljivost + + + Urejevalnik + Zavrti + Napačna pot + Invalid video path + Urejanje slike ni uspelo + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + Uredi sliko z: + Edit video with: + Ne najdem urejevalnika slik + No video editor found + Neznana lokacija datoteke + Ne morem prepisati izvorne datoteke + Zavrti levo + Zavrti desno + Zavrti za 180º + Zrcaljenje + Zrcali horizontalno + Zrcali vertikalno + Prosto + Drugo + + + Simple ozadje + Nastavi kot ozadje + Nastavljanje ozadja ni uspelo + Nastavi kot ozadje z: + Nastavljam ozadje… + Ozadje uspešno nastavljeno + Pokončno razmerje stranic + Ležeče razmerje stranic + Domači zaslon + Zaklenjeni zaslon + Domači in zaklenjeni zaslon + + + Diaprojekcija + Interval (sekund): + Vključi fotografije + Vključi videoposnetke + Vključi GIFe + Naključni vrstni red + Premik nazaj + Ponavljaj diaprojekcijo + Animation + None + Fade + Slide + Diaprojekcija se je zaključila + Ne najdem datotek za diaprojekcijo + + + Združi neposredne podmape + + + Združi po + Ne združuj datotek + Mapa + Zadnjič spremenjeno + Last modified (daily) + Last modified (monthly) + Posneto + Date taken (daily) + Date taken (monthly) + Tip datoteke + Končnica + Združevanje in sortiranje sta dve neodvisni polji. + + + Mapa uporabljena na pripomočku: + Prikaži ime mape + + + Avtomatično predvajaj videoposnetke + Zapomni si zadnji položaj predvajanja + Preklopi vidljivost imen datotek + Ponavljaj videoposnetke + Animiraj GIFe v predogledu + Najvišja svetlost pri celozaslonskem predvajanju + Obreži predoglede slik v kvadrate + Prikaži trajanje posnetkov + Zavrti celozaslonski medij za + Sistemska nastavitev + Obračanje naprave + Razmerje stranic + Črno ozadje in statusna vrstica pri celozaslonskem mediju + Horizontalno pomikanje sličic + Avtomatično skrij sistemski UI v celozaslonskem načinu + Izbriši prazne mape po brisanju njihove vsebine + Dovoli nadzor svetlosti fotografije z vertikalnimi gestami + Dovoli nadzor glasnosti in svetlosti videoposnetka z vertikalnimi gestami + Pokaži število elementov v glavnem pogledu + Prikaži razširjene podrobnosti nad celozaslonskim prikazom + Urejaj razširjene podrobnosti + Dovoli enoprstno povečavo v celozaslonskem načinu + Dovoli takojšnje spremembe medija s klikanjem na robove zaslona + Dovoli globoko povečavo slik + Skrij razširjene podrobnosti, ko je statusna vrstica skrita + Prikaži določene akcijske gumbe na dnu zaslona + Prikaži Koš na zaslonih map + Globoko povečljive slike + Prikaži slike v največji možni kvaliteti + Prikaži Koš kot zadnji element na glavnem zaslonu + Dovoli zapiranje celozaslonskega načina z gesto navzdol + Dovoli 1:1 povečavo z dvojnim pritiskom + Vedno odpri videoposnetke na ločenem zaslonu z novimi horizontalnimi gestami + Prikaži notch, če je na voljo + Dovoli obračanje slik z gestami + Prioriteta pri nalaganju datotek + Hitrost + Kompromis + Izogni se prikazovanju napačnih datotek + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Sličice + Celozaslonski prikaz + Razširjene podrobnosti + Akcije na dnu + + + Urejaj vidne akcije na dnu + Preklopi priljubljene + Preklopi vidljivost datotek + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + Kako naredim Simple galerijo za privzeto aplikacijo na napravi? + Najprej morate najti trenutno privzeto aplikacijo za prikaz slik med aplikacijami v nastavitvah naprave, kjer poiščete gumb v smislu \"Odpri kot privzeto\", kliknite nanj in izberite \"Izbriši privzeto\". + Ko boste naslednjič želeli odpreti fotografijo ali videoposnetek, bi se moral pokazati izbirnik, v katerem lahko izberete Simple galerijo in jo določite kot privzeto aplikacijo. + Aplikacijo sem zaklenil z geslom, ki se ga ne spomnim več. Kaj lahko naredimo? + To lahko rešite na 2 načina. Lahko ali ponovno namestite aplikacijo ali pa jo poiščete v nastavitvah in kliknete \"Počisti podatke\". To bo ponastavilo vaše nastavitve, ne bo pa izbrisalo nobenih datotek. + Kako nastaviti, da se določen album vedno prikaže na vrhu? + Z dolgim pritiskom na album se vam prikaže meni, v katerem je na voljo bucika, s katero pripnete album na željeno mesto. Na ta način lahko pripnete več albumov, ki bodo razvrščeni v skladu s privzetim načinom razvrščanja. + Ali lahko hitro predvajam videoposnetke? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Kakšna je razlika med skrivanjem in izključevanjem mape? + Izključevanje mape jo skrije le v Simple galeriji, medtem ko jo skrivanje skrije tudi v ostalih aplikacijah oz. galerijah. Deluje tako, da kreira prazno \".nomedia\" datoteko v izbrani mapi, katero lahko odstranite tudi s katerimkoli urejevalnikom datotek. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Zakaj se v galeriji prikažejo datoteke z naslovnicami glasbenih map ali nalepk? + Lahko se zgodi, da se vam prikažejo nepoznani albumi, ki vsebujejo tovrstne datoteke. Lahko jih preprosto skrijete tako, da z dolgim pritiskom na album prikličete meni in tam izberete Izključi. V naslednjem oknu lahko izberete tudi nadrejeno mapo, obstaja pa verjetnost, da bo to preprečilo tudi prikazovanje ostalih povezanih albumov. + Mapa s slikami se ne prikaže. Kaj naj naredim? + Razlogi so lahko različni, rešitev je vseeno preprosta. Pojdite v Nastavitve in izberite Urejaj vključene mape, izberite plus in poiščite željeno mapo. + Kaj, če želim prikazati le nekaj izbranih map? + Dodajanje mape med vključene mape avtomatično ne izključi ničesar. Lahko pa naredite sledeče: v Nastavitvah -> Urejaj izključene mape izključite korensko mapo \"/\", željene mape pa dodajte v Nastavitvah -> Urejaj vključene mape. + To bo naredilo vidne le vključene mape, saj sta tako vključevanje kot tudi izključevanje rekurzivna, kar pomeni, da je mapa, ki je istočasno izključena in vključena, vidna. + Ali lahko obrezujem slike s to aplikacijo? + Da, slike lahko obrezujete z vlečenjem kotov slike. Do urejevalnika lahko pridete z dolgim pritiskom na sličico fotografije in izborom opcije Uredi ali izborom funkcije Uredi iz celozaslonskega načina prikaza. + Ali lahko združujem sličice medijskih datotek? + Seveda, uporabite \"Združi po\" opcijo v meniju, ki je na voljo v pregledu sličic. Datoteke lahko združujete po različnih kriterijih, vključujoč po datumu posnetka. Če uporabite funkcijo \"Prikaži vso vsebino map\" jih lahko združujete tudi po mapah. + Kot kaže razvrščanje po datumu posnetka ne deluje pravilno. Kako lahko to popravim? + To se najverjetneje zgodi po kopiranju datotek iz nekje drugje. To lahko popravite tako, da izberete sličice in uporabite funkcijo \"Popravi datum posnetka\". + Opaziti je slabo barvno povezovanje na slikah. Kako lahko izboljšam kvaliteto? + Trenutna rešitev prikazovanja slik deluje dobro v veliki večini primerov, če pa vseeno želite višjo kvaliteto, lahko uporabite funkcijo \"Prikaži slike v najvišji možni kvaliteti\" v Nastavitvah v razdelku \"Globoko povečljive slike\". + Skril sem mapo/datoteko. Kako jo lahko zopet prikažem? + Lahko uporabite funkcijo \"Začasno prikaži skrite elemente\", ki se nahaja v meniju na glavnem zaslonu ali preklopite \"Prikaži skrite elemente\" v Nastavitvah aplikacije. Če želite element označiti kot viden, z dolgim pritiskom nanj prikličite meni in izberite \"Prikaži\". Skrivanje map deluje tako, da se kreira prazno \".nomedia\" datoteko v izbrani mapi, ki jo lahko odstranite tudi s katerimkoli urejevalnikom datotek. + Zakaj aplikacija zaseda toliko prostora? + Predpomnilnik aplikacije lahko zasede do 250MB, zagotavlja pa hitrejše nalaganje fotografij. Če aplikacija zaseda še več prostora, je to najverjetneje zaradi velikega števila elementov v košu. Tudi te datoteke se vštevajo v velikost aplikacije. Ročno odstranite datoteke iz koša. Vsaka datoteka v košu je sicer avtomatično izbrisana po 30 dneh. + + + + Simple Gallery Pro - Pregledovalnik fotografij + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro je visoko prilagodljiva lokalna galerija. Organizirajte & urejajte vaše fotografije, vrnite izbrisane datoteke iz koša, zaščitite & skrijte datoteke in pregledujte ogromno različnih vrst foto & video formatov, vključujoč RAW, SVG in mnoge druge. + + Aplikacije ne vsebuje reklam in nepotrebnih dovoljenj. Prav tako aplikacija ne potrebuje internetnega dostopa, zato je vaša zasebnost zagotovljena. + + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- + + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml new file mode 100644 index 000000000..e2b6fe3ce --- /dev/null +++ b/app/src/main/res/values-sr/strings.xml @@ -0,0 +1,427 @@ + + + Једноставна галерија + Галерија + Измени + Отвори камеру + (скривено) + (изузето) + Прикачи фасциклу + Раскачи фасциклу + Закачи на врх + Прикажи садржај свих фасцикли + Све фасцикле + Пређи на преглед фасцикли + Друга фасцикла + Прикажи на мапи + Непозната локација + Јачина звука + Осветљење + Закључај оријентацију + Откључај оријентацију + Измени оријентацију + Форсирај портрет + Форсирај пејзаж + Користи подразумевајућу оријентацију + Поправи вредност за датум креирања + Исправљам… + Датуми успешно исправљени + No Date Taken values have been found + Подели верзију са промењеним димензијама + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Switch to file search across all visible folders + Set as default folder + Unset as default folder + + + Филтрирај медију + Слике + Видео снимци + ГИФови + Сирове слике + СВГови + Portraits + Нема пронађених медија датотека са изабраним филтерима. + Измени филтере + + + Ова функција скрива фасциклу додавањем \'.nomedia\' фајла у њу, она ће такође сакрити све подфасцикле. Можете их видети притиском на опцију \'Прикажи скривене ставке\' у подешавањима. Настави? + Изузми + Изузете фасцикле + Управљај изузетим фасциклама + Ово ће изузети одабир заједно са подфасциклама само из Једноставне галерије. Можете управљати изузетим фасциклама у подешавањима. + Изузми родитеља уместо овог? + Изузимање фасцикли ће их заједно са њиховим подфасциклама учинити скривеним само у Једноставној галерији, они ће и даље бити видљиви у другим апликацијама. \n\nАко желите да их сакријете од других апликација, употребите Сакриј функцију. + Уклони све + Уклони све фасцикле са листе изузетих? Ово неће обрисати фасцикле. + Скривене фасцикле + Управљај скривеним фасциклама + Изгледа да немате ни једну фасциклу скривену са \".nomedia\" датотеком. + + + Укључене фасцикле + Управљај укљученим фасциклама + Додај фасциклу + Ако имате неке фасцикле које садрже медију, али нису препознате од стране апликације, можете их додати ручно овде. \n\nДодавањем неких ставки овде нећете изузети неку другу фасциклу. + No media files have been found. You can solve it by adding the folders containing media files manually. + + + Промена величине + Промени величину одабраног и сачувај + Ширина + Висина + Задржи пропорције + Унесите исправну резолуцију + + + Едитор + Ротирај + Неисправна стаза слике + Invalid video path + Измена слике неуспешна + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + Измени слику са: + Edit video with: + Није пронађен едитор слика + No video editor found + Непозната локација датотеке + Не могу да препишем изворну датотеку + Ротирај лево + Ротирај десно + Ротирај за 180º + Заврти + Заврти хоризонтално + Заврти вертикално + Слободна пропорција + Друга пропорција + + + Једноставна позадина + Подеси као позадину + Подешавање позадине неуспешно + Подеси као позадину са: + Подешавам позадину… + Позадина је успешно подешена + Пропорција портрета + Пропорција пејзажа + Почетни екран + Закључај екран + Почетни и закључани екран + + + Слајдшоу + Интервал (секунди): + Садржи слике + Садржи видео снимке + Садржи ГИФове + Насумични редослед + Помери уназад + Понављај слајдшоу + Animation + None + Fade + Slide + Слајдшоу се завршио + Нису пронађени медији за слајдшоу + + + Групирај директне подфасцикле + + + Групиши према + Не групиши датотеке + Фасцикла + Задње измењено + Last modified (daily) + Last modified (monthly) + Датум настанка + Date taken (daily) + Date taken (monthly) + Тип датотеке + Тип датотеке + Имајте на уму да су груписање и сортирање 2 различите ствари + + + Фасцикла приказана у виџету: + Прикажи име фасцикле + + + Пуштај видео снимке аутоматски + Запамти позицију задње пуштаног видеа + Измени видљивост датотеке + Пуштај видео снимке у недоглед + Анимирај ГИФове на сличицама + Максимално осветљење када се пушта медиј преко целог екрана + Скрати сличице у квадрате + Прикажи дужину видео снимака + Ротирај медиј преко целог екрана за + Системска подешавања + Ротација уређаја + Пропорције + Црна позадина на приказу преко целог екрана + Скролуј сличице хоризонтално + Аутоматски сакриј системски кориснички интерфејс приликом пуштања преко целог екрана + Обриши празне фасцикле након брисања њиховог садржаја + Дозволи контролисање осветљења слика са вертикалним покретима + Дозволи контролисање јачине звука видео снимка и осветљења са вертикалним покретима + Прикажи број медија у фасцикли на главном прегледу + Прикажи више детаља преко медија пуштеног преко целог екрана + Управљај дотатним детаљима + Омогући зумирање једним прстом код медија на целом екрану + Дозволи брзо мењање медија кликом на стране екрана + Дозволи дубоко зумирање слика + Сакриј додатне детаље када је статусна трака скривена + Прикажи дугмиће за неке акције на дну екрана + Прикажи канту за отпатке на екрану са фасциклама + Дубоко зумирајуће слике + Прикажи слике у највећем могућем квалитету + Прикажи канту за отпатке као задњу ставку на главном екрану + Дозволи затварање приказа преко целог екрана са покретом на доле + Дозволи 1:1 зумирање са два дводупла притиска на екран + Увек отварај видео снимке наз асебном екрану са новим хоризонталним гестикулацијама + Прикажи исечак уколико је доступан + Дозволи ротацију слика гестикулацијама + Приоритет приликом учитавања фајла + Брзина + Компромис + Не приказуј оштећене датотеке + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Сличице + Медији преко целог екрана + Додатни детаљи + Акције на дну + + + Управљај са видљивим акцијама на дну + Укључи/искључи омиљени + Укључи/искључи видљивост датотеке + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + Како да подесим Једноставну галерију да буде главна галерија уређаја? + Први морате да нађете тренутну главну галерију у Апликације секцији подешавања вашег уређаја, потражите дугме које каже нешто попут \"Отвори по дифолту\", кликни на то, затим изабери \"Уклони дифолт\". + Следећи пут када покушате да отворите слику или видео требало би да видите изабирач апликација, где можете да изаберете Једноставну галерију и учините је подразумевајућом апликацијом. + Закључао сам апликацију са шифром и заборавио шифру. Како да решим проблем? + Постоје 2 начина. Можете да обришете апликацију, или да нађете апликацију у подешавањима и кликнете на \"Обриши податке\". То ће ресетовати сва подешавања, али неће обрисати медија фајлове. + Како да подесим да се неки албум увек појављује на врху? + Дуго притисните на жељени албум и изаберите Закачи икону у менију за акције, то ће га поставити на врх. Можете да закачите више фасцикли истовремено, с тим што ће бити сортирани према подразумевајућем методу за сортирање. + Како да премотавам видео снимке? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Која је разлика између скривања и изузимања фасцикле? + Изузимање спречава приказивање фасцикле само у Једноставној галерији, док се скривање односи на цео систем и скрива фасциклу од свих других галерија. Он функционише тако што прави празан \".nomedia\" фајл у задатој фасцикли, који затим можете да уклоните са било којим фајл менаџером. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Зашто се фасцикле са сликама музичких извођача или налепницама приказују? + Може се десити да се приказују неки необични албуми. Можете их лако изузети дугим притиском, а затим одабиром Изузми. У следећем дијалогу можете изабрати фасциклу родитеља, шансе су да ће спречити све остале албуме од приказивања. + Фасцикла са сликама се не приказује, или не приказује све ставке. Шта да урадим? + Може бити више разлога, али решење је лако. Идите у Подешавања -> Управљај садржаним фасциклама, изаберите плус и изаберите жељену фасциклу. + Шта ако желим само неколико одређених фасцикли у приказу? + Додавањем фасцикли у Садржаним фасциклама не изузима аутоматски ништа. Оно што можете је да одете у Подешавања -> Управљај изузетим фасциклама, изузмете почетну фасциклу \"/\", а затим додате жељене фасцикле у Подешавања -> Управљај садржаним фасциклама. + То ће учинити да се само одређене фасцикле приказују, јер и изузимање и укључивање су рекурзивни и ако је нека фасцикла и изузета и садржана, она ће се појавити. + Да ли могу да исечем слику са овом апликацијом? + Да, можете да сечете слике са едитором, повлачењем ивица слике. Можете ући у едитор дугим притиском на сличицу слике и одабирањем Измени, или избором Измени из приказа преко целог екрана. + Да ли могу да групишем сличице медија? + Да, само употребите \"Групиши према\" мени ставку док сте у прегледу са сличицама. Можете да групишете датотеке према више критеријума, укључујући датум креирања. Ако користите \"Прикажи садржај свих фасцикли\" функцију, можете да их групишете и према фасциклама. + Сортирање према датуму креирања не ради како треба, како да га поправим? + Највероватнији узрок су датотеке које се копирају од некле. То можете поправити селектовањем сличице датотеке и изабиром \"Исправи датум креирања\". + Имам проблема са приказом боја у сликама. Како да унапредим квалитет приказа слика? + Тренутно решење за приказивање слика функционише добро у већини случајева, али ако хоћете још бољи квалитет слика, можете да укључите \"Прикажи слике у најбољем могућем квалитету\" у подешавањима апликације, у \"Дубоко зумирање слика\" секцији. + Имам скривену датотеку/фасциклу. Како да је приказујем поново? + Можете да притиснете \"Привремено прикажи скривене ставке\" мени ставку на главном екрану, или да измените \"Прикажи скривене ставке\" у подешавањима апликације, да видите скривене ставке. Ако желите да је учините видљивом, једноставно је дуго притисните и изабеерите \"Откриј\". Фасцикле су скривене додавањем скривене \".nomedia\" датотеке у њих, које можете да обришете у било ком менаџеру датотека. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + + + Simple Gallery Pro - Photo Manager & Editor + + Browse your memories without any interruptions with this photo and video gallery + + Једноставна галерија Про је високо прилагодљива галерија којој није неопходан интернет да би радила. Организуј и измени своје слике, опорави обрисане датотеке са кантом за отпатке, заштити и сакриј датотеке имај увид у огромну количину различитих фотографија и видео формата укључујући RAW, SVG и многих других. + + Апликација не садржи огласе и сувишне дозволе. С обзиром да апликација не захтева приступ интернету, ваша приватност је заштићена. + + ------------------------------------------------- + ЈЕДНОСТАВНА ГАЛЕРИЈА ПРО – МОГУћНОСТИ + ------------------------------------------------- + + • Галерија којој није неопходан интернет, не садржи огласе и искачуће рекламе + • Једноставна галерија фото едитор - исеци, ротирај, измени димензије, цртај, примени филтере и још тога + • Није вам неопходан приступ интернету за рад, што вам даје више приватности и сигурности + • Нису неопходне сувишне дозволе за рад галерије + • Брза претрага слика, видео снимака и датотека + • Отвори и прегледај доста различитих фотографија и видео типова (RAW, SVG, panoramic итд) + • Разноликост интуитивних гестикулација да на лак начин измените и организујете датотеке + • Доста начина за филтрирање, груписање и сортирање датотека + • Прилагодите изглед Једноставне галерије Про + • Доступна на 32 језика + • Означите датотеке као омиљене да имате брз приступ истим + • Заштитите ваше фотографије и видео снимке са шаблоном, пином или отиском прста + • Употребите пин, шаблон и отисак прста да заштитите покретање апликације или одређене функције + • Опоравите обрисане фотографије и видео снимке из канте за отпатке + • Измените видљивост фајлова да сакријете фотографије и видео снимке + • Направите прилагођени слајдшоу за ваше датотеке + • Имајте увид у детаљне информације ваших датотека (резолуције, EXIF вредности итд..) + • Једноставна галерија Про је отвореног кода + … и још доста тога! + + ЕДИТОР ФОТО ГАЛЕРИЈЕ + Једноставна галерија Про чини једноставним да измените ваше слике у ходу. Исеците, заокрените, ротирајте или измените величину ваших слика. Ако се осећате креативним, можете додати филтере или насликати своје слике! + + ПОДРШКА ЗА МНОГЕ ТИПОВЕ ФАЈЛОВА + За разлику од неких других галерија програма и организатора фотографија, Једноставна галерија Про подржава велики спектар различитих типова датотека укључујући JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic фотографије, Panoramic видео снимци и још доста других. + + ВИСОКО ПРИЛАГОДЉИВ МЕНАЏЕР ГАЛЕРИЈА + Од корисничког интерфејса до функцијских дугмића на доњој линији са алаткама, Једноставна галерија Про је високо прилагодљива и ради онако како ви желите. Ни један други менаџер за галерију има ову врсту флексибилности! Захваљујући томе што је отвореног кода, ми смо доступни на 33 језика! + + ОПОРАВИ ОБРИСАНЕ ФОТОГРАФИЈЕ И ВИДЕО СНИМКЕ + Случајно обрисане драгоцене фотографије или видео снимци? Не брините! Једноставна галерија Про поседује згодну канту за отпатке из које можете опоравити обрисане фотографије и видео снимке на лак начин. + + ЗАШТИТИ И САКРИЈ ФОТОГРАФИЈЕ, ВИДЕО СНИМКЕ и ДАТОТЕКЕ + Употребом пина, шаблона или скенера за отисак прста на вашем уређају можете заштитити фотографије, видео снимке и читаве албуме. Можете заштитити и саму апликацију или ставити кључеве на одређене функције у апликацији. Например, не могу се обрисати датотеке без скенирања отиска прста, што у великој мери може спречити нежељено или случајно брисање. + + Погледајте цео пакет Simple Tools овде: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Фејсбук: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index a0c67d09e..7957d91ea 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1,45 +1,69 @@ Simple Gallery - Gallery + Galleri Redigera - Starta kameran - Öppna med - Hittade ingen giltig app + Öppna kameran (dold) - Fäst mappen - Släpp mappen + (utesluten) + Fäst mapp + Lossa mapp + Fäst högst upp Visa alla mappars innehåll Alla mappar Byt till mappvy - Andra mappar - Show on map - Unknown location - No application with maps has been found - No Camera app has been found - Increase column count - Reduce column count - Temporarily show hidden - Change cover image - Select photo - Use default + Annan mapp + Visa på karta + Okänd plats + Volym + Ljusstyrka + Aktivera rotationslås + Inaktivera rotationslås + Ändra orientering + Lås skärmen i stående läge + Lås skärmen i liggande läge + Använd standardorientering + Korrigera fotodatum + Korrigerar… + Datumen har korrigerats + No Date Taken values have been found + Dela en version med ändrad storlek + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Växla till filsökning i alla synliga mappar + Set as default folder + Unset as default folder + + + Filtrera media + Bilder + Videor + GIF-bilder + RAW-bilder + SVG-bilder + Portraits + Inga mediefiler hittades med valda filter. + Ändra filter - This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden folders\' option in Settings. Continue? - Exclude - Excluded folders - Manage excluded folders - This will exclude the selection together with its subfolders from Simple Gallery only. You can manage excluded folders in Settings. - Exclude a parent instead? - Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\\n\\nIf you want to hide them from other apps too, use the Hide function. - Remove all - Remove all folders from the list of excluded? This will not delete the folders. + Denna funktion döljer mappen och alla dess undermappar genom att lägga till en \'.nomedia\'-fil i den. Du kan se dem genom att växla \'Visa dolda objekt\'-alternativet i Inställningar. Vill du fortsätta? + Uteslut + Uteslutna mappar + Hantera uteslutna mappar + Detta utesluter bara den markerade mappen och dess undermappar från Simple Gallery. Du kan hantera uteslutna mappar i Inställningar. + Vill du utesluta en överordnad mapp istället? + Uteslutning av mappar döljer bara dem och deras undermappar i Simple Gallery, de visas fortfarande i andra appar.\n\nAnvänd Dölj-funktionen om du även vill dölja dem från andra appar. + Ta bort alla + Vill du ta bort alla mappar från uteslutningslistan? Detta raderar inte mapparna. + Dolda mappar + Hantera dolda mappar + Det verkar som att inga mappar har dolts med \".nomedia\"-filer. - Included folders - Manage included folders - Add folder - If you have some folders which contain media, but were not recognized by the app, you can add them manually here. + Inkluderade mappar + Hantera inkluderade mappar + Lägg till mapp + Om du har vissa mappar som innehåller media men som inte känns igen av appen, kan du lägga till dem manuellt här. + Inga mediefiler hittades. Du kan lägga till de mappar som innehåller mediefiler manuellt. Ändra storlek @@ -50,67 +74,350 @@ Ange en giltig bildupplösning - Redigera - Spara + Redigerare Rotera - Sökväg Ogiltig bildsökväg - Image editing failed + Invalid video path + Bilden kunde inte redigeras + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully Redigera bilden med: - Hittade ingen bildredigerare + Edit video with: + Ingen bildredigerare hittades + No video editor found Okänd filplats Det gick inte att skriva över källfilen - Rotate left - Rotate right - Rotate by 180º - Flip - Flip horizontally - Flip vertically - Out of memory error + Rotera åt vänster + Rotera åt höger + Rotera 180º + Vänd + Vänd horisontellt + Vänd vertikalt + Fritt + Annat - Bakgrundsbild - Ange som bakgrundsbild - Det gick inte att byta bakgrundsbild - Ange som bakgrundsbild med: - Hittade ingen app som klarar av detta - Inställningar för bakgrundsbild… - Bakgrundsbilden är ändrad - Portrait aspect ratio - Landscape aspect ratio + Bakgrund + Använd som bakgrund + Det gick inte att ställa in bakgrunden + Ställ in som bakgrund med: + Ställer in bakgrunden… + Bakgrunden har ställts in + Stående bildförhållande + Liggande bildförhållande + Startskärm + Låsskärm + Startskärm och låsskärm + + + Bildspel + Intervall (sekunder): + Inkludera foton + Inkludera videor + Inkludera GIF-bilder + Spela upp i slumpmässig ordning + Spela upp i omvänd ordning + Spela upp i en slinga + Animation + Ingen + Tona + Glid + Bildspelet har avslutats + Ingen media hittades för bildspelet + + + Gruppera direkta undermappar + + + Gruppera efter + Gruppera inte filer + Mapp + Senast ändrad + Senast ändrad (dag) + Senast ändrad (månad) + Fotodatum + Fotodatum (dag) + Fotodatum (månad) + Filtyp + Filnamnstillägg + Observera att gruppering och sortering är två oberoende funktioner + + + Mapp som visas i widgeten: + Visa mappnamnet - Visa dolda mappar - Spela upp videos automatiskt - Visa/dölj filnamnen - Visa media - Endast bilder - Endast videos - Gifs only - Images, videos, gifs - Bilder och videos - Återspela videos - Animate gifs at thumbnails - Max brightness when viewing media - Crop thumbnails into squares - Rotate fullscreen media by - System setting - Device rotation - Aspect ratio - Dark background at fullscreen media - Scroll thumbnails horizontally + Spela upp videor automatiskt + Kom ihåg senaste videouppspelningsposition + Visa/dölj filnamn + Spela upp videor om och om igen + Animera GIF-bilders miniatyrer + Maximal ljusstyrka när media visas i helskärmsläge + Beskär miniatyrer till kvadrater + Visa videolängder + Rotera media i helskärmsläge + Systeminställning + Enhetens rotation + Bildförhållande + Svart bakgrund när media visas i helskärmsläge + Rulla horisontellt genom miniatyrer + Dölj systemanvändargränssnittet automatiskt när media visas i helskärmsläge + Ta bort tomma mappar när deras innehåll tas bort + Tillåt styrning av fotoljusstyrka med vertikala gester + Tillåt styrning av videovolym och videoljusstyrka med vertikala gester + Visa antalet mediefiler i varje mapp i huvudvyn + Visa utökad information över media i helskärmsläge + Hantera utökad information + Tillåt zoomning med ett finger när media visas i helskärmsläge + Tillåt snabbyte av media genom tryckning på skärmens kanter + Tillåt djupzoomning av bilder + Dölj utökad information när statusfältet är dolt + Visa några åtgärdsknappar längst ned på skärmen + Visa Papperskorgen i mappvyn + Djupt zoombara bilder + Visa bilder i högsta möjliga kvalitet + Visa Papperskorgen som det sista objektet i huvudvyn + Tillåt avslutning av helskärmsläget med en nedåtgest + Tillåt 1:1-zoomning med två dubbeltryck + Öppna alltid videor i en separat vy med nya horisontella gester + Show a notch if available + Allow rotating images with gestures + File loading priority + Speed + Compromise + Avoid showing invalid files + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Miniatyrer + Visning av media i helskärmsläge + Utökad information + Åtgärder längst ned på skärmen + + + Hantera synliga åtgärder längst ned på skärmen + Lägg till/ta bort från favoriter + Visa/dölj fil + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + How can I make Simple Gallery the default device gallery? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + Simple Gallery Pro - Photo Manager & Editor - Ett Galleri för att visa bilder och videos utan en massa reklam. + Browse your memories without any interruptions with this photo and video gallery - Ett enkelt verktyg för att visa bilder och vdeos. Objekten kan sorteras efter datum, storlek, namn både stigande och fallande, bilder kan zoomas in. Mediafiler visas i flera kolumner beroende av skärmens storlek, du kan ändra antalet kolumner genom en nyp-rörelse. De går att döpa om, dela, ta bort, kopiera, flytta. Bilder kan också beskäras, roteras och anges som bakgrundsbild direkt från appen. + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. - Galleriet kan också användas av tredjeparts för förhandsgranskning av bilder / videos, bifoga bilagor i e-postklienter etc. Den är perfekt för det dagliga användandet. + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. - Innehåller ingen reklam eller onödiga behörigheter. Det är helt och hållet opensource, innehåller anpassningsbara färger. + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- - Detta är bara en app i en serie av appar. Du hittar resten av dem här http://www.simplemobiletools.com + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + Medyayı filtrele + Resimler + Videolar + GIF\'ler + RAW resimler + SVG\'ler + Portreler + Seçilen filtrelerle hiçbir medya dosyası bulunamadı. + Filtreleri değiştir - Bu işlev, klasöre\'.medya yok\'dosyası ekleyerek gizler; tüm alt klasörleri de gizler. Bunları Ayarlar\'da\'Gizli klasörleri göster\'seçeneğine basarak görebilirsiniz. Devam et? - Dışlama + Bu işlev, klasöre bir \'.nomedia\' dosyası ekleyerek gizler, tüm alt klasörleri de gizler. Ayarlar\'dan \'Gizli öğeleri göster\' seçeneğini değiştirerek onları görebilirsiniz. Devam edilsin mi? + Hariç tut Hariç tutulan klasörler Hariç tutulan klasörleri yönet - Bu, seçimi alt klasörleriyle birlikte yalnızca Basit Galeri\'den hariç tutacaktır. Dışlanan klasörleri Ayarlar\'dan yönetebilirsiniz. - Bunun yerine ebeveynleri hariç tutun? - Klasörler hariç tutulduğunda, onları Basit Galeri\'de gizli olan alt klasörleriyle bir araya getirirler, ancak yine de diğer uygulamalarda görünür olurlar.\n\nBunları diğer uygulamalardan gizlemek isterseniz, Gizle işlevini kullanın. - Hepsini sil - Hariç tutulanlar listesinden tüm klasörleri kaldırmak mı istiyorsunuz? Bu, klasörler silinmez. + Bu, alt klasörleriyle birlikte seçimi yalnızca Basit Galeriden hariç tutacaktır. Hariç tutulan klasörleri Ayarlar\'dan yönetebilirsiniz. + Bunun yerine bir üst dizin hariç tutulsun mu? + Klasörleri hariç tutmak, onları yalnızca Basit Galeri\'de gizlenen alt klasörleriyle bir araya getirecek, diğer uygulamalarda görünmeye devam edecektir.\n\nBunları diğer uygulamalardan gizlemek istiyorsanız, Gizle işlevini kullanın. + Tümünü kaldır + Tüm klasörler hariç tutulanlar listesinden kaldırılsın mı? Bu klasörleri silmez. + Gizli klasörler + Gizli klasörleri yönet + \".nomedia\" dosyasıyla gizlenmiş herhangi bir klasörünüz yok gibi görünüyor. Dahil edilen klasörler Dahil edilen klasörleri yönet Klasör ekle - Ortam içeren, ancak uygulama tarafından tanınmayan bazı klasörleriniz varsa, bunları el ile buradan ekleyebilirsiniz. + Medya içeren, ancak uygulama tarafından tanınmayan bazı klasörleriniz varsa, bunları elle ekleyebilirsiniz.\n\nBuraya bazı öğeler eklemek başka bir klasörü hariç tutmaz. + Hiçbir medya dosyası bulunamadı. Medya dosyalarını içeren klasörleri elle ekleyerek çözebilirsiniz. Yeniden boyutlandır @@ -47,70 +71,353 @@ Genişlik Yükseklik En-boy oranını koru - Lütfen geçerli bir çözüm önerisi girin + Lütfen geçerli bir çözünürlük girin - Editör - Kaydet + Düzenleyici Döndür - Yol - Görüntü yolu geçersiz + Geçersiz resim yolu + Geçersiz video yolu Resim düzenleme başarısız - İle resmi düzenle: - Resim editörü bulunamadı + Video düzenleme başarısız + Resim düzenleme iptal edildi + Video düzenleme iptal edildi + Dosya başarıyla düzenlendi + Resim başarıyla düzenlendi + Video başarıyla düzenlendi + Resmi şununla düzenle: + Videoyu şununla düzenle: + Resim düzenleyici bulunamadı + Video düzenleyici bulunamadı Bilinmeyen dosya konumu Kaynak dosyanın üzerine yazılamadı Sola döndür Sağa döndür - Ters çevir 180º + 180º döndür Çevir - Yatay - Dikey - Yetersiz bellek hatası + Yatay olarak çevir + Dikey olarak çevir + Serbest + Diğer Basit Duvar Kağıdı Duvar kağıdı olarak ayarla - Duvar Kağıdı Olarak Ayarlanılamıyor - İle duvar kağıdı olarak ayarla: - Mümkün olan herhangi bir uygulama bulunamadı - Duvar kağıdını ayarlama… + Duvar kağıdı olarak ayarlanmadı + Şununla duvar kağıdı olarak ayarla: + Duvar kağıdı ayarlanıyor… Duvar kağıdı başarıyla ayarlandı - Portrait aspect ratio - Landscape aspect ratio + Dikey en boy oranı + Yatay en boy oranı + Ana ekran + Kilit ekranı + Ana ve kilit ekranı + + + Slayt gösterisi + Süre (saniye): + Fotoğrafları dahil et + Videoları dahil et + GIF\'leri dahil et + Rastgele sırala + Geriye doğru git + Slayt gösterisini tekrarla + Animasyon + Hiçbiri + Karart + Kaydır + Slayt gösterisi sona erdi + Slayt gösterisi için medya bulunamadı + + + Doğrudan alt klasörleri gruplandır + + + Gruplandırma + Dosyaları gruplandırma + Klasör + Son değiştirilme + Son değiştirilme (günlük) + Son değiştirilme (aylık) + Çekildiği tarih + Çekildiği tarih (günlük) + Çekildiği tarih (aylık) + Dosya türü + Uzantı + Lütfen gruplandırmanın ve sıralamanın 2 bağımsız alan olduğunu unutmayın + + + Widget\'ta gösterilen klasör: + Klasör adını göster - Gizli klasörleri göster - Videoları otomatik olarak oynat - Dosya adı görünürlüğünü değiştir - Medyayı göster - Yalnızca resimler - Yalnızca videolar - Gifs only - Images, videos, gifs - Resimler ve videolar - Videolar döngüsü - Küçük resimlerde gif\'leri canlandırın - Ortam görüntülerken azami parlaklık + Videoları otomatik oynat + Son video oynatma konumunu hatırla + Dosya adı görünürlüğünü aç/kapat + Videoları tekrarla + Küçük resimlerdeki GIF\'leri hareketlendir + Tam ekran medya görüntülerken maksimum parlaklık Küçük resimleri karelere kırp - Tarafından tam ekran medyayı döndür - Sistem ayarı - Cihaz döndürme - En-boy oranı - Dark background at fullscreen media - Scroll thumbnails horizontally + Video sürelerini göster + Tam ekran medyayı döndür + Sistem ayarları + Cihaz yönü + En boy oranı + Tam ekran medyada siyah arka plan ve durum çubuğu + Küçük resimleri yatay olarak kaydır + Tam ekran medyada sistem arayüzünü otomatik gizle + İçeriğini sildikten sonra boş klasörleri sil + Dikey hareketlerle fotoğraf parlaklığının kontrolüne izin ver + Video sesini ve parlaklığını dikey hareketlerle kontrol etmeye izin ver + Ana görünümde klasör medya sayısını göster + Tam ekran medya üzerinde genişletilmiş ayrıntıları göster + Genişletilmiş ayrıntıları yönet + Tam ekran medyalarda tek parmakla yakınlaştırmaya izin ver + Ekran kenarlarına tıklayarak anında medya değiştirmeye izin ver + Derin yakınlaştırma resimlerine izin ver + Durum çubuğu gizlendiğinde genişletilmiş ayrıntıları gizle + Ekranın alt kısmında bazı eylem düğmelerini göster + Geri dönüşüm kutusu\'nu klasörler ekranında gösterme + Derin yakınlaştırılabilir resimler + Resimleri mümkün olan en yüksek kalitede göster + Geri dönüşüm kutusu\'nu ana ekranda son öğe olarak göster + Tam ekran görünümünü aşağı hareketi ile kapatmaya izin ver + İki çift dokunuşla 1:1 yakınlaştırmaya izin ver + Videoları yeni yatay hareketlerle daima ayrı bir ekranda aç + Varsa bir çentik göster + Hareketlerle resimlerin döndürülmesine izin ver + Dosya yükleme önceliği + Hız + Ödünsüz + Geçersiz dosyaları göstermekten kaçın + Resim dosyası türlerini göster + Videoları iki kez dokunarak yakınlaştırmaya izin ver + Klasör küçük resim stili + File thumbnail style + Thumbnail spacing + Dosya sayısını ayrı bir satırda göster + Dosya sayısını parantez içinde göster + Dosya sayısını gösterme + Uzun klasör başlıklarını 1 satırla sınırla + Kare + Yuvarlatılmış köşeler + + + Küçük resimler + Tam ekran medya + Genişletilmiş ayrıntılar + Alt eylemler + + + Görünür alt eylemleri yönet + Favoriyi göster/gizle + Dosya görünürlüğünü aç/kapat + + + Özel + Sıfırla + Kare + Dönüştür + Filtre + Yok + Ayarla + Gölgeler + Pozlama + Vurgular + Parlaklık + Kontrast + Doygunluk + Berraklık + Gama + Siyahlar + Beyazlar + Sıcaklık + Netlik + Sıfırla + Odak + Yok + Radyal + Doğrusal + Aynalı + Gauss + Metin + Metin Seçenekleri + Metin Rengi + Yazı Tipi + Ekle + Düzenle + Düzle + Yazı Tipi + Renk + Arka Renk + Hizalama + Öne + Sil + Metniniz + Fırça + Renk + Boyut + Sertlik + Öne + Sil + Fırça Rengi + Editör + Editör kapatılsın mı? + Değişiklikleri gerçekten atmak istiyor musunuz? + Evet + Hayır + İptal + Kabul Et + Kaydet + Dışa aktarılıyor… + %s dışa aktarılıyor. + Etiket + Sticker Color + Etiket Seçenekleri + Ekle + Color + Sil + Öne + Straighten + Değiştir + Opaklık + Contrast + Saturation + Brightness + Uploads + Kaplama + Normal + Karartma + Örtme + Kaplama + Aydınlatma + Çarpma + Renk Yanması + Hafif Aydınlık + Şiddetli Aydınlık + Hiçbiri + Altın + Hafif Sızıntı 1 + Mozaik + Kağıt + Yağmur + Nostaljik + Yatay Çevir + Dikey Çevir + Geri Al + Yinele + Renk Seçici + Şeffaf + Beyaz + Gri + Siyah + Açık Mavi + Mavi + Mor + Orkide + Pembe + Kırmızı + Turuncu + Altın + Sarı + Zeytin + Yeşil + Akuamarin + Pipetlenebilir renk + Kırp + + + Basit Galeri\'yi nasıl varsayılan cihaz galerisi yapabilirim? + Önce cihaz ayarlarınızın Uygulamalar bölümünde varsayılan galeriyi bulmanız, \"Varsayılan olarak aç\" gibi bir şey söyleyen bir düğme aramanız, üzerine tıklamanız ve ardından \"Varsayılanları Temizle\"yi seçmeniz gerekir. + Gelecek sefer video veya resim açmayı denediğinizde, Basit Galeri\'yi seçip varsayılan uygulama haline getirebileceğiniz bir uygulama seçicisini görmelisiniz. + Uygulamayı bir şifre ile kilitledim ama unuttum. Ne yapabilirim? + Bunu 2 şekilde çözebilirsiniz. Uygulamayı yeniden yükleyebilir veya uygulamayı cihaz ayarlarınızdan bulabilir ve \"Verileri temizle\"yi seçebilirsiniz. Tüm ayarlarınızı sıfırlar, herhangi bir medya dosyasını kaldırmaz. + Bir albümün her zaman en üstte görünmesini nasıl sağlayabilirim? + İstediğiniz albüme uzunca basabilir ve eylem menüsündeki Sabitle simgesini seçebilirsiniz. Birden çok klasörü de sabitleyebilirsiniz, sabitlenmiş öğeler varsayılan sıralama yöntemine göre sıralanır. + Videoları nasıl hızlıca ileri sarabilirim? + Ekranın yan tarafına iki kez dokunarak veya arama çubuğunun yanındaki geçerli veya maksimum süre metinlerine dokunarak bunu yapabilirsiniz. Videoları uygulama ayarlarından ayrı bir ekranda açmayı etkinleştirirseniz yatay hareketleri de kullanabilirsiniz. + Klasörün gizlenmesi ve hariç tutulması arasındaki fark nedir? + Hariç tut, klasörü yalnızca Basit Galeri\'de görüntülemeyi engellerken, Gizle sistem genelinde çalışır ve klasörü diğer galerilerden de gizler. Verilen klasörde boş bir \".nomedia\" dosyası oluşturarak çalışır, daha sonra herhangi bir dosya yöneticisi ile kaldırabilirsiniz. Bazı cihazların Kamera, Ekran Görüntüleri ve İndirilenler gibi klasörlerin gizlenmesine izin vermediğini unutmayın. + Neden albüm resimlerini içeren klasörler görünüyor? + Bazı olağandışı albümlerin ortaya çıktığını göreceksiniz. Onları uzun süre basarak ve Hariç tut\'u seçerek kolayca hariç tutabilirsiniz. Bir sonraki iletişim kutusunda, üst klasörü seçebilirsiniz, ancak diğer ilgili albümlerin de gösterilmesini önleyecektir. + Resimler içeren bir klasör görünmüyor, ne yapabilirim? + Bunun birden fazla nedeni olabilir, ancak bunu çözmek kolaydır. Sadece Ayarlar -> Dahil Edilen Klasörleri Yönet\'e gidin, Artı\'yı seçin ve gerekli klasöre gidin. + Sadece birkaç belirli klasörün görünmesini istersem ne olur? + Dahil Edilen Klasörler\'e bir klasör eklemek otomatik olarak hiçbir şeyi hariç tutmaz. Yapabilecekleriniz Ayarlar -> Hariç Tutulan Klasörleri Yönet, kök klasörü \"/\" hariç tut, ardından Ayarlar -> Dahil Edilen Klasörleri Yönet. + Bu, hem hariç tutulan hem de dahil edilen olmak üzere, yalnızca seçilen klasörleri görünür hale getirir ve bir klasör her ikisi de hariç tutulur ve dahil edilirse, görünür. + Bu uygulamayla görüntüleri kırpabilir miyim? + Evet, görüntü köşelerini sürükleyerek resimleri düzenleyicide kırpabilirsiniz. Düzenleyiciye, bir resim küçük resmine uzun basıp Düzenle\'yi seçerek veya tam ekran görünümünden Düzenle\'yi seçerek ulaşabilirsiniz. + Medya dosyası küçük resimlerini bir şekilde gruplayabilir miyim? + Elbette, küçük resimler görünümünde \"Gruplandırma\" menü öğesini kullanın. Çekildiği Tarih de dahil olmak üzere dosyaları birçok kritere göre gruplayabilirsiniz. \"Tüm klasörleri göster\" işlevini kullanırsanız, bunları klasörlere göre de gruplayabilirsiniz. + Çekildiği Tarihe Göre Sıralama düzgün çalışmıyor gibi görünüyor, nasıl düzeltebilirim? + Büyük olasılıkla bir yerden kopyalanan dosyalardan kaynaklanır. Dosya küçük resimlerini seçip \"Çekilen tarih değerini düzelt\" seçeneğini seçerek düzeltebilirsiniz. + Görüntülerde renk şeritleri görüyorum. Kaliteyi nasıl arttırabilirim? + Görüntüleri görüntülemek için geçerli çözüm, vakaların büyük çoğunluğunda iyi çalışır, ama daha iyi görüntü kalitesi istiyorsanız, \"Derin yakınlaştırılabilir resimler\" bölümündeki uygulama ayarlarında \"Resimleri mümkün olan en yüksek kalitede göster\" seçeneğini etkinleştirebilirsiniz. + Bir dosya/klasör gizledim. Nasıl gösterebilirim? + Ana ekranda \"Geçici olarak gizli öğeleri göster\" menü öğesine veya gizli öğeyi görmek için uygulama ayarlarında \"Gizli öğeleri göster\" seçeneğine tıklayabilirsiniz. Göstermek isterseniz, sadece uzun basın ve \"Göster\"i seçin. Klasörler gizlenmiş bir \".nomedia\" dosyası ekleyerek gizlenir, dosyayı herhangi bir dosya yöneticisi ile de silebilirsiniz. + Uygulama neden bu kadar yer kaplıyor? + Uygulama önbelleği 250 MB\'a kadar çıkabilir, daha hızlı resim yüklemesini sağlar. Uygulama daha da fazla yer kaplıyorsa, büyük olasılıkla Geri Dönüşüm Kutusu\'nda öğe bulundurmanızdan kaynaklanmaktadır. Bu dosyalar uygulama boyutuna göre sayılır. Geri Dönüşüm Kutusu\'nu açarak ve tüm dosyaları silerek ya da uygulama ayarlarından temizleyebilirsiniz. Geri dönüşümdeki her dosya 30 gün sonra otomatik olarak silinir. + + Basit Galeri Pro - Fotoğraf Yönetici, Düzenleyici - Fotoğrafları ve videoları reklamsız görüntülemek için kullanılan bir galeri. + Bu fotoğraf ve video galerisinde kesintisiz olarak anılarınıza göz atın - Fotoğrafları ve videoları görüntülemek için kullanılabilecek basit bir araç. Öğeler tarihine, boyutuna, adına göre artan veya azalan olarak sıralanabilir, fotoğraflar yakınlaştırılabilir. Medya dosyaları, ekranın boyutuna bağlı olarak birden fazla sütunda gösterilir, sıkıştırma hareketleriyle sütun sayısını değiştirebilirsiniz. Yeniden adlandırılabilir, paylaşılabilir, silinebilir, kopyalanabilir, taşınabilirler. Resimler ayrıca kırpılabilir, döndürülebilir veya doğrudan uygulama\'dan Duvar kağıdı olarak ayarlanabilir. + Basit Galeri Pro, özelleştirilebilir bir çevrimdışı galeridir. Fotoğraflarınızı düzenleyin ve organize edin, geri dönüşüm kutusuyla silinen dosyaları kurtarın, dosyaları koruyun ve gizleyin ve RAW, SVG ve çok daha fazlası dahil olmak üzere çok çeşitli fotoğraf ve video formatlarını görüntüleyin. - Galeri, görüntüleri/videoları önizlemek, e-posta istemcilerine ek ekler yapmak için üçüncü taraf kullanımı için de önerilir. Bu\'s günlük kullanım için mükemmel. + Uygulama hiçbir reklam ve gereksiz izinler içermez. Uygulama internet erişimi gerektirmediğinden gizliliğiniz de korunur. - Reklam içermeyen veya gereksiz izinler. Tamamen açık kaynaktır, özelleştirilebilir renkler sağlar. + ------------------------------------------------- + BASİT GALERİ PRO – ÖZELLİKLER + ------------------------------------------------- - Bu uygulama, daha büyük bir uygulama serisinden sadece bir parça. Geri kalanını şu adresten bulabilirsiniz http://www.simplemobiletools.com + • Reklamsız veya açılır penceresiz, çevrimdışı galeri + • Basit galeri fotoğraf editörü - kırpma, döndürme, yeniden boyutlandırma, çizim, filtreler ve daha fazlası + • İnternet erişimi gerekmez, size daha fazla gizlilik ve güvenlik sunar + • Gereksiz izinler gerekmez + • Resimleri, videoları ve dosyaları hızlıca arama + • Birçok farklı fotoğraf ve video türünü (RAW, SVG, panoramik vb.) açma ve görüntüleme + • Dosyaları kolayca düzenlemek ve organize etmek için çeşitli sezgisel hareketler + • Dosyaları filtrelemenin, gruplandırmanın ve sıralamanın birçok yolu + • Basit Galeri Pro\'nun görünümünü özelleştirme + • 32 dilde mevcut + • Hızlı erişim için dosyaları sık kullanılan olarak işaretleme + • Fotoğraflarınızı ve videolarınızı desen, pin veya parmak izi ile koruma + • Uygulama başlatılmasını veya belirli işlevleri de korumak için pin, desen ve parmak izi kullanma + • Silinen fotoğrafları ve videoları geri dönüşüm kutusundan kurtarma + • Fotoğrafları ve videoları gizlemek için dosyaların görünürlüğünü değiştirme + • Dosyalarınızın özelleştirilebilir bir slayt gösterisini oluşturma + • Dosyalarınızın ayrıntılı bilgilerini (çözünürlük, EXIF değerleri vb.) görüntüleme + • Basit Galeri Pro açık kaynak kodludur + … ve çok daha fazlası! + + FOTOĞRAF GALERİSİ EDİTÖRÜ + Basit Galeri Pro, fotoğraflarınızı anında düzenlemenizi kolaylaştırır. Resimlerinizi kırpın, çevirin, döndürün ve yeniden boyutlandırın. Kendinizi biraz daha yaratıcı hissediyorsanız, filtre ekleyebilir ve resimlerinizi çizebilirsiniz! + + BİRÇOK DOSYA TÜRÜ İÇİN DESTEK + Diğer galeri görüntüleyicilerinden ve fotoğraf düzenleyicilerinden farklı olarak, Basit Galeri Pro, JPEG, PNG, MP4, MKV, RAW, SVG, Panoramik fotoğraflar, Panoramik videolar ve daha pek çok farklı dosya türünü destekler. + + SON DERECE ÖZELLEŞTİRİLEBİLİR GALERİ YÖNETİCİSİ + Kullanıcı arayüzünden alt araç çubuğundaki işlev düğmelerine kadar, Basit Galeri Pro oldukça özelleştirilebilirdir ve istediğiniz şekilde çalışır. Başka hiçbir galeri yöneticisi bu tür bir esnekliğe sahip değildir! Açık kaynak olması sayesinde 32 dilde de kullanıma hazırız! + + SİLİNEN FOTOĞRAFLARI VE VİDEOLARI KURTARIN + Yanlışlıkla önemli bir fotoğrafı veya videoyu mu sildiniz? Endişelenmeyin! Basit Galeri Pro, silinmiş fotoğrafları ve videoları kolayca kurtarabileceğiniz kullanışlı bir geri dönüşüm kutusuna sahiptir. + + FOTOĞRAFLARI, VİDEOLARI VE DOSYALARI KORUYUN VE GİZLEYİN + Pin, desen veya cihazınızın parmak izi tarayıcısını kullanarak fotoğrafları, videoları ve albümün tamamını koruyabilir ve gizleyebilirsiniz. Uygulamanın kendisini koruyabilir veya uygulamanın belirli işlevlerine kilitler yerleştirebilirsiniz. Örneğin, parmak izi taraması olmadan bir dosyayı silemez ve dosyalarınızı yanlışlıkla silmeye karşı korumaya yardımcı olabilirsiniz. + + Tüm Basit Araçlar paketini buradan inceleyin: + https://www.simplemobiletools.com + + Basit Galeri Pro\'nun bağımsız web sitesi: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + Фільтр мультимедійних файлів + Зображення + Відео + GIF-зображення + RAW-зображення + SVG-зображення + Портрети + З вибраними фільтрами мультимедійні файли не знайдено. + Змінити фільтри + + + Ця функція приховує теку шляхом додавання в неї файлу \".nomedia\"; це також приховає і підтеки. Ви можете побачити приховані теки, обравши опцію \'Показати приховані елементи\' в \"Налаштуваннях\". Продовжити? + Виключити + Виключені теки + Керування виключеними теками + Ця дія виключить обрані теки з їх підтеками тільки з Simple Gallery. Керувати виключеними теками можна у \"Налаштуваннях\". + Виключити батьківську теку натомість? + Виключення тек приховає їх разом з їх підтеками тільки у Simple Gallery, але вони все ще будуть видимі у інших додатках.\n\nЯкщо ви хочете приховати їх і від інших додатків, використовуйте функцію \"Приховати\". + Видалити все + Видалити всі теки зі списку виключених? Це не видалить теки з пристрою. + Приховані теки + Керування прихованими теками + Схоже, у вас немає тек, прихованих за допомогою файлу \".nomedia\". + + + Включені теки + Керування включеними теками + Додати теку + Якщо у вас є теки з медіафайлами, але вони не були розпізнані додатком, ви можете додати їх тут вручну.\n\nДодавання елементів сюди не виключить будь-яку іншу теку. + Жоден медіафайл не знайдено. Ви можете додати теки з медіафайлами вручну, щоб вирішити цю проблему. + + + Змінити розмір + Змінити розмір вибраного і зберегти + Ширина + Висота + Зберігати співвідношення сторін + Введіть допустиму роздільну здатність + + + Редактор + Обернути + Неприпустимий шлях до зображення + Неприпустимий шлях до відео + Не вдалося редагувати зображення + Не вдалося редагувати відео + Редагування зображення скасовано + Редагування відео скасовано + Файл вдало відредагован + Зображення вдало відредаговано + Відео вдало відредаговано + Редагувати зображення за допомогою: + Реданувати відео у: + Не знайдено редакторів зображень + Не знайдено редакторів відео + Невідоме розташування файлу + Не вдалося перезаписати вихідний файл + Обернути ліворуч + Обернути праворуч + Обернути на 180º + Віддзеркалити + Віддзеркалити горизонтально + Віддзеркалити вертикально + Вільне + Інше + + + Simple Wallpaper + Встановити як шпалери + Не вдалося встановити шпалери + Встановити шпалери за допомогою: + Встановлення шпалер… + Шпалери успішно встановлено + Співвідношення сторін у портретній орієнтації + Співвідношення сторін у ландшафтній орієнтації + Домашній екран + Екран блокування + Домашній екран і екран блокування + + + Слайдшоу + Інтервал (секунди): + Включити фото + Включити відео + Включити GIF-зображення + Випадковий порядок + Рухатися назад + Зациклити показ слайдів + Анімація + Немає + Згасання + Зміщення + Слайдшоу закінчено + Не знайдено медіафайлів для показу у слайдшоу + + + Групувати безпосередні підтеки + + + Групувати за… + Не групувати файли + текою + останньою зміною + останньою зміною (по днях) + останньою зміною (по місяцях) + датою зйомки + датою зйомки (по днях) + датою зйомки (по місяцях) + типом файлу + розширенням + Зверніть увагу, що групування і сортування - це два окремих поля + + + Тека, що відображається на віджеті: + Показувати ім\'я теки + + + Відтворювати відео автоматично + Запам\'ятовувати місце зупинки перегляду + Перемкнути відображення імені файлу + Зациклити відео + Анімувати ескізи GIF-файлів + Максимальна яскравість екрана при повноекранному перегляді медіафайлу + Обрізати ескізи у квадрат + Показувати тривалість відео + При повноекранному перегляді обертати за… + системними налаштуваннями + поворотом пристрою + співвідношенням сторін + Чорне тло і рядок стану при повноекранному перегляді + Гортати ескізи горизонтально + Автоматично приховувати системний інтерфейс при повноекранному перегляді + Видаляти порожні теки після видалення їх вмісту + Дозволити керування яскравістю фотографій вертикальними жестами + Дозволити керування яскравістю та гучністю відео вертикальними жестами + Показувати кількість файлів у теці на головному екрані + Показувати розширені подробиці при повноекранному перегляді + Керування розширеними подробицями + Дозволити масштабування одним пальцем при повноекранному перегляді + Дозволити миттєво змінювати медіафайл натисканням на сторони екрана + Дозволити глибоке масштабування зображень + Приховати розширені подробиці, коли рядок стану прихований + Показати деякі кнопки дій внизу екрана + Показувати \"Кошик\" на головному екрані + Глибокомасштабовані зображення + Показувати зображення в найвищій можливій якості + Показувати \"Кошик\" останнім елементом на головному екрані + Дозволити закриття повноекранного перегляду свайпом згори вниз + Дозволити масштабування до 1:1 подвійним тапом + Завжди відкривати відео на окремому екрані з новими горизонтальними жестами + Показувати мітку, якщо доступна + Дозволити обертання зображень жестами + Пріоритет завантаження файлів + Швидкість + Компроміс + Запобігати показу пошкоджених файлів + Показувати типи файлів зображень + Масштабувати відео подвійним натисканням по ньому + Вид мініатюр тек + File thumbnail style + Thumbnail spacing + Показувати кількість файлів в окремому рядку + Показувати кількість файлів у дужках + Не показувати кількість файлів + Обмежити довгі назви тек одним рядком + Квадрат + Закруглені кути + + + Ескізи + Повноекранний перегляд + Розширені подробиці + Кнопки дій внизу екрана + + + Керування видимими кнопками дій внизу екрана + Перемкнути улюблене + Перемкнути видимість файлу + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Текст + Параметри тексту + Колір тексту + Шрифт + Додати + Редагувати + Straighten + Шрифт + Колір + BG Color + Вирівнювання + To Front + Видалити + Ваш текст + Пензель + Колір + Розмір + Hardness + To Front + Видалити + Колір пензля + Редактор + Закрити редактор? + Ви дійсно хочете відмінити зміни? + Так + Ні + Відхилити + Прийняти + Зберегти + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Додати + Колір + Видалити + To Front + Straighten + Replace + Прозорість + Контрастність + Насиченість + Яскравість + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Горизонтальний переворот + Вертикальний переворот + Скасувати + Повторити + Вибрати колір + Прозорий + Білий + Сірий + Чорний + Блакитний + Синій + Фіолетовий + Бузковий + Рожевий + Червоний + Помаранчевий + Золотий + Жовтий + Оливковий + Зелений + Аквамариновий + Pipettable color + Trim + + + Як зробити Simple Gallery галереєю за-замовчуванням? + Спочатку необхідно знайти поточну галерею за-замовчуванням в розділі \"Додатки\" налаштувань вашого пристрою. Знайдіть і натисніть на кнопку \"Використовувати за-замовчуванням\" абощо, потім оберіть \"Очистити замовчування\". + Наступного разу коли ви намагатиметеся відкрити зображення або відео, ви побачите вікно з вибором додатків для цього. Оберіть Simple Gallery та зробіть його додатком за-замовчуванням. + Я заблокував додаток за допомогою пароля і забув його. Що я можу зробити? + Ви можете вирішити цю проблему двома способами: перевстановити додаток або знайти його в розділі \"Додатки\" налаштувань вашого пристрою та обрати \"Очистити дані\". Це скине усі ваші налаштування додатка, але не видалить жодного медіафайлу. + Як зробити альбом завжди доступним у верхній частині? + Ви можете виконати довге натискання на бажаному альбомі і вибрати піктограму \"Закріпити\" у меню дій, що закріпить його вгорі. Ви також можете закріпити декілька тек; закріплені елементи будуть відсортовані за методом сортування за-замовчуванням. + Як я можу швидко прокручувати відео? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + В чому полягає різниця між приховуванням та виключенням теки? + \"Виключити\" запобігає відображенню теки тільки в додатку Simple Gallery, в той час як \"Приховати\" працює на системному рівні і приховує теку і в інших галереях також. Це здійснюється шляхом створення порожнього файлу \".nomedia\" в заданій теці, який може бути видалений пізніше будь-яким файловим менеджером. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Чому відображаються теки з музичними обкладинками або стікерами? + Може так трапитися, що з\'являться деякі незвичні альбоми. Їх можна легко виключити довгим натисканням і вибором \"Виключити\". В наступному діалозі у вас буде можливість обрати бітьківську теку. Швидше за все, це дозволить також запобігти появі інших пов\'язаних альбомів. + Тека з зображеннями не відображається, що мені робити? + У цього може бути кілька причин, проте вирішення доволі просте. Перейдіть в \"Налаштування\" -> \"Керування виключеними теками\", оберіть \"Плюс\" та перейдіть до потрібної теки. + Що робити, якщо я хочу, щоб лише кілька окремих тек були видимими? + Додавання теки до \"Включених тек\" автоматично не виключає нічого. Що ви можете зробити, то це перейти в \"Налаштування\" -> \"Керування виключеними теками\", виключити кореневу теку \"/\", потім додати бажані теки через \"Налаштування\" -> \"Керування включеними теками\". + Це зробить видимими лише обрані теки. Якщо тека є одночасно і виключеною, і включеною, вона відобразиться, оскільки і виключення, і включення є рекурсивним. + Чи можу я обрізати зображення у цьому додатку? + Так, обрізати зображення можна у редакторі шляхом перетягування кутів зображення. Відкрити редактор можна або довгим натисканням на ескіз зображення і наступним вибором \"Редагувати\", або вибором \"Редагувати\" при повноекранному перегляді. + Чи можу я якимось чином згрупувати ескізи медіафайлів? + Звісно, просто скористайтеся пунктом меню \"Групувати за…\", знаходячись на екрані ескізів. Групувати файли можна за кількома критеріями, включаючи дату зйомки. Якщо ви використовуєте функцію \"Показати вміст усіх тек\", ви також можете групувати їх за теками. + Сортування за датою зйомки, здається, працює некоректно. Як я можу це виправити? + Найбільш вірогідна причина цього - копіювання фалів з іншого місця. Це можна виправити, обравши ескізи файлів і потім - \"Виправити дату зйомки\". + Я бачу деякі кольорові нашарування на зображенні. Як я можу покращити якість? + Поточне рішення для показу зображень відмінно працює в переважній більшості випадків, але якщо вам потрібна ще краща якість зображень, ви можете увімкнути опцію \"Показувати зображення в найвищій можливій якості\" в розділі \"Глибокомасштабовані зображення\" налаштувань додатка. + Я приховав файл / теку. Як я можу відмінити цю дію? + Щоб побачити приховані елементи, ви можете або натиснути пункт меню \"Тимчасово показати приховані елементи\" на головному екрані, або перемкнути опцію \"Показати приховані елементи\" в налаштуваннях додатка. Якщо ви більше не хочете приховувати елемент, довго натисніть на нього і оберіть \"Не приховувати\". Теки приховуються шляхом створення прихованого файлу \".nomedia\" в них, тож ви також можете видалити цей файл будь-яким файловим менеджером. + Чому додаток займає так багато місця? + Кеш додатка може займати до 500 МБ, він забезпечує швидше завантаження зображень. Якщо додаток займає ще більше місця, найбільш вірогідно, це спричинено видаленими елементами у Кошику. Вони враховуються у загальному розмірі додатка. Ви можете очистити Кошик, відкривши його та видаливши всі файли, або через налаштування додатка. Кожен файл у Кошику автоматично видаляється через 30 днів. + + + + Simple Gallery Pro - фотоменеджер і редактор + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro - це офлайн-галерея з великою кількістю налаштувань. Впорядковуйте та редагуйте ваші фото, відновлюйте видалені файли з кошика, захищайте та приховуйте файли і переглядайте фото і відео різноманітних форматів, включаючи RAW, SVG та багато іншого. + + Цей додаток не містить реклами і непотрібних дозволів. Оскільки додаток не потребує доступу в інтернет, ваша приватність захищена. + + ------------------------------------------------- + SIMPLE GALLERY PRO – ФУНКЦІЇ + ------------------------------------------------- + + • Офлайн-галерея без реклами чи спливаючих заставок + • Фоторедактор Simple gallery – обрізайте, обертайте, змінюйте розмір, малюйте, накладайте фільтри та інше + • Інтернет-з\'єднання не потрібне, що дає більше приватності і безпеки + • Не потребуються зайві дозволи + • Швидкий пошук зображень, відео і файлів + • Відкривайте та переглядайте багато різних фото- і відеоформатів (RAW, SVG, панорамні фото тощо) + • Різноманітні інтуїтивно зрозумілі жести для легкого редагуваання та упорядкування файлів + • Багато способів фільтрувати, групувати і сортувати файли + • Налаштуйте зовнішній вигляд Simple Gallery Pro + • Доступна 32 мовами + • Позначайте файли як улюблені для швидкого доступу + • Захищайте ваші фото і відео графічним ключем, PIN-кодом або відбитком пальця + • Використовуйте графічний ключ, PIN-код і відбиток пальця також для блокування запуску галереї або окремих її функцій + • Відновлюйте видалені фото і відео з кошика + • Змінюйте видимість файлів, щоб приховати фото і відео + • Створюйте налаштовуване слайд-шоу з ваших файлів + • Переглядайте детальну інформацію про ваші файли (роздільна здатність, записи EXIF тощо) + • Simple Gallery Pro є додатком з відкритим джерельним кодом + … та багато-багато іншого! + + ФОТОРЕДАКТОР + Simple Gallery Pro дозволяє легко редагувати ваші зображення на льоту. Обрізайте, віддзеркалюйте, обертайте та змінюйте розмір своїх зображень. Якщо ви почуваєтеся креативно, можете додавати фільтри та малювати на ваших зображеннях! + + ПІДТРИМКА БАГАТЬОХ ТИПІВ ФАЙЛІВ + На відміну від деяких інших переглядачів та організаторів галереї, Simple Gallery Pro підтримує величезний перелік різноманітних типів файлів, включаючи JPEG, PNG, MP4, MKV, RAW, SVG, панорамні фото, панорамні відео та багато іншого. + + МЕНЕДЖЕР ГАЛЕРЕЇ З БЕЗЛІЧЧЮ НАЛАШТУВАНЬ + Від зовнішнього вигляду до функціональних кнопок у нижній панелі інструментів: Simple Gallery Pro має безліч налаштувань та працює у потрібний вам спосіб. Жодний інший менеджер галереї не має такої гнучкості! Завдяки відкритому джерельному коду цей додаток доступний 32 мовами! + + ВІДНОВЛЮЙТЕ ВИДАЛЕНІ ФОТО І ВІДЕО + Випадково видалили дорогоцінне фото чи відео? Не хвилюйтеся! Simple Gallery Pro пропонує зручний кошик, звідки можна легко відновити видалені фото і відео. + + ЗАХИЩАЙТЕ І ПРИХОВУЙТЕ ФОТО, ВІДЕО І ФАЙЛИ + Використовуючи PIN-код, графічний ключ чи сканер відбитка пальця на вашому пристрої, ви можете захистити та приховати фото, відео та цілі альбоми. Ви можете захистити сам додаток або заблокувати окремі його функції. Наприклад, заборона видалення файлів без сканування відбитку пальця допоможе захистити ваші файли від випадкового видалення. + + Перегляньте повний набір додатків Simple Tools тут: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml deleted file mode 100644 index 11e77efc5..000000000 --- a/app/src/main/res/values-v21/styles.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml new file mode 100644 index 000000000..b6ba2dcd7 --- /dev/null +++ b/app/src/main/res/values-vi/strings.xml @@ -0,0 +1,444 @@ + + + Simple Gallery + Thư viện + Biên tập + Mở Camera + (ẩn) + (không bao gồm) + Ghim thư mục + Bỏ ghim thư mục + Ghim lên đầu + Hiển thị tất cả nội dung thư mục + Tất cả các thư mục + Chuyển sang xem thư mục + Thư mục khác + Hiển thị trên bản đồ + Vị trí không xác định + Âm lượng + Độ sáng + Khóa xoay màn hình + Mở khóa xoay + Thay đổi hướng màn hình + Bắt buộc dọc + Bắt buộc ngang + Sử dụng hướng mặc định + Sửa giá trị ngày chụp + Đang sửa… + Sửa ngày thành công + No Date Taken values have been found + Chia sẻ phiên bản đã thay đổi kích thước + Xin chào, có vẻ như bạn đã nâng cấp từ ứng dụng miễn phí cũ. Giờ đây, bạn có thể gỡ cài đặt phiên bản cũ có nút \'Nâng cấp lên Pro\' ở đầu Cài đặt ứng dụng. \n\nBạn sẽ chỉ xóa các mục Thùng rác, các mục yêu thích không được đánh dấu và bạn cũng sẽ phải đặt lại cài đặt ứng dụng của bạn.\n\nCảm ơn! + Chuyển sang tìm kiếm tệp trên tất cả các thư mục hiển thị + Set as default folder + Unset as default folder + + + Lọc + Hình ảnh + Video + Ảnh GIF + Ảnh RAW + SVGs + Chân dung + Không có tập tin phương tiện nào được tìm thấy với các bộ lọc được chọn. + Thay đổi bộ lọc + + + Chức năng này ẩn thư mục bằng cách thêm tệp \'.nomedia\' vào nó, nó cũng sẽ ẩn tất cả các thư mục con. Bạn có thể nhìn thấy chúng bằng cách bật tùy chọn \'Hiển thị các mục bị ẩn\' trong Cài đặt. Tiếp tục? + Loại trừ + Các thư mục bị loại trừ + Quản lý các thư mục loại trừ + Điều này sẽ loại trừ lựa chọn cùng với các thư mục con của nó khỏi Simple Gallery. Bạn có thể quản lý các thư mục loại trừ trong Cài đặt. + Loại trừ thư mục mẹ thay thế? + Các thư mục loại trừ sẽ làm cho chúng cùng với các thư mục con của chúng bị ẩn chỉ trong Simple Gallery, chúng vẫn sẽ hiển thị trong các ứng dụng khác.\n\nNếu bạn cũng muốn ẩn chúng khỏi các ứng dụng khác, hãy sử dụng chức năng Ẩn. + Xóa tất cả + Xóa tất cả các thư mục khỏi danh sách loại trừ? Điều này sẽ không xóa các thư mục. + Các thư mục ẩn + Quản lý thư mục ẩn + Có vẻ như bạn không có bất kỳ thư mục nào bị ẩn với tệp \".nomedia\". + + + Bao gồm các thư mục + Quản lý các thư mục được bao gồm + Thêm thư mục + Nếu bạn có một số thư mục chứa tập tin phương tiện, nhưng ứng dụng không nhận ra, bạn có thể thêm chúng theo cách thủ công tại đây. \n\nThêm một số mục ở đây sẽ không loại trừ bất kỳ thư mục nào khác. + Không có tập tin phương tiện được tìm thấy. Bạn có thể thêm các thư mục chứa tệp phương tiện theo cách thủ công. + + + Thay đổi kích thước + Thay đổi kích thước mục đã chọn và lưu + Chiều rộng + Chiều cao + Giữ tỷ lệ khung hình + Vui lòng nhập độ phân giải hợp lệ + + + Trình biên tập + Xoay + Đường dẫn hình ảnh không hợp lệ + Invalid video path + Thất bại + Video editing failed + Image editing cancelled + Video editing cancelled + Tập tin được chỉnh sửa thành công + Image edited successfully + Video edited successfully + Chỉnh sửa hình ảnh với: + Edit video with: + Không tìm thấy trình chỉnh sửa hình ảnh + No video editor found + Vị trí tệp không xác định + Không thể ghi đè tệp nguồn + Xoay trái + Xoay phải + Xoay 180 độ + Lật + Lật theo chiều ngang + Lật theo chiều dọc + Tự do + Khác + + + Hình nền + Cài làm hình nền + Thất bại + Đặt làm hình nền với: + Cài đặt hình nền… + Đặt hình nền thành công + Tỷ lệ khung hình dọc + Tỷ lệ khung hình ngang + Màn hình chính + Màn hình khóa + Màn hình chính và khóa + + + Trình chiếu + Khoảng thời gian (giây): + Bao gồm hình ảnh + Bao gồm video + Bao gồm ảnh GIF + Thứ tự ngẫu nhiên + Lùi lại + Trình chiếu theo vòng lặp + Hiệu ứng + Không + Phai màu + Trượt + Trình chiếu kết thúc + Không có tập tin phương tiện cho trình chiếu được tìm thấy + + + Nhóm thư mục con trực tiếp + + + Nhóm theo + Không nhóm các tệp + Thư mục + Sửa đổi lần cuối + Sửa đổi lần cuối (hàng ngày) + Sửa đổi lần cuối (hàng tháng) + Ngày chụp + Ngày chụp (hàng ngày) + Ngày chụp (hàng tháng) + Loại tệp + Phần mở rộng + Xin lưu ý rằng Nhóm và Sắp xếp là 2 trường độc lập + + + Thư mục hiển thị trên widget: + Hiển thị tên thư mục + + + Phát video tự động + Ghi nhớ vị trí phát video cuối cùng + Chuyển đổi khả năng hiển thị tên tệp + Tự động phát video đầu khi hết video cuối + GIF động tại hình thu nhỏ + Độ sáng tối đa khi xem tập tin phương tiện toàn màn hình + Cắt hình thu nhỏ thành hình vuông + Hiển thị thời lượng video + Xoay tập tin toàn màn hình theo + Thiết lập hệ thống + Hướng xoay của thiết bị + Tỷ lệ khung hình + Nền đen khi xem toàn màn hình + Cuộn hình thu nhỏ theo chiều ngang + Tự động ẩn giao diện người dùng hệ thống khi xem toàn màn hình + Xóa các thư mục trống sau khi xóa nội dung của chúng + Cho phép kiểm soát độ sáng của ảnh bằng cách vuốt dọc màn hình + Cho phép kiểm soát âm lượng và độ sáng của video bằng cách vuốt dọc màn hình + Hiển thị số thư mục trên giao diện chính + Hiển thị chi tiết mở rộng khi xem toàn màn hình + Quản lý chi tiết mở rộng + Cho phép thu phóng một ngón tay khi xem toàn màn hình + Cho phép chuyển tiếp ngay lập tức bằng cách nhấp vào các cạnh màn hình + Cho phép phóng to hình ảnh sâu + Ẩn chi tiết mở rộng khi thanh trạng thái bị ẩn + Hiển thị một số nút chức năng ở dưới cùng của màn hình + Hiển thị Thùng rác tại màn hình thư mục + Hình ảnh có thể phóng to + Hiển thị hình ảnh với chất lượng cao nhất có thể + Hiển thị Thùng rác là mục cuối cùng trên màn hình chính + Cho phép đóng chế độ xem toàn màn hình bằng cách vuốt xuống + Cho phép phóng to 1:1 với hai lần nháy đúp + Luôn mở video trên một màn hình riêng biệt bằng cử chỉ ngang mới + Hiển thị màn hình chữ V nếu có + Cho phép xoay hình ảnh bằng cử chỉ + Ưu tiên tải tệp + Tốc độ + Thỏa hiệp + Tránh hiển thị các tệp không hợp lệ + Hiển thị các loại tệp hình ảnh + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Hình thu nhỏ + Toàn màn hình + Chi tiết mở rộng + Hành động dưới cùng + + + Quản lý các hành động dưới cùng có thể nhìn thấy + Yêu thích + Ẩn/bỏ ẩn + + + Tùy chỉnh + Cài lại + Hình vuông + Biến đổi + Bộ lọc + Không + Điều chỉnh + Đổ bóng + Phơi sáng + Làm nổi bật + Độ sáng + Tương phản + Độ bão hòa + Sáng sủa + Đồ thị gamma + Đen + Trắng + Nhiệt độ + Độ sắc nét + Đặt lại + Tiêu điểm + Không + Xuyên tâm + Tuyến tính + Nhân đôi + Đường cong gauss + Văn bản + Tùy chọn văn bản + Màu văn bản + Font + Thêm + Biên tập + Duỗi thẳng + Font + Màu sắc + Màu BG + Căn chỉnh + Lên trước + Xóa + Văn bản của bạn + Cọ vẽ + Màu sắc + Kích thước + Độ đậm + Lên trước + Xóa bỏ + Màu cọ + Trình biên tập + Đóng trình biên tập? + Do you really want to discard the changes? + + Không + Hủy bỏ + Chấp nhận + Lưu + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Lật H + Lật V + Hoàn tác + Lui lại + Bộ chọn màu + Trong suốt + Trắng + Xám + Đen + Màu xanh nhạt + Màu xanh da trời + Màu tím + màu hoa lan + Hồng + Đỏ + Cam + Vàng kim + Màu vàng + Xanh ô-liu + Xanh lá + Xanh ngọc nước biển + Màu sắc dễ thương + Trim + + + Làm thế nào để đặt Simple Gallery thành thư viện mặc định? + Trước tiên, bạn phải tìm thư viện hiện tại mặc định trong phần Ứng dụng trong Cài đặt thiết bị của bạn, tìm nút có nội dung như \"Mở theo mặc định\", nhấp vào đó, sau đó chọn \"Xóa mặc định\". +         Lần tới, bạn hãy thử mở một hình ảnh hoặc video, bạn sẽ thấy một bộ chọn ứng dụng, nơi bạn có thể chọn Simple Gallery và biến nó thành ứng dụng mặc định. + Tôi đã khóa ứng dụng bằng mật khẩu, nhưng tôi quên mất. Tôi có thể làm gì? + Bạn có thể giải quyết nó theo 2 cách. Bạn có thể cài đặt lại ứng dụng hoặc tìm ứng dụng trong cài đặt thiết bị của mình và chọn \"Xóa dữ liệu\". Nó sẽ thiết lập lại tất cả các cài đặt của bạn, nó sẽ không xóa bất kỳ tập tin phương tiện nào. + Làm thế nào tôi có thể làm cho một album luôn xuất hiện ở đầu? + Bạn có thể nhấn và giữ album mong muốn và chọn biểu tượng Ghim ở menu hành động, nó sẽ ghim nó lên trên cùng. Bạn cũng có thể ghim nhiều thư mục, các mục được ghim sẽ được sắp xếp theo phương pháp sắp xếp mặc định. + Làm cách nào tôi có tua video nhanh? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + Sự khác biệt giữa ẩn và loại trừ một thư mục là gì? + Loại trừ ngăn chỉ hiển thị thư mục trong Thư viện đơn giản, trong khi Ẩn hoạt động theo hệ thống và nó cũng ẩn thư mục khỏi các phòng trưng bày khác. Nó hoạt động bằng cách tạo một tệp \".nomedia\" trong thư mục đã cho, sau đó bạn cũng có thể xóa bằng bất kỳ trình quản lý tệp nào. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Tại sao hiển thị các thư mục với ảnh bìa hoặc nhãn dán? + Nó có thể xảy ra rằng bạn sẽ thấy một số album bất thường xuất hiện. Bạn có thể dễ dàng loại trừ chúng bằng cách nhấn giữ và chọn Loại trừ. Trong hộp thoại tiếp theo bạn có thể chọn thư mục mẹ sau đó, rất có thể nó sẽ ngăn các album liên quan khác xuất hiện. + Một thư mục chứa hình ảnh không hiển thị hoặc không hiển thị tất cả các mục. Tôi có thể làm gì? + Điều đó có thể có nhiều lý do, nhưng giải quyết nó là dễ dàng. Chỉ cần vào Cài đặt -> Quản lý các thư mục được bao gồm, chọn Thêm và điều hướng đến thư mục cần thiết. + Nếu tôi chỉ muốn một vài thư mục cụ thể hiển thị thì sao? + Thêm thư mục tại Thư mục được bao gồm sẽ không tự động loại trừ bất cứ điều gì. Những gì bạn có thể làm là vào Cài đặt -> Quản lý các thư mục bị loại trừ, loại trừ thư mục gốc \"/\", sau đó thêm các thư mục mong muốn tại Cài đặt -> Quản lý các thư mục được bao gồm. +         Điều đó sẽ chỉ hiển thị các thư mục đã chọn, vì cả loại trừ và bao gồm đều được đệ quy và nếu một thư mục được loại trừ và bao gồm, nó sẽ hiển thị. + Tôi có thể cắt hình ảnh với ứng dụng này không? + Có, bạn có thể cắt hình ảnh trong trình chỉnh sửa, bằng cách kéo các góc hình ảnh. Bạn có thể truy cập trình chỉnh sửa bằng cách nhấn và thu nhỏ hình ảnh và chọn Chỉnh sửa hoặc chọn Chỉnh sửa từ chế độ xem toàn màn hình. + Tôi có thể bằng cách nào đó nhóm hình thu nhỏ tập tin phương tiện không? + Được chứ, chỉ cần sử dụng mục menu \"Nhóm theo\" trong khi ở chế độ xem hình thu nhỏ. Bạn có thể nhóm các tệp theo nhiều tiêu chí, bao gồm cả Ngày chụp. Nếu bạn sử dụng chức năng \"Hiển thị tất cả các nội dung thư mục\", bạn cũng có thể nhóm chúng theo các thư mục. + Sắp xếp theo ngày chụp dường như không hoạt động đúng, làm thế nào tôi có thể sửa nó? + Rất có thể là do các tệp được sao chép từ đâu đó. Bạn có thể sửa nó bằng cách chọn hình thu nhỏ của tệp và chọn \"Sửa giá trị ngày chụp\". + Tôi thấy một số dải màu trên hình ảnh. Làm thế nào tôi có thể cải thiện chất lượng? + Giải pháp hiện tại để hiển thị hình ảnh hoạt động tốt trong phần lớn các trường hợp, nhưng nếu bạn muốn chất lượng hình ảnh tốt hơn nữa, bạn có thể bật \"Hiển thị hình ảnh ở chất lượng cao nhất có thể\" ở cài đặt ứng dụng, trong phần \"Hình ảnh có thể phóng to sâu\". + Tôi đã ẩn một tập tin/thư mục. Làm thế nào tôi có thể bỏ ẩn nó? + Bạn có thể nhấn mục menu \"Tạm thời hiển thị các mục bị ẩn\" ở màn hình chính hoặc chuyển đổi \"Hiển thị các mục bị ẩn\" trong Cài đặt ứng dụng để xem mục bị ẩn. Nếu bạn muốn bỏ ẩn nó, chỉ cần nhấn và giữ \"Bỏ ẩn\". Các thư mục được ẩn bằng cách thêm tệp ẩn \".nomedia\" vào chúng, bạn cũng có thể xóa tệp bằng bất kỳ trình quản lý tệp nào. + Tại sao ứng dụng chiếm quá nhiều dung lượng ? + Bộ nhớ cache ứng dụng có thể mất tới 250MB, nó đảm bảo tải hình ảnh nhanh hơn. Nếu ứng dụng chiếm nhiều dung lượng hơn, rất có thể là do bạn có nhiều mục trong Thùng rác. Những tập tin đó được tính vào kích thước ứng dụng. Bạn có thể xóa thùng rác bằng cách mở nó và xóa tất cả các tệp hoặc từ Cài đặt ứng dụng. Mọi tệp trong Thùng rác sẽ tự động bị xóa sau 30 ngày. + + + + Simple Gallery Pro - Trình quản lý ảnh & biên tập + + Browse your memories without any interruptions with this photo and video gallery + + Simple Gallery Pro là một bộ sưu tập ngoại tuyến có khả năng tùy biến cao. Tổ chức & chỉnh sửa ảnh của bạn, khôi phục các tệp đã xóa bằng thùng rác, bảo vệ & ẩn các tệp và xem được nhiều định dạng ảnh & video bao gồm RAW, SVG và nhiều hơn nữa.     +      Ứng dụng này không chứa quảng cáo và các quyền không cần thiết. Vì ứng dụng không có yêu cầu truy cập internet, quyền riêng tư của bạn sẽ được bảo vệ. + + ------------------------------------------------- + SIMPLE GALLERY PRO – TÍNH NĂNG + ------------------------------------------------- + + • Thư viện ngoại tuyến không có quảng cáo hoặc popups   + + • Trình chỉnh sửa ảnh thư viện đơn giản - cắt, xoay, thay đổi kích thước, vẽ, lọc & hơn nữa + +       • Không cần truy cập internet, mang lại cho bạn sự riêng tư và bảo mật hơn + +       • Không cần quyền không cần thiết   + +       • Nhanh chóng tìm kiếm hình ảnh, video & các tập tin +        + • Mở & xem nhiều loại ảnh và video khác nhau (RAW, SVG, toàn cảnh, v.v.)     + +      • Một loạt các cử chỉ trực quan để dễ dàng chỉnh sửa & tổ chức tập tin + +       • Rất nhiều cách để lọc, nhóm & sắp xếp tập tin + +       • Tùy chỉnh giao diện của Simple Gallery Pro    + +       • Có sẵn trong 32 ngôn ngữ    + +       • Đánh dấu các tập tin là mục yêu thích để truy cập nhanh + +       • Bảo vệ ảnh của bạn & video có mẫu hình, pin hoặc dấu vân tay     + +      • Sử dụng pin, mẫu hình & dấu vân tay để bảo vệ khởi chạy ứng dụng hoặc các chức năng cụ thể + +       • Khôi phục ảnh đã xóa & video từ thùng rác     + +      • Chuyển đổi khả năng hiển thị của các tệp để ẩn ảnh & video    + +       • Tạo một slideshow tùy biến các tập tin của bạn + +       • Xem thông tin chi tiết về các tập tin của bạn (độ phân giải, giá trị EXIF, v.v.) + +       • Simple Gallery Pro là mã nguồn mở + +         Nhiều và nhiều hơn nữa! + + TRÌNH BIÊN TẬP ẢNH + Simple Gallery Pro giúp bạn dễ dàng chỉnh sửa ảnh của mình một cách nhanh chóng. Cắt, lật, xoay và thay đổi kích thước hình ảnh của bạn. Nếu bạn cảm thấy sáng tạo hơn một chút, bạn có thể thêm các bộ lọc và vẽ lên hình ảnh của mình! + + HỖ TRỢ NHIỀU ĐỊNH DẠNG FILE + Không giống như một số Trình xem thư viện khác & Trình tổ chức ảnh, Simple Gallery Pro hỗ trợ rất nhiều loại tệp khác nhau bao gồm JPEG, PNG, MP4, MKV, RAW, SVG, Ảnh toàn cảnh, Video toàn cảnh và nhiều loại khác. + + TRÌNH QUẢN LÝ THƯ VIỆN TÙY BIẾN CAO + Từ giao diện người dùng đến các nút chức năng trên thanh công cụ dưới cùng, Simple Gallery Pro có khả năng tùy biến cao và hoạt động theo cách bạn muốn. Không có trình quản lý thư viện nào khác có sự linh hoạt này! Nhờ nguồn mở, chúng tôi cũng có sẵn trong 32 ngôn ngữ! + + PHỤC HỒI ẢNH & VIDEO ĐÃ BỊ XÓA + Vô tình xóa một bức ảnh hoặc video quý giá? Hãy lo lắng! Simple Gallery Pro có thùng rác tiện dụng, nơi bạn có thể khôi phục ảnh & video đã xóa dễ dàng. + + BẢO VỆ & ẨN ẢNH ,VIDEO & TỆP + Sử dụng Mã pin, mẫu hình hoặc vân tay của bạn, bạn có thể bảo vệ và ẩn ảnh, video & toàn bộ album. Bạn có thể tự bảo vệ ứng dụng hoặc đặt khóa trên các chức năng cụ thể của ứng dụng. Ví dụ, bạn không thể xóa một tập tin mà chưa quét dấu vân tay, giúp bảo vệ các tập tin của bạn khỏi bị xóa do vô tình. + + Kiểm tra bộ công cụ Simple đầy đủ tại đây: + https://www.simplemobiletools.com + + Website của Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-w480dp/integers.xml b/app/src/main/res/values-w480dp/integers.xml index 2bfaa32f7..2f22506b5 100644 --- a/app/src/main/res/values-w480dp/integers.xml +++ b/app/src/main/res/values-w480dp/integers.xml @@ -1,4 +1,6 @@ - 3 - 5 + 3 + 3 + 5 + 5 diff --git a/app/src/main/res/values-w600dp/integers.xml b/app/src/main/res/values-w600dp/integers.xml index a98204fd4..ca1cfb86b 100644 --- a/app/src/main/res/values-w600dp/integers.xml +++ b/app/src/main/res/values-w600dp/integers.xml @@ -1,4 +1,6 @@ - 4 - 7 + 4 + 7 + 7 + 8 diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 96a8517fc..eee460a12 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,116 +1,421 @@ - Simple Gallery - 简约图库 + 简易图库 + 图库 编辑 打开相机 - 打开方式 - 未找到可用应用 - (隐藏) - 锁定文件夹 - 解除锁定文件夹 - 显示文件夹中所有内容 + (已隐藏) + (已排除) + 固定文件夹 + 取消固定文件夹 + 置顶固定 + 文件视图 所有文件夹 - 切换到文件夹视图 + 文件夹视图 其他文件夹 - 在地图中显示 + 在地图上显示 未知位置 - 未找到地图应用 - 未找到相机应用 - 增加一栏 - 减少一栏 - 显示/隐藏缓存内容 - 更改封面图片 - 选择图片 - 使用默认 + 音量 + 亮度 + 锁定屏幕方向 + 解锁屏幕方向 + 更改屏幕方向 + 强制竖屏 + 强制横屏 + 使用默认屏幕方向 + 修复拍摄日期 + 正在修复… + 日期修复成功 + 没有找到拍摄日期 + 调整尺寸并分享 + 嘿,\n\n您似乎已经从旧的免费版应用升级。您现在可以卸载旧版本,在该版本的应用设置顶部有一个“升级到专业版”按钮。\n\n此操作会删除回收站项目,取消标记收藏项目,并重置应用设置。\n\n谢谢! + 切换文件搜索范围为所有可见的文件夹 + 设置为默认文件夹 + 取消设置为默认文件夹 + + + 筛选媒体文件 + 图片 + 视频 + GIF + RAW 图片 + SVG 矢量图 + 人像 + 没有媒体文件匹配选定的筛选条件。 + 更改筛选条件 - 通过添加文件 \'.nomedia\' 到文件夹,可以防止文件夹及其子文件夹下的所有媒体被扫描。您可以通过设置中的 \'显示隐藏文件夹\' 选项改变设置,是否继续? + 该功能通过添加“.nomedia”文件到文件夹来隐藏它,这也会隐藏其所有子文件夹。您可以通过启用设置中的“显示隐藏的项目”选项来查看它们,是否继续? 排除 - 排除文件夹 - 管理排除文件夹 - 文件夹及其子文件夹中的媒体将不会在 Simple Gallery 中显示,您可以在设置更改。 + 排除的文件夹 + 管理排除的文件夹 + 该操作仅会在“简易图库”中排除显示选定的文件夹及其子文件夹。您可以在设置中管理排除的文件夹。 是否排除父文件夹? - 此文件夹及其子文件夹中的媒体将不会在 Simple Gallery 中显示,但是其它应用可以访问。\\n\\n如果您想对其它应用隐藏,请使用隐藏功能。 - 移除全部 - 是否移除列表中的全部文件夹?文件夹不会被物理删除。 + 此目录及其子目录中的媒体将不会在“简易图库”中显示,但是其它应用可以访问。如果您想对其它应用隐藏,请使用隐藏功能。 + 全部移除 + 是否移除排除列表中的所有项目?此操作不会删除文件夹本身。 + 隐藏的文件夹 + 管理隐藏的文件夹 + 看起来您没有任何使用“.nomedia”文件隐藏的目录。 - 包含文件夹 - 管理包含文件夹 + 包含的文件夹 + 管理包含的文件夹 添加文件夹 - 如果您还有应用未扫描到的媒体文件,请添加所在文件夹路径。 + 如果您还有应用未扫描到的媒体文件,请添加所在目录路径。 + 没有找到媒体文件。请手动添加包含媒体文件的文件夹。 - 缩放 - 缩放选定区域并保存 + 调整尺寸 + 调整选定项目的尺寸并保存 宽度 高度 - 保持纵横比 - 请输入有效分辨率 + 保持高宽比 + 请输入有效的分辨率 编辑器 - 保存 旋转 - 路径 - 无效图片路径 + 无效的图片路径 + 无效的视频路径 图片编辑失败 + 视频编辑失败 + 取消图片编辑 + 取消视频编辑 + 文件编辑成功 + 图片编辑成功 + 视频编辑成功 编辑方式: - 未找到可用图片编辑器 - 未知的文件路径 - 不能覆盖源文件 + 编辑方式: + 没有找到任何图片编辑器 + 没有找到任何视频编辑器 + 未知文件位置 + 无法覆盖源文件 向左旋转 向右旋转 旋转 180º 翻转 水平翻转 垂直翻转 - 内存不足 + 自由 + 其他 - Simple Wallpaper - 设为壁纸 - 壁纸设置失败 - 设为壁纸... - 未找到可用应用 + 简易壁纸 + 设置为壁纸 + 设置壁纸失败 + 设置壁纸方式: 正在设置壁纸… - 壁纸设置成功 - 纵向长宽比 - 横向长宽比 + 设置壁纸成功 + 纵向高宽比 + 横向高宽比 + 主屏幕 + 锁定屏幕 + 主屏幕和锁定屏幕 + + + 幻灯片 + 间隔(秒): + 包含照片 + 包含视频 + 包含GIF + 随机顺序 + 倒序播放 + 循环播放 + 动画 + + 渐隐 + 滑动 + 幻灯片结束 + 没有找到可播放幻灯片媒体文件 + + + 直接分组文件夹 + + + 分组依据 + 不分组文件 + 文件夹 + 最后修改时间 + 最后修改时间(按日) + 最后修改时间(按月) + 拍摄时间 + 拍摄时间(按日) + 拍摄时间(按月) + 文件类型 + 扩展名 + 请注意,分组和排序是两种独立的组织方式 + + + 在小工具上显示的文件夹: + 显示文件夹名称 - 显示所有 - 自动播放 + 自动播放视频 + 记住上次视频播放位置 显示文件名 - 显示多媒体文件 - 仅图片 - 仅视频 - 仅 GIF - 图片,视频,GIF - 图片和视频 循环播放视频 GIF 缩略图 - 浏览时最大亮度 - 裁剪缩略图 - 全屏时方向 - 系统设置 - 设备方向 - 根据长宽比 - 全屏时黑色背景 + 全屏时最高亮度 + 裁剪缩略图为正方形 + 显示视频时长 + 全屏时媒体文件旋转方向 + 跟随系统设置 + 跟随设备旋转 + 根据高宽比 + 全屏时使用黑色背景和状态栏 水平滚动缩略图 + 全屏时自动隐藏状态栏 + 删除没有内容的空文件夹 + 使用纵向滑动手势控制照片亮度 + 使用纵向滑动手势控制视频音量和亮度 + 在主界面显示文件夹媒体计数 + 全屏浏览媒体时显示详细信息 + 管理详细信息项目 + 单指缩放 + 点击屏幕边缘来切换媒体 + 允许深度放大图像 + 当状态栏隐藏时隐藏扩展详情 + 显示底栏 + 在文件夹界面显示回收站 + 深度放大图像 + 以最高质量显示图像 + 在主屏幕界面的最后一项显示回收站 + 使用下滑手势退出全屏视图 + 双击两次后 1:1 放大图像 + 在单独的页面播放视频(可以左右滑动屏幕来快进/快退) + 显示留海(如果可用) + 允许使用手势旋转图像 + 文件加载优先级 + 快速 + 避免显示空文件 + 避免显示任何无效的文件 + 显示图片文件类型 + 允许双击来放大视频 + 文件夹缩略图样式 + File thumbnail style + Thumbnail spacing + 在单独一行中显示计数 + 在括号中显示计数 + 不显示计数 + 将较长的文件夹标题字符限制为1行 + 方形 + 圆角 + + + 缩略图 + 全屏显示媒体 + 扩展详情 + 底栏选项 + + + 管理底栏选项 + 收藏 + 隐藏文件 + + + 自定义 + 重置 + 正方形 + 裁剪旋转 + 滤镜 + + 调整 + 阴影 + 曝光 + 高光 + 亮度 + 对比度 + 饱和度 + 清晰度 + 伽马 + 黑色 + 白色 + 色温 + 锐化 + 重置 + 对焦 + + 放射 + 线性 + 镜像 + 高斯 + 文字 + 文字选项 + 文字颜色 + 字体 + 添加 + 编辑 + 拉伸 + 字体 + 颜色 + 背景颜色 + 对齐 + 移到最上层 + 删除 + 您的文字 + 笔刷 + 颜色 + 尺寸 + 硬度 + 移到最上层 + 删除 + 笔刷颜色 + 编辑器 + 关闭编辑器? + 你确定要放弃更改吗? + + + 取消 + 接受 + 保存 + 正在导出… + 正在导出 %s. + 贴纸 + 贴纸颜色 + 贴纸选项 + 添加 + 颜色 + 删除 + 置顶 + 拉伸 + 替换 + 不透明度 + 对比度 + 饱和度 + 亮度 + 上传 + 叠加层 + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + 水平翻转 + 垂直翻转 + 复原 + 取消复原 + 选色器 + 透明 + 白色 + 灰色 + 黑色 + 浅蓝 + 蓝色 + 紫色 + 淡紫 + 粉红 + 红色 + 橘色 + 金色 + 黄色 + 橄榄绿 + 绿色 + 碧绿 + 取色器 + 剪辑 + + + 如何把简易图库设置为设备的默认图库? + 首先在系统设置中的“应用”菜单下找到您当前的默认图库,选择并点击“默认打开”按钮,然后点击“清除默认值”。下次再打开图片或视频时,您会看到一个应用程序选择器,在这里选择“简易图库”并将其设为默认应用程序即可。 + 我用密码锁定了本应用,但我忘记密码了。该怎么办? + 有两种方法。您可以重新安装本应用,也可以在设置中清除此应用的数据,这样会重置您的所有设置,但不会删除任何媒体文件。 + 如何让某个相册始终显示在最上面? + 您可以长按该相册并在操作栏中点击图钉图标,这样就可以将其固定在顶部了。您也可以固定多个文件夹,已固定的项目会按照默认排序方法进行排序。 + 如何快进/快退视频? + 您可以双击屏幕边缘或点击搜索栏附近的当前时间或总时长的文本。如果您在应用设置中启用了“在单独的页面播放视频”,就可以使用水平手势来快进/快退视频。 + 文件夹的隐藏和排除有什么区别? + 排除功能只是防止其在简易图库中显示,而隐藏功能则使用的是系统提供的方法,这样的话在其他的图库应用中也会隐藏。它的工作原理是在给定的文件夹中创建一个空的.nomedia文件,您可以使用任何文件管理器来删除它。请注意,某些设备不允许隐藏\"Camera\",\"Screenshots\"和\"Downloads\"等文件夹。 + 为什么会出现音乐艺术家封面或贴纸文件夹? + 您可能会看到一些不寻常的相册出现。可以通过长按并选择“排除”来排除它们。在之后的对话框中,您可以选择父文件夹,这样也许就会阻止其他相关的相册出现。 + 有的图片文件夹没有显示,怎么回事? + 原因可能有很多,但解决方法很简单。只需进入设置 -> 管理包含目录,点击+号并选择该文件夹。 + 如果我只想显示几个特定的文件夹,该如何操作? + 在包含目录中添加文件夹不会自动排除其他的内容。您可以这样做:进入设置 -> 管理排除目录,排除根目录\"/\",然后在设置 -> 管理包含目录中添加所需的文件夹。这样就可以只显示选定的文件夹了,由于排除和包含都是递归的,所以排除并包含的文件夹是会显示的。 + 我可以裁剪图片吗? + 当然可以,通过长按图片缩略图并选择编辑,或是在全屏视图中选择编辑来打开编辑器。您可以通过拖动图片边角来剪裁图片。 + 我能将媒体文件缩略图分组吗? + 当然可以,只需在缩略图视图中使用\"分组依据\"菜单项即可。您可以依据多个条件对文件进行分组,包括拍摄日期。如果您使用了\"显示所有文件夹内容\"功能,则可以按文件夹对它们进行分组。 + 按拍摄日期排序似乎有异常,我该如何解决? + 很可能是因为图片是从某处复制过来的。您可以长按文件缩略图并选择\"修复拍摄日期\"来修复它。 + 我看到图片上有一些色带。如何提高提高质量? + 目前显示图片的方案在绝大多数情况下都是正常的,如果您想要更好的图像质量,您可以在设置中启用\"以最高质量显示图像\"。 + 我隐藏了某个文件/文件夹。如何取消隐藏? + 您可以点击主界面上的\"暂时显示隐藏的项目\"选项,或在设置中开启\"显示隐藏的项目\"。 如果您想取消隐藏它,长按它并选择\"取消隐藏\"即可。 我们是通过向文件夹中添加\".nomedia\"文件来隐藏文件夹的,使用文件管理器删除该文件也可以取消隐藏。 + 为什么应用占用了这么多的空间? + 应用缓存最多可达250MB,这样可以使图像加载更快。如果应用占用了更多空间,则很可能是因为回收站中有项目。这些文件被计入应用程序大小。您可以打开回收站并删除所有文件,或从应用设置中清除回收站。回收站中的内容会在30天后自动删除。 + + 简易图库 Pro - 图片管理 & 编辑 - 一个没有广告,用来观看照片及视频的相册。 + 使用此照片和视频库来毫无阻碍地浏览您的回忆 - 一个观看照片和视频的简单实用工具。项目可以根据日期、大小、名称来递增或递减排序,照片可以缩放。媒体文件根据屏幕的大小排列在多个方格中,您可以使用缩放手势来调整每一列的方格数量。媒体文件可以被重命名、分享、删除、复制以及移动。照片亦可被剪切、旋转或是直接在应用中设为壁纸。 + 简易图库 Pro 是一个高度可定制的图库。你可以管理并编辑您的照片,从回收站中恢复已删除的照片,保护并隐藏文件,查看RAW,SVG等等多种照片和视频格式。 - 相册亦提供能让第三方应用预览图片/视频、向电子邮件客户端添加附件等的功能。非常适合日常使用。 + 该应用不包含广告和不必要的权限。我们保护您的隐私,因为该应用不需要联网权限。 - 应用不包含广告与不必要的权限。它是完全开放源代码的,并内置自定义颜色主题。 + ------------------------------------------------- + 简易图库 Pro – 特性 + ------------------------------------------------- - 这个应用只是一系列应用中的一小部份。您可以在 http://www.simplemobiletools.com 找到其余的应用。 + • 完全离线,没有广告或弹出窗口 + • 简易图库图片编辑器 – 裁剪,旋转,调整大小,绘制,滤镜等等 + • 无需联网权限,为您提供更多的隐私和安全 + • 没有不必要的权限 + • 快速搜索图像,视频和文件 + • 支持打开并查看多种照片和视频类型(RAW,SVG,全景等) + • 各种直观的手势,便于编辑和管理文件 + • 多种过滤、分组和排序文件的方法 + • 自定义应用界面外观 + • 支持多达 32 种语言 + • 支持将文件添加到收藏夹以便快速访问 + • 支持使用图案、密码或指纹保护您的照片和视频 + • 同样支持使用图案、密码或指纹保护应用或特定功能 + • 从回收站中恢复已删除的照片和视频 + • 支持隐藏照片和视频 + • 为您的文件创建一个可自定义的幻灯片 + • 查看文件的详细信息(分辨率,EXIF 值等等) + • 该应用是开源的 + … 还有很多很多! + + 图库照片编辑 + 简易图库 Pro 可以轻松地动态编辑图片。支持裁剪、翻转、旋转、或是调整图片大小。如果您希望更有创意的话,可以添加滤镜,或是直接在图片上绘制! + + 支持多种文件类型 + 不同于其他一些图库应用,简易图库 Pro 支持多种文件类型,包括JPEG,PNG,MP4,MKV,RAW,SVG,全景照片,全景视频等等。 + + 高度可定制的图库 + 不论是UI还是底部工具栏上的功能按钮,简易图库 Pro 可按照您的要求高度自定义。其他图库应用可没有这种灵活性!由于该应用是开源的,所以我们还提供 32 种语言! + + 恢复已删除的照片和视频 + 不小心删除了珍贵的照片或视频?别担心!简易图库 Pro 具有方便的回收站,您可以轻松地恢复已删除的照片和视频。 + + 保护并隐藏照片、视频和文件 + 使用密码、图案或指纹保护和隐藏照片、视频、或是整个相册。您也可以保护应用自身或禁用一些特定功能。 例如,只有指纹验证通过才可以删除文件,从而有效地防止您的文件被意外删除。 + + 在这里查看全套简易应用系列: + https://www.simplemobiletools.com + + 简易图库 Pro 的独立网站: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + 篩選媒體檔案 + 圖片 + 影片 + GIF + RAW圖檔 + SVG + Portraits + 選擇的篩選條件未發現媒體檔案。 + 更改篩選條件 + + + 這功能藉由添加一個\".nomedia\"檔案,來隱藏資料夾和所有子資料夾。您可以透過[設定]中的\"顯示隱藏的項目\"選項來查看。\n是否繼續? + 排除 + 排除資料夾 + 管理排除資料夾 + 此資料夾與子資料夾將只會都從簡易相簿中排除。您可以在設定中進行管理。 + 是否排除上層資料夾? + [排除資料夾]只會將選擇的資料夾與子資料夾一起從簡易相簿中隱藏,他們仍會出現在其他應用程式中。\n\n如果您要在其他應用程式中也隱藏,請使用[隱藏]功能。 + 移除全部 + 是否將排除列表中的所有資料夾都移除?這不會刪除資料夾。 + 隱藏資料夾 + 管理隱藏資料夾 + 您似乎沒有用\".nomedia\"檔案來隱藏的資料夾 + + + 包含資料夾 + 管理包含資料夾 + 增加資料夾 + 如果有些資料夾含有媒體檔案,卻沒被辨識到,您可以在此手動加入。 + No media files have been found. You can solve it by adding the folders containing media files manually. + + + 縮放 + 縮放所選區域並儲存 + 寬度 + 高度 + 保持長寬比 + 請輸入有效的解析度 + + + 編輯器 + 旋轉 + 無效的圖片路徑 + Invalid video path + 圖片編輯失敗 + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully + 編輯圖片: + Edit video with: + 找不到圖片編輯器 + No video editor found + 未知的檔案位置 + 無法誤蓋原始檔案 + 向左轉 + 向右轉 + 旋轉180º + 翻轉 + 水平翻轉 + 垂直翻轉 + 自由 + 其它 + + + 簡易桌布 + 設為桌布 + 設為桌布失敗 + 用其他程式設為桌布: + 桌布設定中… + 成功設為桌布 + 直向長寬比 + 橫向長寬比 + 主頁螢幕 + 鎖定螢幕 + 主頁和鎖定螢幕 + + + 投影片 + 間隔 (秒): + 包含照片 + 包含影片 + 包含GIF + 隨機順序 + 反向播放 + 投影片循環 + Animation + None + Fade + Slide + 投影片結束 + 找不到投影片的媒體檔案 + + + 改變瀏覽類型 + 格狀 + 列表 + 歸類子資料夾 + + + 歸類 + 不歸類檔案 + 資料夾 + 最後修改 + Last modified (daily) + Last modified (monthly) + 拍照日期 + Date taken (daily) + Date taken (monthly) + 檔案類型 + 副檔名 + 請注意,歸類和排序是兩者是獨立的 + + + 在小工具顯示資料夾: + 顯示資料夾名稱 + + + 自動播放影片 + 記住影片上次播放位置 + 顯示檔案名稱 + 影片循環播放 + 縮圖顯示GIF動畫 + 瀏覽時最大亮度 + 縮圖裁剪成正方形 + 顯示影片長度 + 全螢幕時旋轉方向 + 系統設定方向 + 裝置實際方向 + 圖片長寬比 + 全螢幕時黑色背景和狀態欄 + 橫向滑動縮圖 + 全螢幕時自動隱藏系統介面 + 刪除內容後刪除空白資料夾 + 允許用上下手勢來控制相片的亮度 + 允許用上下手勢來控制影片的音量和亮度 + 主畫面顯示資料夾內的媒體檔案數量 + 全螢幕時顯示詳細資訊 + 管理詳細資訊 + 全螢幕時允許單指縮放 + 允許點擊螢幕邊緣來快速切換媒體檔案 + 允許深度縮放圖片 + 狀態欄隱藏時,同時隱藏詳細資訊 + 在螢幕底部顯示一些操作按鈕 + 在資料夾畫面顯示回收桶 + 可深度縮放的圖片 + 以最高品質顯示圖片 + 回收桶顯示在主畫面最後一項 + 允許用下滑手勢來關閉全螢幕檢視 + 允許用兩次雙擊來1:1縮放 + 總是用新的水平手勢在獨立畫面開啟影片 + 如果可以,顯示瀏海螢幕 + 允許用手勢來旋轉圖片 + 檔案讀取優先權 + 速度 + 折衷 + 避免顯示無效的檔案 + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + 縮圖 + 全螢幕媒體檔案 + 詳細資訊 + 底部操作 + + + 管理要顯示的底部操作 + 我的最愛 + 檔案顯示 + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + 我如何將簡易相簿設為預設相簿? + 首先你必須先在裝置設定中應用程式部分尋找目前預設相簿,找到一個像是\"預設開啟(Open by default)\"的按鈕,點下去,然後選擇\"清除預設(Clear defaults)\"。 + 下次你要開啟圖片或影片時應該會看到程式選擇器,你可以在那裡選擇簡易相簿並設為預設程式。 + 我用密碼鎖住了這應用程式,但我忘記了。怎麼辦? + 有兩個解決方法。你可以重新安裝應用程式,或者在裝置設定中找到這程式然後選擇\"清除資料(Clear data)\"。這將重置你程式內所有設定,不會移除任何媒體檔案。 + 我如何讓某個相冊總是出現在頂端? + 你可以長按想要的相冊,然後在操作選單中選擇[圖釘]圖示,就會釘選於頂端。你也能釘選多個資料夾,釘選的項目會依預設的排序方法來排序。 + 我如何快轉影片? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + 隱藏和排除資料夾,兩者有什麼不同? + [排除]只在簡易相簿中避免顯示出來;而[隱藏]則作用於整個系統,資料夾也會被其他相簿隱藏。這是藉由在指定資料夾內建立一個\".nomedia\"空白檔案來進行隱藏,你之後也能用任何檔案管理器移除。 Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + 為什麼有些音樂專輯封面或貼圖的資料夾會出現? + 這情況可能發生,你會看到一些異常的相冊出現。你可以長按再選擇[排除]來輕易排除他們。在下個對話框,你可以選擇上層資料夾,有可能讓其他相關相冊也避免出現。 + 有一個圖片資料夾沒有顯示出來,怎麼辦? + 可能有多種原因,不過很好解決。只要到[設定] -> [管理包含資料夾],選擇[加號]然後導引到需要的資料夾。 + 如果我只想看到幾個特定的資料夾,怎麼做? + 在[包含資料夾]內添加資料夾並不會自動排除任何東西。你能做的是到[設定] -> [管理排除資料夾],排除根目錄 \"/\",然後在[設定] -> [管理包含資料夾]添加想要的資料夾。 + 那樣的話就只有選擇的資料夾可見。因為排除和包含都是遞迴的,如果一個資料夾被排除又被包含,則會顯示出來。 + 我可以用這程式裁減圖片嗎? + 是的,你能夠在編輯器內拉動圖片角落來裁剪圖片。要進入編輯器,你可以長按圖片縮圖然後選擇[編輯],或是在全螢幕檢視下選擇[編輯]。 + 我可以歸類媒體檔案的縮圖嗎? + 當然,只要在縮圖瀏覽中使用[歸類]選單項目就可以了。你能依多種條件歸類檔案,包含拍照日期。如果你使用了[資料夾內容全部顯示]功能,你還能以資料夾來歸類。 + 依拍照日期排序似乎沒正確運作,我該如何修復? + 那很可能是由於檔案從某處複製過來所造成的。你可以選擇檔案縮圖,然後選擇\"修復拍照日期數值\"來進行修復。 + 我在圖片上看到一些色彩條紋。我如何提升品質? + 目前顯示圖片的處理方法,在大部分情況下都能正常運行。但如果你想要更好的圖片品質,你可以在程式設定中[可深度縮放的圖片]部分,啟用[以最高品質顯示圖片]。 + 我隱藏了一個檔案/資料夾。我如何取消隱藏? + 你可以在主畫面的選單項按[暫時顯示隱藏的項目],或者在程式設定中切換[顯示隱藏的項目]來看隱藏項目。如果你想要取消隱藏,只要長按然後選擇[取消隱藏]。以添加\".nomedia\"檔案進行隱藏的資料夾,你也可以用任何檔案管理器來刪除這檔案。 + 為什麼這應用程式占用了這麼多空間? + 應用程式快取最多占用250MB,以確保更快的圖片讀取。如果這應用程式占用了多更多的空間,最有可能是因為你在垃圾桶內有東西。那些檔案也計入應用程式大小內。你可以開啟垃圾桶並刪除全部檔案,或者從應用程式設定來清除垃圾桶。垃圾桶內的每個檔案都會在30天後自動刪除。 + + + + 簡易相簿 Pro - 相片管理&編輯器 + + Browse your memories without any interruptions with this photo and video gallery + + 簡易相簿Pro是一個高度自訂化的離線相簿。整理和編輯你的照片,從回收桶恢復刪除的檔案,保護和隱藏檔案,以及瀏覽大量不同的照片&影片格式,包含RAW、SVG…等更多。 + + 這應用程式沒有廣告和非必要的權限。並且由於不需要網路連線,您的隱私受到保護。 + + ------------------------------------------------- + 簡易相簿PRO – 特色 + ------------------------------------------------- + + • 沒有廣告和彈出畫面的離線相簿 + • 簡易相簿編輯器 – 裁減、旋轉、縮放、繪畫、濾鏡…等更多 + • 不需要網路連線,給您更多隱私及安全 + • 不會要求非必要的權限 + • 快速搜尋圖片、影片和檔案 + • 開啟和瀏覽多種不同的照片和影片類型 (RAW、SVG、全景之類的) + • 多種直觀的手勢,以便於編輯和整理檔案 + • 大量方法來篩選、歸類和排序檔案 + • 自訂簡易相簿Pro的外觀 + • 支援32種語言 + • 將檔案標記為我的最愛,以快速存取 + • 以圖形、PIN碼或指紋來保護您的照片&影片 + • 也能以圖形、PIN碼或指紋來保護應用程式開啟或特定功能 + • 從回收桶恢復刪除的照片&影片 + • 切換檔案的可見度來隱藏照片&影片 + • 用您的檔案建立可自訂的投影片 + • 查看您檔案的詳細資訊 (解析度、EXIF值…等) + • 簡易相簿Pro是開源的 + …還有多更多! + + 照片相簿編輯器 + 簡易相簿Pro使編輯圖片變得非常輕鬆。裁減、翻轉、旋轉及縮放您的圖片。如果您想更有一點創意,您可以直接在圖片上添加濾鏡和繪畫! + + 支援多種檔案類型 + 不同於其他相簿瀏覽器和照片整理器,簡易相簿Pro支援大量不同的檔案類型,包含JPEG、PNG、MP4、MKV、RAW、SVG、全景照片、全景影片…等更多。 + + 高度自訂化的相簿管理 + 從UI到底部工具列的功能按鈕,簡易相簿Pro是高度自訂化的,任您隨心所欲的方式操作。沒有其他相簿管理器有這樣的靈活性。歸功於開源,我們也支援32種語言! + + 恢復刪除的照片&影片 + 不小心刪除掉珍貴的照片或影片?別擔心!簡易相簿Pro標榜有便利的回收桶,您可以在那裡輕鬆恢復照片&影片。 + + 保護&隱藏照片、影片和檔案 + 使用Pin碼、圖形或裝置的指紋掃描器,您可以保護和隱藏照片、影片及整個相冊。您可以保護應用程式本身或者對程式的特定功能設個鎖。例如:您無法未經指紋掃描就刪除檔案,有助於檔案遭意外刪除。 + + 於此查看簡易工具系列全套: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools + + + + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index fdd431271..c2c209025 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,49 +1,73 @@ - 簡易藝廊 - 藝廊 + 簡易相簿 + 簡易相簿 編輯 開啟相機 - 以其他應用軟體開啟 - 沒有可用的應用軟體 - (隱藏) - 鎖定資料夾 - 解除鎖定資料夾 - 顯示所有資料夾的內容 - 所有資料夾 - 切換到資料夾檢視 + (隱藏) + (排除) + 釘選資料夾 + 取消釘選資料夾 + 釘選在頂端 + 資料夾內容全部顯示 + 全部資料夾 + 切換成資料夾檢視 其他資料夾 - Show on map - Unknown location - No application with maps has been found - No Camera app has been found - Increase column count - Reduce column count - Temporarily show hidden - Change cover image - Select photo - Use default + 在地圖上顯示 + 未知的位置 + 音量 + 亮度 + 鎖定方向 + 解除鎖定方向 + 改變方向 + 強制直向 + 強制橫向 + 使用預設方向 + 修復拍攝日期數值 + 修復中… + 日期修復成功 + 未發現拍攝日期數值 + 分享調整大小的版本 + 嘿\n\n你似乎從舊版免費應用程式升級了。現在你能解除安裝舊版了,在應用程式設定的頂端有個\'升級至專業版\'按鈕。\n\n將只有回收桶項目會被刪除,我的最愛項目會被解除標記,以及也會重置你的應用程式設定。\n\n感謝! + 檔案搜尋目標切換成全部的可見資料夾 + Set as default folder + Unset as default folder + + + 篩選媒體檔案 + 圖片 + 影片 + GIF + RAW圖檔 + SVG + 人像 + 選擇的篩選條件未發現媒體檔案。 + 更改篩選條件 - This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden folders\' option in Settings. Continue? - Exclude - Excluded folders - Manage excluded folders - This will exclude the selection together with its subfolders from Simple Gallery only. You can manage excluded folders in Settings. - Exclude a parent instead? - Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\\n\\nIf you want to hide them from other apps too, use the Hide function. - Remove all - Remove all folders from the list of excluded? This will not delete the folders. + 這功能藉由添加一個\".nomedia\"檔案,來隱藏資料夾和所有子資料夾。您可以透過[設定]中的\"顯示隱藏的項目\"選項來查看。\n是否繼續? + 排除 + 排除資料夾 + 管理排除資料夾 + 此資料夾與子資料夾將只會都從簡易相簿中排除。您可以在設定中進行管理。 + 是否排除上層資料夾? + [排除資料夾]只會將選擇的資料夾與子資料夾一起從簡易相簿中隱藏,他們仍會出現在其他應用程式中。\n\n如果您要在其他應用程式中也隱藏,請使用[隱藏]功能。 + 移除全部 + 是否將排除列表中的所有資料夾都移除?這不會刪除資料夾。 + 隱藏資料夾 + 管理隱藏資料夾 + 您似乎沒有用\".nomedia\"檔案來隱藏的資料夾 - Included folders - Manage included folders - Add folder - If you have some folders which contain media, but were not recognized by the app, you can add them manually here. + 包含資料夾 + 管理包含資料夾 + 增加資料夾 + 如果有些資料夾含有媒體檔案,卻沒被辨識到,您可以在此手動加入。 + 未發現媒體檔案。您可以手動添加包含媒體檔案的資料夾來解決。 縮放 - 縮放選區並儲存 + 縮放所選區域並儲存 寬度 高度 保持長寬比 @@ -51,66 +75,349 @@ 編輯器 - 儲存 旋轉 - 路徑 - 圖檔路徑錯誤 - 圖檔編輯失敗 - 編輯用: - 沒有可用的圖檔編輯器 - 未知檔案路徑 - 不能覆蓋源檔案 - Rotate left - Rotate right - Rotate by 180º - Flip - Flip horizontally - Flip vertically - Out of memory error + 無效的圖片路徑 + Invalid video path + 圖片編輯失敗 + Video editing failed + 圖片編輯取消 + Video editing cancelled + 檔案編輯成功 + Image edited successfully + Video edited successfully + 編輯圖片: + Edit video with: + 找不到圖片編輯器 + No video editor found + 未知的檔案位置 + 無法誤蓋原始檔案 + 向左轉 + 向右轉 + 旋轉180º + 翻轉 + 水平翻轉 + 垂直翻轉 + 自由 + 其它 簡易桌布 - 設定為桌布 - 桌布設定失敗 - 用其他應用軟體設定桌布…… - 沒有可用的應用軟體 - 正在應用軟體桌布… - 成功應用軟體桌布 - Portrait aspect ratio - Landscape aspect ratio + 設為桌布 + 設為桌布失敗 + 用其他程式設為桌布: + 桌布設定中… + 成功設為桌布 + 直向長寬比 + 橫向長寬比 + 主頁螢幕 + 鎖定螢幕 + 主頁和鎖定螢幕 + + + 投影片 + 間隔 (秒): + 包含照片 + 包含影片 + 包含GIF + 隨機順序 + 反向播放 + 投影片循環 + 動畫 + + 漸變 + 滑動 + 投影片結束 + 找不到投影片的媒體檔案 + + + 歸類子資料夾 + + + 歸類 + 不歸類檔案 + 資料夾 + 最後修改 + 最後修改 (按日) + 最後修改 (按月) + 拍攝日期 + 拍攝日期 (按日) + 拍攝日期 (按月) + 檔案類型 + 副檔名 + 請注意,歸類和排序兩者是獨立的 + + + 在小工具顯示資料夾: + 顯示資料夾名稱 - 秀出隱藏資料夾 自動播放影片 + 記住影片上次播放位置 顯示檔案名稱 - 秀出多媒體檔案 - 僅圖片 - 僅影片 - Gifs only - Images, videos, gifs - 圖片和影片 - 迴圈播放影片 - Animate gifs at thumbnails - Max brightness when viewing media - Crop thumbnails into squares - Rotate fullscreen media by - System setting - Device rotation - Aspect ratio - Dark background at fullscreen media - Scroll thumbnails horizontally + 影片循環播放 + 縮圖顯示GIF動畫 + 瀏覽時最大亮度 + 縮圖裁剪成正方形 + 顯示影片長度 + 全螢幕時旋轉方向 + 系統設定方向 + 裝置實際方向 + 圖片長寬比 + 全螢幕時黑色背景和狀態欄 + 橫向滑動縮圖 + 全螢幕時自動隱藏系統介面 + 刪除內容後刪除空白資料夾 + 允許用上下手勢來控制相片的亮度 + 允許用上下手勢來控制影片的音量和亮度 + 主畫面顯示資料夾內的媒體檔案數量 + 全螢幕時顯示詳細資訊 + 管理詳細資訊 + 全螢幕時允許單指縮放 + 允許點擊螢幕邊緣來快速切換媒體檔案 + 允許深度縮放圖片 + 狀態欄隱藏時,同時隱藏詳細資訊 + 在螢幕底部顯示一些操作按鈕 + 在資料夾畫面顯示回收桶 + 可深度縮放的圖片 + 以最高品質顯示圖片 + 回收桶顯示在主畫面最後一項 + 允許用下滑手勢來關閉全螢幕檢視 + 允許用兩次雙擊來1:1縮放 + 總是用新的水平手勢在獨立畫面開啟影片 + 如果可以,顯示瀏海螢幕 + 允許用手勢來旋轉圖片 + 檔案讀取優先權 + 速度 + 妥協 + 避免顯示無效的檔案 + 顯示圖片檔案類型 + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + 縮圖 + 全螢幕媒體檔案 + 詳細資訊 + 底部操作 + + + 管理要顯示的底部操作 + 我的最愛 + 檔案顯示 + + + 自訂 + 重置 + 正方形 + 變形 + 濾鏡 + + 調整 + 暗部 + 曝光 + 高光 + 亮度 + 對比 + 飽和度 + 清晰度 + Gamma + 黑色 + 白色 + 色溫 + 銳利度 + 重置 + 聚焦 + + 放射 + 線性 + 鏡像 + 高斯 + 文字 + 文字選項 + 文字顏色 + 字型 + 添加 + 編輯 + 拉伸 + 字型 + 顏色 + 背景顏色 + 對齊 + 移到最上層 + 刪除 + 你的文字 + 筆刷 + 顏色 + 尺寸 + 硬度 + 移到最上層 + 刪除 + 筆刷顏色 + 編輯器 + 關閉編輯器? + Do you really want to discard the changes? + + + 取消 + 接受 + 儲存 + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + 水平翻轉 + 垂直翻轉 + 復原 + 取消復原 + 顏色挑選器 + 透明 + 白色 + 灰色 + 黑色 + 淺藍 + 藍色 + 紫色 + 淡紫 + 粉紅 + 紅色 + 橘色 + 金色 + 黃色 + 橄欖 + 綠色 + 藍綠 + 顏色吸取器 + Trim + + + 我如何將簡易相簿設為預設相簿? + 首先你必須先在裝置設定中應用程式部分尋找目前預設相簿,找到一個像是\"預設開啟(Open by default)\"的按鈕,點下去,然後選擇\"清除預設(Clear defaults)\"。 + 下次你要開啟圖片或影片時應該會看到程式選擇器,你可以在那裡選擇簡易相簿並設為預設程式。 + 我用密碼鎖住了這應用程式,但我忘記了。怎麼辦? + 有兩個解決方法。你可以重新安裝應用程式,或者在裝置設定中找到這程式然後選擇\"清除資料(Clear data)\"。這將重置你程式內所有設定,不會移除任何媒體檔案。 + 我如何讓某個相冊總是出現在頂端? + 你可以長按想要的相冊,然後在操作選單中選擇[圖釘]圖示,就會釘選於頂端。你也能釘選多個資料夾,釘選的項目會依預設的排序方法來排序。 + 我如何快轉影片? + 你可以雙擊畫面邊緣,或點擊進度條附近的當前或總時長文字。 如果你在應用程式設定中啟用在獨立畫面打開影片,也能使用水平手勢。 + 隱藏和排除資料夾,兩者有什麼不同? + [排除]只在簡易相簿中避免顯示出來;而[隱藏]則作用於整個系統,資料夾也會被其他相簿隱藏。這是藉由在指定資料夾內建立一個\".nomedia\"空白檔案來進行隱藏,你之後也能用任何檔案管理器移除。 Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + 為什麼有些音樂專輯封面或貼圖的資料夾會出現? + 這情況可能發生,你會看到一些異常的相冊出現。你可以長按再選擇[排除]來輕易排除他們。在下個對話框,你可以選擇上層資料夾,有可能讓其他相關相冊也避免出現。 + 有一個圖片資料夾沒有顯示出來,怎麼辦? + 可能有多種原因,不過很好解決。只要到[設定] -> [管理包含資料夾],選擇[加號]然後導引到需要的資料夾。 + 如果我只想看到幾個特定的資料夾,怎麼做? + 在[包含資料夾]內添加資料夾並不會自動排除任何東西。你能做的是到[設定] -> [管理排除資料夾],排除根目錄 \"/\",然後在[設定] -> [管理包含資料夾]添加想要的資料夾。 + 那樣的話就只有選擇的資料夾可見。因為排除和包含都是遞迴的,如果一個資料夾被排除又被包含,則會顯示出來。 + 我可以用這程式裁減圖片嗎? + 是的,你能夠在編輯器內拉動圖片角落來裁剪圖片。要進入編輯器,你可以長按圖片縮圖然後選擇[編輯],或是在全螢幕檢視下選擇[編輯]。 + 我可以歸類媒體檔案的縮圖嗎? + 當然,只要在縮圖瀏覽中使用[歸類]選單項目就可以了。你能依多種條件歸類檔案,包含拍攝日期。如果你使用了[資料夾內容全部顯示]功能,你還能以資料夾來歸類。 + 依拍攝日期排序似乎沒正確運作,我該如何修復? + 那很可能是由於檔案從某處複製過來所造成的。你可以選擇檔案縮圖,然後選擇\"修復拍攝日期數值\"來進行修復。 + 我在圖片上看到一些色彩條紋。我如何提升品質? + 目前顯示圖片的處理方法,在大部分情況下都能正常運行。但如果你想要更好的圖片品質,你可以在程式設定中[可深度縮放的圖片]部分,啟用[以最高品質顯示圖片]。 + 我隱藏了一個檔案/資料夾。我如何取消隱藏? + 你可以在主畫面的選單項按[暫時顯示隱藏的項目],或者在程式設定中切換[顯示隱藏的項目]來看隱藏項目。如果你想要取消隱藏,只要長按然後選擇[取消隱藏]。以添加\".nomedia\"檔案進行隱藏的資料夾,你也可以用任何檔案管理器來刪除這檔案。 + 為什麼這應用程式占用了這麼多空間? + 應用程式快取最多占用250MB,以確保更快的圖片讀取。如果這應用程式占用了多更多的空間,最有可能是因為你在垃圾桶內有東西。那些檔案也計入應用程式大小內。你可以開啟垃圾桶並刪除全部檔案,或者從應用程式設定來清除垃圾桶。垃圾桶內的每個檔案都會在30天後自動刪除。 + + 簡易相簿 Pro - 相片管理&編輯器 - 一個沒有廣告,用來觀看照片及影片的藝廊。 + 使用相片和影片相簿來毫無阻礙地瀏覽您的回憶。 - 一個觀看照片跟影片的簡單實用工具。項目可以根據日期、大小、名稱來進行遞增及遞減排序,照片可以縮放。媒體檔案們根據螢幕的大小呈列在多個方格中,您可以使用捏放手勢來調整一列中的方格數量。媒體檔案可以被重新命名、分享、刪除、複製以及移動。照片亦可被裁切、旋轉或是直接在應用軟體中設定為桌布。 + 簡易相簿Pro是一個高度自訂化的離線相簿。整理和編輯你的照片,從回收桶恢復刪除的檔案,保護和隱藏檔案,以及瀏覽大量不同的照片&影片格式,包含RAW、SVG…等更多。 - 藝廊亦提供讓第三方軟體能夠用來預覽圖片/影片、添加附件於電子郵件客戶端軟體中等功能。非常適合日常使用。 + 這應用程式沒有廣告和非必要的權限。並且由於不需要網路連線,您的隱私受到保護。 - 應用軟體不包含廣告與非必要的權限。它是完全開放原始碼的,並內建自訂顔色之使用者介面主題。 + ------------------------------------------------- + 簡易相簿PRO – 特色 + ------------------------------------------------- - 這個應用軟體只是一系列應用軟體中的一小部份。您可以在 http://www.simplemobiletools.com 找到剩下的軟體。 + • 沒有廣告和彈出畫面的離線相簿 + • 簡易相簿編輯器 – 裁減、旋轉、縮放、繪畫、濾鏡…等更多 + • 不需要網路連線,給您更多隱私及安全 + • 不會要求非必要的權限 + • 快速搜尋圖片、影片和檔案 + • 開啟和瀏覽多種不同的照片和影片類型 (RAW、SVG、全景之類的) + • 多種直觀的手勢,以便於編輯和整理檔案 + • 大量方法來篩選、歸類和排序檔案 + • 自訂簡易相簿Pro的外觀 + • 支援32種語言 + • 將檔案標記為我的最愛,以快速存取 + • 以圖形、PIN碼或指紋來保護您的照片&影片 + • 也能以圖形、PIN碼或指紋來保護應用程式開啟或特定功能 + • 從回收桶恢復刪除的照片&影片 + • 切換檔案的可見度來隱藏照片&影片 + • 用您的檔案建立可自訂的投影片 + • 查看您檔案的詳細資訊 (解析度、EXIF值…等) + • 簡易相簿Pro是開源的 + …還有多更多! + + 照片相簿編輯器 + 簡易相簿Pro使編輯圖片變得非常輕鬆。裁減、翻轉、旋轉及縮放您的圖片。如果您想更有一點創意,您可以直接在圖片上添加濾鏡和繪畫! + + 支援多種檔案類型 + 不同於其他相簿瀏覽器和照片整理器,簡易相簿Pro支援大量不同的檔案類型,包含JPEG、PNG、MP4、MKV、RAW、SVG、全景照片、全景影片…等更多。 + + 高度自訂化的相簿管理 + 從UI到底部工具列的功能按鈕,簡易相簿Pro是高度自訂化的,任您隨心所欲的方式操作。沒有其他相簿管理器有這樣的靈活性。歸功於開源,我們也支援32種語言! + + 恢復刪除的照片&影片 + 不小心刪除掉珍貴的照片或影片?別擔心!簡易相簿Pro標榜有便利的回收桶,您可以在那裡輕鬆恢復照片&影片。 + + 保護&隱藏照片、影片和檔案 + 使用Pin碼、圖形或裝置的指紋掃描器,您可以保護和隱藏照片、影片及整個相冊。您可以保護應用程式本身或者對程式的特定功能設個鎖。例如:您無法未經指紋掃描就刪除檔案,有助於檔案遭意外刪除。 + + 於此查看簡易工具系列全套: + https://www.simplemobiletools.com + + 簡易相簿 Pro的獨立網站: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools - @color/default_dark_theme_text_color - @color/default_dark_theme_background_color + #66000000 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 4944170eb..48dd76ef1 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,11 +2,26 @@ 150dp 100dp - 20dp - 40dp - 150dp - 24dp - 50dp - - 56dp + 20dp + 22dp + 26dp + 96dp + 70dp + 60dp + 60dp + 72dp + 64dp + 128dp + 164dp + 48dp + 76dp + 90dp + 98dp + 180dp + 48dp + 86dp + 110dp + 40dp + 30dp + 180dp diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index d1487c62f..a36ddae4c 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -1,69 +1,42 @@ + GIF + RAW + SVG + com.simplemobiletools.gallery.pro - Allow setting custom folder covers - - Allow selecting multiple items by finger dragging\n - Added an option to always use black background at fullscreen media + + Adding some folder thumbnail customization at the app settings\n + Allow setting a default folder to be opened at app launch - Allow hiding individual files by prepending filenames with a dot - Added horizontal and vertical image flipping in the image editor - - Allow setting portrait wallpapers\n - Added more fields in photo Properties dialog + Allow fast forwarding videos by double clicking on screen sides + Fully replaced the photo editor with a powerful third party library (for resizing images use Menu -> Resize from the fullscreen view, until it gets added in the editor) + + Allow customizing the bottom navigation bar color\n + Share files in the order they were selected - - Improved fullscreen image zooming\n - Added more settings related to screen autorotating\n - Split copy and move functions for ease of use + + Implemented export/importing for app settings and other preferences, like sorting\n + Allow hiding Notch on fullscreen view on Android 9+\n + Autosave images zoomed at the fullscreen view - - Added an option to temporarily show hidden folders\n - Added a setting for preventing thumbnail croping to square\n - Added a setting for auto-rotating the screen depending on photo aspect ratio + + Allow drawing in the editor\n + Allow batch rotating images from the thumbnails view (proper thumbnail refreshing might need an app restart)\n + Allow sharing images directly from the editor\n + Allow zooming GIFs and videos - - Added an option to use max brightness at viewing fullscreen media\n - Added an option to manually include folders which contain media, but were not recognized by the app + Reverted to the old way of opening videos, opening on separate screen can be enabled in the app settings + + Rewrote video playback, use a separate screen + added fast-forwarding with horizontal swiping\n + Added optional 1:1 pixel ratio zooming with two double taps at fullscreen view\n + Allow adding Copy at the fullscreen bottom actions\n + Always include images at slideshows, never videos - - Fixed some sharing and editor issues\n - Sorry for the frequent updates lately, they should be stopped now. + + Added an initial widget implementation for creating homescreen folder shortcuts\n + Added optional grouping of direct subfolders, as a check at the \"Change view type\" dialog - - Exclude the subfolders of excluded folders too\n - Added an easy way of excluding parent folders from the exclude confirmation dialog\n - Added draggable scrollbars\n - Allow setting a third party video player as the default - - Added an option to toggle gif animation at thumbnails - Allow setting different sorting per folder - - Implement proper folder hiding via .nomedia file\n - Allow managing excluded folders in Settings - - Added menu items for easy image rotating in fullscreen view - Added menu buttons for changing the column count - Allow picking colors by hex codes - Allow showing the photos and videos on a map, if there are available map coordinates - Show some additional Exif data at photo properties - - Allow zooming pngs and gifs\n - Allow creating new folders at Copy/Move or Save as dialog destinations - - Added an option to loop videos automatically - - Added more color customization options\n - Your settings have been cleared, please reset them - - Allow changing the column count with pinch gestures - Added an option to display images or videos only - Added a Select all button at selecting media and folders - - Added an image resizer to the editor\n - Allow displaying images and videos from all folders together - - Allow pinning folders at the top diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml index 3d424225f..cc7e8155c 100644 --- a/app/src/main/res/values/integers.xml +++ b/app/src/main/res/values/integers.xml @@ -1,4 +1,8 @@ - 2 - 3 + 2 + 3 + 3 + 4 + + 33794 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a65e48784..1fa2aa552 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,28 +4,48 @@ Gallery Edit Open camera - Open with - No valid app found (hidden) + (excluded) Pin folder Unpin folder + Pin to the top Show all folders content All folders Switch to folder view Other folder Show on map Unknown location - No application with maps has been found - No Camera app has been found - Increase column count - Reduce column count - Temporarily show hidden - Change cover image - Select photo - Use default + Volume + Brightness + Lock orientation + Unlock orientation + Change orientation + Force portrait + Force landscape + Use default orientation + Fix Date Taken value + Fixing… + Dates fixed successfully + No Date Taken values have been found + Share a resized version + Hey,\n\nseems like you upgraded from the old free app. You can now uninstall the old version, which has an \'Upgrade to Pro\' button at the top of the app settings.\n\nYou will only have the Recycle bin items deleted, favorite items unmarked and you will also have to reset your app settings.\n\nThanks! + Switch to file search across all visible folders + Set as default folder + Unset as default folder + + + Filter media + Images + Videos + GIFs + RAW images + SVGs + Portraits + No media files have been found with the selected filters. + Change filters - This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden folders\' option in Settings. Continue? + This function hides the folder by adding a \'.nomedia\' file into it, it will hide all subfolders too. You can see them by toggling the \'Show hidden items\' option in Settings. Continue? Exclude Excluded folders Manage excluded folders @@ -34,12 +54,16 @@ Excluding folders will make them together with their subfolders hidden just in Simple Gallery, they will still be visible in other applications.\n\nIf you want to hide them from other apps too, use the Hide function. Remove all Remove all folders from the list of excluded? This will not delete the folders. + Hidden folders + Manage hidden folders + Seems like you don\'t have any folders hidden with a \".nomedia\" file. Included folders Manage included folders Add folder - If you have some folders which contain media, but were not recognized by the app, you can add them manually here. + If you have some folders which contain media, but were not recognized by the app, you can add them manually here.\n\nAdding some items here will not exclude any other folder. + No media files have been found. You can solve it by adding the folders containing media files manually. Resize @@ -51,13 +75,20 @@ Editor - Save Rotate - Path Invalid image path + Invalid video path Image editing failed + Video editing failed + Image editing cancelled + Video editing cancelled + File edited successfully + Image edited successfully + Video edited successfully Edit image with: - No image editor found + Edit video with: + No image editor found + No video editor found Unknown file location Could not overwrite the source file Rotate left @@ -66,51 +97,327 @@ Flip Flip horizontally Flip vertically - Out of memory error + Free + Other Simple Wallpaper Set as Wallpaper Setting as Wallpaper failed Set as wallpaper with: - No app capable of it has been found Setting wallpaper… Wallpaper set successfully Portrait aspect ratio Landscape aspect ratio + Home screen + Lock screen + Home and lock screen + + + Slideshow + Interval (seconds): + Include photos + Include videos + Include GIFs + Random order + Move backwards + Loop slideshow + Animation + None + Fade + Slide + The slideshow ended + No media for the slideshow have been found + + + Group direct subfolders + + + Group by + Do not group files + Folder + Last modified + Last modified (daily) + Last modified (monthly) + Date taken + Date taken (daily) + Date taken (monthly) + File type + Extension + Please note that grouping and sorting are 2 independent fields + + + Folder shown on the widget: + Show folder name - Show hidden media Play videos automatically + Remember last video playback position Toggle filename visibility - Show media - Images only - Videos only - Gifs only - Images, videos, gifs - Images and videos Loop videos - Animate gifs at thumbnails - Max brightness when viewing media + Animate GIFs at thumbnails + Max brightness when viewing fullscreen media Crop thumbnails into squares + Show video durations Rotate fullscreen media by System setting Device rotation Aspect ratio - Dark background at fullscreen media + Black background at fullscreen media Scroll thumbnails horizontally + Automatically hide system UI at fullscreen media + Delete empty folders after deleting their content + Allow controlling photo brightness with vertical gestures + Allow controlling video volume and brightness with vertical gestures + Show folder media count on the main view + Show extended details over fullscreen media + Manage extended details + Allow one finger zoom at fullscreen media + Allow instantly changing media by clicking on screen sides + Allow deep zooming images + Hide extended details when status bar is hidden + Show some action buttons at the bottom of the screen + Show the Recycle Bin at the folders screen + Deep zoomable images + Show images in the highest possible quality + Show the Recycle Bin as the last item on the main screen + Allow closing the fullscreen view with a down gesture + Allow 1:1 zooming in with two double taps + Always open videos on a separate screen with new horizontal gestures + Show a notch if available + Allow rotating images with gestures + File loading priority + Speed + Compromise + Avoid showing invalid files + Show image file types + Allow zooming videos with double tapping them + Folder thumbnail style + File thumbnail style + Thumbnail spacing + Show file count on a separate line + Show file count in brackets + Do not show file count + Limit long folder titles to 1 line + Square + Rounded corners + + + Thumbnails + Fullscreen media + Extended details + Bottom actions + + + Manage visible bottom actions + Toggle favorite + Toggle file visibility + + + Custom + Reset + Square + Transform + Filter + None + Adjust + Shadows + Exposure + Highlights + Brightness + Contrast + Saturation + Clarity + Gamma + Blacks + Whites + Temperature + Sharpness + Reset + Focus + None + Radial + Linear + Mirrored + Gaussian + Text + Text Options + Text Color + Font + Add + Edit + Straighten + Font + Color + BG Color + Alignment + To Front + Delete + Your text + Brush + Color + Size + Hardness + To Front + Delete + Brush Color + Editor + Close Editor? + Do you really want to discard the changes? + Yes + No + Cancel + Accept + Save + Exporting… + Exporting %s. + Sticker + Sticker Color + Sticker Options + Add + Color + Delete + To Front + Straighten + Replace + Opacity + Contrast + Saturation + Brightness + Uploads + Overlay + Normal + Darken + Screen + Overlay + Lighten + Multiply + Color Burn + Soft Light + Hard Light + None + Golden + Lightleak 1 + Mosaic + Paper + Rain + Vintage + Flip H + Flip V + Undo + Redo + Color Picker + Transparent + White + Gray + Black + Light blue + Blue + Purple + Orchid + Pink + Red + Orange + Gold + Yellow + Olive + Green + Aquamarin + Pipettable color + Trim + + + How can I make Simple Gallery the default device gallery? + First you have to find the currently default gallery in the Apps section of your device settings, look for a button that says something like \"Open by default\", click on it, then select \"Clear defaults\". + The next time you will try opening an image or video you should see an app picker, where you can select Simple Gallery and make it the default app. + I locked the app with a password, but I forgot it. What can I do? + You can solve it in 2 ways. You can either reinstall the app, or find the app in your device settings and select \"Clear data\". It will reset all your settings, it will not remove any media files. + How can I make an album always appear at the top? + You can long press the desired album and select the Pin icon at the actions menu, that will pin it to the top. You can pin multiple folders too, pinned items will be sorted by the default sorting method. + How can I fast forward videos? + You can do it by double tapping the side of the screen, or tapping the current or max duration texts near the seekbar. If you enable opening videos on a separate screen in the app settings, you can use horizontal gestures too. + What is the difference between hiding and excluding a folder? + Exclude prevents displaying the folder only in Simple Gallery, while Hide works system-wise and it hides the folder from other galleries too. It works by creating an empty \".nomedia\" file in the given folder, which you can then remove with any file manager too. Note that some devices do not allow hiding folders like Camera, Screenshots and Downloads. + Why do folders with music cover art or stickers show up? + It can happen that you will see some unusual albums show up. You can easily exclude them by long pressing them and selecting Exclude. In the next dialog you can then select the parent folder, chances are it will prevent the other related albums showing up too. + A folder with images isn\'t showing up, or it doesn\'t show all items. What can I do? + That can have multiple reasons, but solving it is easy. Just go in Settings -> Manage Included Folders, select Plus and navigate to the required folder. + What if I want just a few particular folders visible? + Adding a folder at the Included Folders doesn\'t automatically exclude anything. What you can do is go in Settings -> Manage Excluded Folders, exclude the root folder \"/\", then add the desired folders at Settings -> Manage Included Folders. + That will make only the selected folders visible, as both excluding and including are recursive and if a folder is both excluded and included, it will show up. + Can I crop images with this app? + Yes, you can crop images in the editor, by dragging the image corners. You can get to the editor either by long pressing an image thumbnail and selecting Edit, or selecting Edit from the fullscreen view. + Can I somehow group media file thumbnails? + Sure, just use the \"Group by\" menu item while at the thumbnails view. You can group files by multiple criteria, including Date Taken. If you use the \"Show all folders content\" function you can group them by folders too. + Sorting by Date Taken doesn\'t seem to work properly, how can I fix it? + It is most likely caused by the files being copied from somewhere. You can fix it by selecting the file thumbnails and selecting \"Fix Date Taken value\". + I see some color banding on the images. How can I improve the quality? + The current solution for displaying images works fine in the vast majority of cases, but if you want even better image quality, you can enable the \"Show images in the highest possible quality\" at the app settings, in the \"Deep zoomable images\" section. + I have hidden a file/folder. How can I unhide it? + You can either press the \"Temporarily show hidden items\" menu item at the main screen, or toggle \"Show hidden items\" in the app settings to see the hidden item. If you want to unhide it, just long press it and select \"Unhide\". Folders are hidden by adding a hidden \".nomedia\" file into them, you can delete the file with any file manager too. + Why does the app take up so much space? + App cache can take up to 250MB, it ensures quicker image loading. If the app is taking up even more space, it is most likely caused by you having items in the Recycle Bin. Those files count to the app size. You can clear the Recycle bin by opening it and deleting all files, or from the app settings. Every file in the Bin is deleted automatically after 30 days. + + Simple Gallery Pro - Photo Manager & Editor - A gallery for viewing photos and videos without ads. + A premium app for managing and editing your photos, videos, GIFs without ads - A simple tool usable for viewing photos and videos. Items can be sorted by date, size, name both ascending or descending, photos can be zoomed in. Media files are shown in multiple columns depending on the size of the display, you can change the column count by pinch gestures. They can be renamed, shared, deleted, copied, moved. Images can also be cropped, rotated, flipped or set as Wallpaper directly from the app. + Simple Gallery Pro is a highly customizable offline gallery. Organize & edit your photos, recover deleted files with the recycle bin, protect & hide files and view a huge variety of different photo & video formats including RAW, SVG and much more. - The Gallery is also offered for third party usage for previewing images / videos, adding attachments at email clients etc. It\'s perfect for everyday usage. + The app contains no ads and unnecessary permissions. As the app doesn’t require internet access either, your privacy is protected. - Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. + ------------------------------------------------- + SIMPLE GALLERY PRO – FEATURES + ------------------------------------------------- - This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com + • Offline gallery with no ads or popups + • Simple gallery photo editor – crop, rotate, resize, draw, filters & more + • No internet access needed, giving you more privacy and security + • No unnecessary permissions required + • Quickly search images, videos & files + • Open & view many different photo and video types (RAW, SVG, panoramic etc) + • A variety of intuitive gestures to easily edit & organize files + • Lots of ways to filter, group & sort files + • Customize the appearance of Simple Gallery Pro + • Available in 32 languages + • Mark files as favorites for quick access + • Protect your photos & videos with a pattern, pin or fingerprint + • Use pin, pattern & fingerprint to protect the app launch or specific functions too + • Recover deleted photos & videos from the recycle bin + • Toggle visibility of files to hide photos & videos + • Create a customizable slideshow of your files + • View detailed information of your files (resolution, EXIF values etc) + • Simple Gallery Pro is open source + … and much much more! + + PHOTO GALLERY EDITOR + Simple Gallery Pro makes it easy to edit your pictures on the fly. Crop, flip, rotate and resize your pictures. If you’re feeling a little more creative you can add filters and draw on your pictures! + + SUPPORT FOR MANY FILE TYPES + Unlike some other gallery viewers & photo organizers, Simple Gallery Pro supports a huge range of different file types including JPEG, PNG, MP4, MKV, RAW, SVG, Panoramic photos, Panoramic videos and many more. + + HIGHLY CUSTOMIZABLE GALLERY MANAGER + From the UI to the function buttons on the bottom toolbar, Simple Gallery Pro is highly customizable and works the way you want it to. No other gallery manager has this kind of flexibility! Thanks to being open source, we’re also available in 32 languages! + + RECOVER DELETED PHOTOS & VIDEOS + Accidentally deleted a precious photo or video? Don’t worry! Simple Gallery Pro features a handy recycle bin where you can recover deleted photos & videos easily. + + PROTECT & HIDE PHOTOS, VIDEOS & FILES + Using pin, pattern or your device’s fingerprint scanner you can protect and hide photos, videos & entire albums. You can protect the app itself or place locks on specific functions of the app. For example, you can’t delete a file without a fingerprint scan, helping to protect your files from accidental deletion. + + Check out the full suite of Simple Tools here: + https://www.simplemobiletools.com + + Standalone website of Simple Gallery Pro: + https://www.simplemobiletools.com/gallery + + Facebook: + https://www.facebook.com/simplemobiletools + + Reddit: + https://www.reddit.com/r/SimpleMobileTools