diff --git a/CHANGELOG.md b/CHANGELOG.md index 58136d1a8..feff7768b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ Changelog ========== +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)* ---------------------------- diff --git a/app/build.gradle b/app/build.gradle index d3c104ce3..b31ee9a6c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ android { applicationId "com.simplemobiletools.gallery.pro" minSdkVersion 21 targetSdkVersion 28 - versionCode 214 - versionName "6.1.1" + versionCode 216 + versionName "6.1.3" multiDexEnabled true setProperty("archivesBaseName", "gallery") } @@ -57,12 +57,12 @@ android { } dependencies { - implementation 'com.simplemobiletools:commons:5.5.18' + implementation 'com.simplemobiletools:commons:5.6.3' implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0' - implementation 'androidx.multidex:multidex:2.0.0' + implementation 'androidx.multidex:multidex:2.0.1' implementation 'it.sephiroth.android.exif:library:1.0.1' implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.16' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3' implementation 'com.google.android.exoplayer:exoplayer-core:2.9.2' implementation 'com.google.vr:sdk-panowidget:1.180.0' implementation 'com.google.vr:sdk-videowidget:1.180.0' @@ -78,7 +78,7 @@ dependencies { //implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' //implementation 'com.github.tibbi:subsampling-scale-image-view:v3.10.1-fork' - implementation 'com.github.tibbi:subsampling-scale-image-view:fcb724fb0a' + implementation 'com.github.tibbi:subsampling-scale-image-view:4.0.2' // implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation 'com.github.tibbi:PhotoView:2.3.0-fork' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b00e56d71..8b41fb872 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -122,6 +122,11 @@ android:name=".activities.PhotoVideoActivity" android:configChanges="orientation|keyboardHidden|screenSize"/> + + ) } // cached folders have been loaded, recheck folders one by one starting with the first displayed 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 index 536e05245..fdca85100 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MediaActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MediaActivity.kt @@ -22,23 +22,18 @@ import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition import com.simplemobiletools.commons.dialogs.ConfirmationDialog -import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.OTG_PATH import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE import com.simplemobiletools.commons.helpers.REQUEST_EDIT_IMAGE import com.simplemobiletools.commons.models.FileDirItem -import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.views.MyGridLayoutManager import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.gallery.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.ExcludeFolderDialog -import com.simplemobiletools.gallery.pro.dialogs.FilterMediaDialog +import com.simplemobiletools.gallery.pro.dialogs.* import com.simplemobiletools.gallery.pro.extensions.* import com.simplemobiletools.gallery.pro.helpers.* import com.simplemobiletools.gallery.pro.interfaces.DirectoryDao @@ -224,10 +219,10 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener { findItem(R.id.temporarily_show_hidden).isVisible = !config.shouldShowHidden findItem(R.id.stop_showing_hidden).isVisible = config.temporarilyShowHidden - findItem(R.id.increase_column_count).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID && config.mediaColumnCnt < MAX_COLUMN_COUNT - findItem(R.id.reduce_column_count).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID && config.mediaColumnCnt > 1 - - findItem(R.id.toggle_filename).isVisible = config.viewTypeFiles == VIEW_TYPE_GRID + val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath) + findItem(R.id.increase_column_count).isVisible = viewType == VIEW_TYPE_GRID && config.mediaColumnCnt < MAX_COLUMN_COUNT + findItem(R.id.reduce_column_count).isVisible = viewType == VIEW_TYPE_GRID && config.mediaColumnCnt > 1 + findItem(R.id.toggle_filename).isVisible = viewType == VIEW_TYPE_GRID } setupSearch(menu) @@ -367,7 +362,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener { initZoomListener() val fastscroller = if (config.scrollHorizontally) media_horizontal_fastscroller else media_vertical_fastscroller MediaAdapter(this, mMedia.clone() as ArrayList, this, mIsGetImageIntent || mIsGetVideoIntent || mIsGetAnyIntent, - mAllowPickingMultiple, media_grid, fastscroller) { + mAllowPickingMultiple, mPath, media_grid, fastscroller) { if (it is Medium) { itemClicked(it.path) } @@ -385,7 +380,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener { } private fun setupScrollDirection() { - val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID + val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath) + val allowHorizontalScroll = config.scrollHorizontally && viewType == VIEW_TYPE_GRID media_vertical_fastscroller.isHorizontal = false media_vertical_fastscroller.beGoneIf(allowHorizontalScroll) @@ -493,12 +489,7 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener { } private fun changeViewType() { - val items = arrayListOf( - RadioItem(VIEW_TYPE_GRID, getString(R.string.grid)), - RadioItem(VIEW_TYPE_LIST, getString(R.string.list))) - - RadioGroupDialog(this, items, config.viewTypeFiles) { - config.viewTypeFiles = it as Int + ChangeViewTypeDialog(this, false, mPath) { invalidateOptionsMenu() setupLayoutManager() media_grid.adapter = null @@ -646,7 +637,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener { } private fun setupLayoutManager() { - if (config.viewTypeFiles == VIEW_TYPE_GRID) { + val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath) + if (viewType == VIEW_TYPE_GRID) { setupGridLayoutManager() } else { setupListLayoutManager() @@ -722,7 +714,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener { } private fun initZoomListener() { - if (config.viewTypeFiles == VIEW_TYPE_GRID) { + val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath) + if (viewType == VIEW_TYPE_GRID) { val layoutManager = media_grid.layoutManager as MyGridLayoutManager mZoomListener = object : MyRecyclerView.MyZoomListener { override fun zoomIn() { @@ -840,7 +833,8 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener { media_empty_text.beVisibleIf(media.isEmpty() && !isFromCache) media_grid.beVisibleIf(media_empty_text_label.isGone()) - val allowHorizontalScroll = config.scrollHorizontally && config.viewTypeFiles == VIEW_TYPE_GRID + val viewType = config.getFolderViewType(if (mShowAll) SHOW_ALL else mPath) + val allowHorizontalScroll = config.scrollHorizontally && viewType == VIEW_TYPE_GRID media_vertical_fastscroller.beVisibleIf(media_grid.isVisible() && !allowHorizontalScroll) media_horizontal_fastscroller.beVisibleIf(media_grid.isVisible() && allowHorizontalScroll) setupAdapter() 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 index 7518add3c..8183613d4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PanoramaVideoActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PanoramaVideoActivity.kt @@ -8,23 +8,17 @@ import android.os.Handler import android.view.View import android.view.Window import android.view.WindowManager -import android.view.animation.AnimationUtils 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.getFormattedDuration -import com.simplemobiletools.commons.extensions.onGlobalLayout -import com.simplemobiletools.commons.extensions.showErrorToast -import com.simplemobiletools.commons.extensions.toast +import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE import com.simplemobiletools.commons.helpers.isPiePlus import com.simplemobiletools.gallery.pro.R import com.simplemobiletools.gallery.pro.extensions.* -import com.simplemobiletools.gallery.pro.helpers.HIDE_PLAY_PAUSE_DELAY import com.simplemobiletools.gallery.pro.helpers.MIN_SKIP_LENGTH import com.simplemobiletools.gallery.pro.helpers.PATH -import com.simplemobiletools.gallery.pro.helpers.PLAY_PAUSE_VISIBLE_ALPHA import kotlinx.android.synthetic.main.activity_panorama_video.* import kotlinx.android.synthetic.main.bottom_video_time_holder.* import java.io.File @@ -41,7 +35,6 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList private var mDuration = 0 private var mCurrTime = 0 - private var mHidePlayPauseHandler = Handler() private var mTimerHandler = Handler() public override fun onCreate(savedInstanceState: Bundle?) { @@ -56,18 +49,6 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) } - setupButtons() - - cardboard.setOnClickListener { - vr_video_view.displayMode = CARDBOARD_DISPLAY_MODE - } - - explore.setOnClickListener { - mIsExploreEnabled = !mIsExploreEnabled - vr_video_view.setPureTouchTracking(mIsExploreEnabled) - explore.setImageResource(if (mIsExploreEnabled) R.drawable.ic_explore else R.drawable.ic_explore_off) - } - handlePermission(PERMISSION_WRITE_STORAGE) { if (it) { checkIntent() @@ -102,7 +83,6 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList } if (!isChangingConfigurations) { - mHidePlayPauseHandler.removeCallbacksAndMessages(null) mTimerHandler.removeCallbacksAndMessages(null) } } @@ -115,6 +95,7 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList return } + setupButtons() intent.removeExtra(PATH) video_curr_time.setOnClickListener { skip(false) } @@ -152,10 +133,14 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList setupTimer() } - if (mPlayOnReady) { + if (mPlayOnReady || config.autoplayVideos) { mPlayOnReady = false - playVideo() + mIsPlaying = true + resumeVideo() + } else { + video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline) } + video_toggle_play_pause.beVisible() } override fun onCompletion() { @@ -164,7 +149,7 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList }) } - video_play_outline.setOnClickListener { + video_toggle_play_pause.setOnClickListener { togglePlayPause() } } catch (e: Exception) { @@ -205,18 +190,15 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList private fun togglePlayPause() { mIsPlaying = !mIsPlaying - video_play_outline.alpha = PLAY_PAUSE_VISIBLE_ALPHA - mHidePlayPauseHandler.removeCallbacksAndMessages(null) if (mIsPlaying) { - playVideo() + resumeVideo() } else { pauseVideo() } - schedulePlayPauseFadeOut() } - private fun playVideo() { - video_play_outline.setImageResource(R.drawable.ic_pause) + private fun resumeVideo() { + video_toggle_play_pause.setImageResource(R.drawable.ic_pause_outline) if (mCurrTime == mDuration) { setVideoProgress(0) mPlayOnReady = true @@ -229,7 +211,7 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList private fun pauseVideo() { vr_video_view.pauseVideo() - video_play_outline.setImageResource(R.drawable.ic_play) + video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline) window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } @@ -246,55 +228,57 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList video_seekbar.progress = video_seekbar.max video_curr_time.text = mDuration.getFormattedDuration() pauseVideo() - video_play_outline.alpha = PLAY_PAUSE_VISIBLE_ALPHA - } - - private fun schedulePlayPauseFadeOut() { - mHidePlayPauseHandler.removeCallbacksAndMessages(null) - mHidePlayPauseHandler.postDelayed({ - video_play_outline.animate().alpha(0f).start() - }, HIDE_PLAY_PAUSE_DELAY) } private fun setupButtons() { - val navBarHeight = navigationBarHeight - video_time_holder.apply { - (layoutParams as RelativeLayout.LayoutParams).bottomMargin = navBarHeight - setPadding(paddingLeft, paddingTop, navigationBarWidth, paddingBottom) + 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.onGlobalLayout { - (explore.layoutParams as RelativeLayout.LayoutParams).bottomMargin = navBarHeight + video_time_holder.height + 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 = navBarHeight + video_time_holder.height + bottomMargin = newBottomMargin rightMargin = navigationBarWidth } - vr_view_gradient_background.layoutParams.height = navBarHeight + video_time_holder.height + explore.height explore.requestLayout() } + video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline) + + cardboard.setOnClickListener { + vr_video_view.displayMode = CARDBOARD_DISPLAY_MODE + } + + explore.setOnClickListener { + mIsExploreEnabled = !mIsExploreEnabled + vr_video_view.setPureTouchTracking(mIsExploreEnabled) + explore.setImageResource(if (mIsExploreEnabled) R.drawable.ic_explore else R.drawable.ic_explore_off) + } } private fun toggleButtonVisibility() { val newAlpha = if (mIsFullscreen) 0f else 1f - arrayOf(cardboard, explore, vr_view_gradient_background).forEach { + arrayOf(cardboard, explore).forEach { it.animate().alpha(newAlpha) + } + + arrayOf(cardboard, explore, video_toggle_play_pause, video_curr_time, video_duration).forEach { it.isClickable = !mIsFullscreen } - var anim = android.R.anim.fade_in - if (mIsFullscreen) { - anim = android.R.anim.fade_out - video_seekbar.setOnSeekBarChangeListener(null) - } else { - video_seekbar.setOnSeekBarChangeListener(this) - } - - AnimationUtils.loadAnimation(this, anim).apply { - duration = 150 - fillAfter = true - video_time_holder.startAnimation(this) - } + video_seekbar.setOnSeekBarChangeListener(if (mIsFullscreen) null else this) + video_time_holder.animate().alpha(newAlpha).start() } private fun handleClick() { @@ -336,8 +320,7 @@ open class PanoramaVideoActivity : SimpleActivity(), SeekBar.OnSeekBarChangeList override fun onStopTrackingTouch(seekBar: SeekBar?) { mIsPlaying = true - playVideo() + resumeVideo() mIsDragged = false - schedulePlayPauseFadeOut() } } 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 index 6ab3b2478..b5b1c0fff 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PhotoVideoActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/PhotoVideoActivity.kt @@ -16,6 +16,7 @@ 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.commons.helpers.isPiePlus +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 @@ -26,6 +27,7 @@ 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 { @@ -63,6 +65,12 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList private fun checkIntent(savedInstanceState: Bundle? = null) { mUri = intent.data ?: return var filename = getFilenameFromUri(mUri!!) + mIsFromGallery = intent.getBooleanExtra(IS_FROM_GALLERY, false) + if (mIsFromGallery && filename.isVideoFast()) { + launchVideoPlayer() + return + } + if (intent.extras?.containsKey(REAL_FILE_PATH) == true) { val realPath = intent.extras.getString(REAL_FILE_PATH) if (realPath != null) { @@ -76,7 +84,6 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList } } - mIsFromGallery = intent.getBooleanExtra(IS_FROM_GALLERY, false) if (mUri!!.scheme == "file") { if (filename.contains('.')) { scanPathRecursively(mUri!!.path) @@ -134,6 +141,41 @@ open class PhotoVideoActivity : SimpleActivity(), ViewPagerFragment.FragmentList initBottomActions() } + private fun launchVideoPlayer() { + val newUri = getFinalUriFromPath(mUri.toString(), BuildConfig.APPLICATION_ID) + if (newUri == null) { + toast(R.string.unknown_error_occurred) + return + } + + var isPanorama = false + val realPath = intent?.extras?.getString(REAL_FILE_PATH) ?: "" + try { + if (realPath.isNotEmpty()) { + val fis = FileInputStream(File(realPath)) + parseFileChannel(realPath, fis.channel, 0, 0, 0) { + isPanorama = true + } + } + } catch (ignored: Exception) { + } catch (ignored: OutOfMemoryError) { + } + + if (isPanorama) { + Intent(applicationContext, PanoramaVideoActivity::class.java).apply { + putExtra(PATH, realPath) + startActivity(this) + } + } else { + val mimeType = getUriMimeType(mUri.toString(), newUri) + Intent(applicationContext, VideoPlayerActivity::class.java).apply { + setDataAndType(newUri, mimeType) + startActivity(this) + } + } + finish() + } + override fun onConfigurationChanged(newConfig: Configuration?) { super.onConfigurationChanged(newConfig) initBottomActionsLayout() 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..d4ea4fb25 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/VideoPlayerActivity.kt @@ -0,0 +1,522 @@ +package com.simplemobiletools.gallery.pro.activities + +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.graphics.Color +import android.graphics.Point +import android.graphics.SurfaceTexture +import android.graphics.drawable.ColorDrawable +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.util.DisplayMetrics +import android.view.* +import android.widget.SeekBar +import com.google.android.exoplayer2.* +import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory +import com.google.android.exoplayer2.source.ExtractorMediaSource +import com.google.android.exoplayer2.source.TrackGroupArray +import com.google.android.exoplayer2.trackselection.TrackSelectionArray +import com.google.android.exoplayer2.upstream.ContentDataSource +import com.google.android.exoplayer2.upstream.DataSource +import com.google.android.exoplayer2.upstream.DataSpec +import com.google.android.exoplayer2.video.VideoListener +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE +import com.simplemobiletools.commons.helpers.isPiePlus +import com.simplemobiletools.gallery.pro.R +import com.simplemobiletools.gallery.pro.extensions.* +import com.simplemobiletools.gallery.pro.helpers.MIN_SKIP_LENGTH +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 var mIsFullscreen = false + private var mIsPlaying = false + private var mWasVideoStarted = false + private var mIsDragged = false + private var mCurrTime = 0 + private var mDuration = 0 + private var mVideoSize = Point(0, 0) + + private var mUri: Uri? = null + private var mExoPlayer: SimpleExoPlayer? = null + private var mTimerHandler = Handler() + + private var mTouchDownX = 0f + private var mTouchDownY = 0f + private var mCloseDownThreshold = 100f + private var mIgnoreCloseDown = false + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_video_player) + + handlePermission(PERMISSION_WRITE_STORAGE) { + if (it) { + initPlayer() + } else { + toast(R.string.no_storage_permissions) + finish() + } + } + } + + override fun onResume() { + super.onResume() + top_shadow.layoutParams.height = statusBarHeight + actionBarHeight + supportActionBar?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.statusBarColor = Color.TRANSPARENT + window.navigationBarColor = Color.TRANSPARENT + if (config.blackBackground) { + video_player_holder.background = ColorDrawable(Color.BLACK) + } + + if (config.maxBrightness) { + val attributes = window.attributes + attributes.screenBrightness = 1f + window.attributes = attributes + } + + updateTextColors(video_player_holder) + } + + override fun onPause() { + super.onPause() + pauseVideo() + + if (config.rememberLastVideoPosition && mWasVideoStarted) { + saveVideoProgress() + } + } + + override fun onDestroy() { + super.onDestroy() + if (!isChangingConfigurations) { + cleanup() + } + } + + 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() + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + setVideoSize() + initTimeHolder() + } + + private fun initPlayer() { + mUri = intent.data ?: return + supportActionBar?.title = getFilenameFromUri(mUri!!) + initTimeHolder() + + if (isPiePlus()) { + window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + } + + showSystemUI(true) + window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> + val isFullscreen = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0 + fullscreenToggled(isFullscreen) + } + + // adding an empty click listener just to avoid ripple animation at toggling fullscreen + video_seekbar.setOnClickListener { } + video_curr_time.setOnClickListener { skip(false) } + video_duration.setOnClickListener { skip(true) } + video_toggle_play_pause.setOnClickListener { togglePlayPause() } + video_player_holder.setOnClickListener { + fullscreenToggled(!mIsFullscreen) + } + + if (config.allowDownGesture) { + video_player_holder.setOnTouchListener { v, event -> + handleEvent(event) + false + } + + video_surface.setOnTouchListener { v, event -> + handleEvent(event) + false + } + } + + initExoPlayer() + video_surface.surfaceTextureListener = this + video_surface.setOnClickListener { + fullscreenToggled(!mIsFullscreen) + } + + if (config.allowVideoGestures) { + video_brightness_controller.initialize(this, slide_info, true, video_player_holder) { x, y -> + video_player_holder.performClick() + } + + video_volume_controller.initialize(this, slide_info, false, video_player_holder) { x, y -> + video_player_holder.performClick() + } + } else { + video_brightness_controller.beGone() + video_volume_controller.beGone() + } + + if (config.hideSystemUI) { + Handler().postDelayed({ + fullscreenToggled(true) + }, 500) + } + } + + private fun initExoPlayer() { + val dataSpec = DataSpec(mUri) + val fileDataSource = ContentDataSource(applicationContext) + try { + fileDataSource.open(dataSpec) + } catch (e: Exception) { + showErrorToast(e) + } + + val factory = DataSource.Factory { fileDataSource } + val audioSource = ExtractorMediaSource(fileDataSource.uri, factory, DefaultExtractorsFactory(), null, null) + mExoPlayer = ExoPlayerFactory.newSimpleInstance(applicationContext).apply { + seekParameters = SeekParameters.CLOSEST_SYNC + audioStreamType = C.STREAM_TYPE_MUSIC + prepare(audioSource) + } + initExoPlayerListeners() + } + + private fun initExoPlayerListeners() { + mExoPlayer!!.addListener(object : Player.EventListener { + override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {} + + override fun onSeekProcessed() {} + + override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {} + + override fun onPlayerError(error: ExoPlaybackException?) {} + + override fun onLoadingChanged(isLoading: Boolean) {} + + override fun onPositionDiscontinuity(reason: Int) {} + + override fun onRepeatModeChanged(repeatMode: Int) {} + + override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {} + + override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {} + + override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { + when (playbackState) { + Player.STATE_READY -> videoPrepared() + Player.STATE_ENDED -> videoCompleted() + } + } + }) + + mExoPlayer!!.addVideoListener(object : VideoListener { + override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) { + mVideoSize.x = width + mVideoSize.y = height + setVideoSize() + } + + override fun onRenderedFirstFrame() {} + }) + } + + private fun videoPrepared() { + if (!mWasVideoStarted) { + video_toggle_play_pause.beVisible() + mDuration = (mExoPlayer!!.duration / 1000).toInt() + video_seekbar.max = mDuration + video_duration.text = mDuration.getFormattedDuration() + setPosition(mCurrTime) + + if (config.rememberLastVideoPosition) { + setLastVideoSavedPosition() + } + + if (config.autoplayVideos) { + resumeVideo() + } else { + video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline) + } + } + } + + private fun resumeVideo() { + video_toggle_play_pause.setImageResource(R.drawable.ic_pause_outline) + if (mExoPlayer == null) { + return + } + + val wasEnded = didVideoEnd() + if (wasEnded) { + setPosition(0) + } + + mWasVideoStarted = true + mIsPlaying = true + mExoPlayer?.playWhenReady = true + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + + private fun pauseVideo() { + video_toggle_play_pause.setImageResource(R.drawable.ic_play_outline) + if (mExoPlayer == null) { + return + } + + mIsPlaying = false + if (!didVideoEnd()) { + mExoPlayer?.playWhenReady = false + } + + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + + private fun togglePlayPause() { + mIsPlaying = !mIsPlaying + if (mIsPlaying) { + resumeVideo() + } else { + pauseVideo() + } + } + + private fun setPosition(seconds: Int) { + mExoPlayer?.seekTo(seconds * 1000L) + video_seekbar.progress = seconds + video_curr_time.text = seconds.getFormattedDuration() + } + + private fun setLastVideoSavedPosition() { + if (config.lastVideoPath == mUri.toString() && config.lastVideoPosition > 0) { + setPosition(config.lastVideoPosition) + } + } + + private fun videoCompleted() { + if (mExoPlayer == null) { + return + } + + clearLastVideoSavedProgress() + mCurrTime = (mExoPlayer!!.duration / 1000).toInt() + if (config.loopVideos) { + resumeVideo() + } else { + video_seekbar.progress = video_seekbar.max + video_curr_time.text = mDuration.getFormattedDuration() + pauseVideo() + } + } + + private fun didVideoEnd(): Boolean { + val currentPos = mExoPlayer?.currentPosition ?: 0 + val duration = mExoPlayer?.duration ?: 0 + return currentPos != 0L && currentPos >= duration + } + + private fun saveVideoProgress() { + if (!didVideoEnd()) { + config.apply { + lastVideoPosition = mExoPlayer!!.currentPosition.toInt() / 1000 + lastVideoPath = mUri.toString() + } + } + } + + private fun clearLastVideoSavedProgress() { + config.apply { + lastVideoPosition = 0 + lastVideoPath = "" + } + } + + private fun setVideoSize() { + val videoProportion = mVideoSize.x.toFloat() / mVideoSize.y.toFloat() + val display = windowManager.defaultDisplay + val screenWidth: Int + val screenHeight: Int + + val realMetrics = DisplayMetrics() + display.getRealMetrics(realMetrics) + screenWidth = realMetrics.widthPixels + screenHeight = realMetrics.heightPixels + + val screenProportion = screenWidth.toFloat() / screenHeight.toFloat() + + video_surface.layoutParams.apply { + if (videoProportion > screenProportion) { + width = screenWidth + height = (screenWidth.toFloat() / videoProportion).toInt() + } else { + width = (videoProportion * screenHeight.toFloat()).toInt() + height = screenHeight + } + video_surface.layoutParams = this + } + } + + private fun changeOrientation() { + requestedOrientation = if (resources.configuration.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } else { + ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + } + + private fun fullscreenToggled(isFullScreen: Boolean) { + mIsFullscreen = isFullScreen + if (isFullScreen) { + hideSystemUI(true) + } else { + showSystemUI(true) + } + + val newAlpha = if (isFullScreen) 0f else 1f + top_shadow.animate().alpha(newAlpha).start() + video_time_holder.animate().alpha(newAlpha).start() + video_seekbar.setOnSeekBarChangeListener(if (mIsFullscreen) null else this) + arrayOf(video_toggle_play_pause, video_curr_time, video_duration).forEach { + it.isClickable = !mIsFullscreen + } + } + + private fun initTimeHolder() { + var right = 0 + var bottom = 0 + + if (hasNavBar()) { + if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { + bottom += navigationBarHeight + } else { + right += navigationBarWidth + bottom += navigationBarHeight + } + } + + video_time_holder.setPadding(0, 0, right, bottom) + video_seekbar.setOnSeekBarChangeListener(this) + video_seekbar!!.max = mDuration + video_duration.text = mDuration.getFormattedDuration() + video_curr_time.text = mCurrTime.getFormattedDuration() + setupTimer() + } + + private fun setupTimer() { + runOnUiThread(object : Runnable { + override fun run() { + if (mExoPlayer != null && !mIsDragged && mIsPlaying) { + mCurrTime = (mExoPlayer!!.currentPosition / 1000).toInt() + video_seekbar.progress = mCurrTime + video_curr_time.text = mCurrTime.getFormattedDuration() + } + + mTimerHandler.postDelayed(this, 1000) + } + }) + } + + private fun skip(forward: Boolean) { + if (mExoPlayer == null) { + return + } + + val curr = mExoPlayer!!.currentPosition + val twoPercents = Math.max((mExoPlayer!!.duration / 50).toInt(), MIN_SKIP_LENGTH) + val newProgress = if (forward) curr + twoPercents else curr - twoPercents + val roundProgress = Math.round(newProgress / 1000f) + val limitedProgress = Math.max(Math.min(mExoPlayer!!.duration.toInt(), roundProgress), 0) + setPosition(limitedProgress) + if (!mIsPlaying) { + togglePlayPause() + } + } + + private fun handleEvent(event: MotionEvent) { + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + mTouchDownX = event.x + mTouchDownY = event.y + } + MotionEvent.ACTION_POINTER_DOWN -> mIgnoreCloseDown = true + MotionEvent.ACTION_UP -> { + val diffX = mTouchDownX - event.x + val diffY = mTouchDownY - event.y + + if (!mIgnoreCloseDown && Math.abs(diffY) > Math.abs(diffX) && diffY < -mCloseDownThreshold) { + supportFinishAfterTransition() + } + mIgnoreCloseDown = false + } + } + } + + private fun cleanup() { + pauseVideo() + video_curr_time.text = 0.getFormattedDuration() + releaseExoPlayer() + video_seekbar.progress = 0 + mTimerHandler.removeCallbacksAndMessages(null) + } + + private fun releaseExoPlayer() { + mExoPlayer?.stop() + Thread { + mExoPlayer?.release() + mExoPlayer = null + }.start() + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + if (mExoPlayer != null && fromUser) { + setPosition(progress) + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) { + mIsDragged = true + } + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + if (mExoPlayer == null) + return + + if (mIsPlaying) { + mExoPlayer!!.playWhenReady = true + } else { + togglePlayPause() + } + + mIsDragged = false + } + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) { + } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?) = false + + override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) { + Thread { + mExoPlayer?.setVideoSurface(Surface(video_surface!!.surfaceTexture)) + }.start() + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) { + } +} 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 index 9c96ec30e..7225ab6ab 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ViewPagerActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ViewPagerActivity.kt @@ -41,7 +41,6 @@ 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 @@ -129,10 +128,6 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View setupRotation() invalidateOptionsMenu() - if (config.blackBackground) { - updateStatusbarColor(Color.BLACK) - } - supportActionBar?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) window.statusBarColor = Color.TRANSPARENT } @@ -500,8 +495,6 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View swipeToNextMedium() } }, mSlideshowInterval * 1000L) - } else { - (getCurrentFragment() as? VideoFragment)!!.playVideo() } } } @@ -511,18 +504,9 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View } private fun getMediaForSlideshow(): Boolean { - mSlideshowMedia = mMediaFiles.toMutableList() - if (!config.slideshowIncludePhotos) { - mSlideshowMedia = mSlideshowMedia.filter { !it.isImage() } as MutableList - } - - if (!config.slideshowIncludeVideos) { - mSlideshowMedia = mSlideshowMedia.filter { it.isImage() || it.isGIF() } as MutableList - } - - if (!config.slideshowIncludeGIFs) { - mSlideshowMedia = mSlideshowMedia.filter { !it.isGIF() } as MutableList - } + mSlideshowMedia = mMediaFiles.filter { + it.isImage() || (config.slideshowIncludeGIFs && it.isGIF()) + }.toMutableList() if (config.slideshowRandomOrder) { mSlideshowMedia.shuffle() 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 index e20ea1351..0206d451e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/MediaAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/adapters/MediaAdapter.kt @@ -24,6 +24,7 @@ import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.gallery.pro.R import com.simplemobiletools.gallery.pro.dialogs.DeleteWithRememberDialog import com.simplemobiletools.gallery.pro.extensions.* +import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_LIST import com.simplemobiletools.gallery.pro.interfaces.MediaOperationsListener import com.simplemobiletools.gallery.pro.models.Medium @@ -35,7 +36,7 @@ import java.text.SimpleDateFormat import java.util.* class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList, val listener: MediaOperationsListener?, val isAGetIntent: Boolean, - val allowMultiplePicks: Boolean, recyclerView: MyRecyclerView, fastScroller: FastScroller? = null, itemClick: (Any) -> Unit) : + val allowMultiplePicks: Boolean, val path: String, recyclerView: MyRecyclerView, fastScroller: FastScroller? = null, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { private val INSTANT_LOAD_DURATION = 2000L @@ -45,7 +46,8 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList() private var loadImageInstantly = false private var delayHandler = Handler(Looper.getMainLooper()) @@ -347,7 +349,12 @@ class MediaAdapter(activity: BaseSimpleActivity, var media: MutableList() foldersToScan.forEach { - val newMedia = mediaFetcher.getFilesFrom(it, isPickImage, isPickVideo, getProperDateTaken, favoritePaths, getVideoDurations) + val newMedia = mediaFetcher.getFilesFrom(it, isPickImage, isPickVideo, getProperDateTaken, favoritePaths, getVideoDurations, false) media.addAll(newMedia) } 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 index b534b5c3e..78537069a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeViewTypeDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeViewTypeDialog.kt @@ -3,22 +3,47 @@ package com.simplemobiletools.gallery.pro.dialogs import android.view.View import androidx.appcompat.app.AlertDialog import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.gallery.pro.R import com.simplemobiletools.gallery.pro.extensions.config +import com.simplemobiletools.gallery.pro.helpers.SHOW_ALL import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_GRID import com.simplemobiletools.gallery.pro.helpers.VIEW_TYPE_LIST import kotlinx.android.synthetic.main.dialog_change_view_type.view.* -class ChangeViewTypeDialog(val activity: BaseSimpleActivity, val callback: () -> Unit) { +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 (config.viewTypeFolders == VIEW_TYPE_GRID) change_view_type_dialog_radio_grid.id else change_view_type_dialog_radio_list.id + 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.isChecked = config.groupDirectSubfolders + 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) @@ -31,8 +56,18 @@ class ChangeViewTypeDialog(val activity: BaseSimpleActivity, val callback: () -> 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 - config.viewTypeFolders = viewType - config.groupDirectSubfolders = view.change_view_type_dialog_group_direct_subfolders.isChecked + 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/PickMediumDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/PickMediumDialog.kt index b1f83527b..f75a8f301 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/PickMediumDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/PickMediumDialog.kt @@ -22,7 +22,8 @@ class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val c var dialog: AlertDialog var shownMedia = ArrayList() val view = activity.layoutInflater.inflate(R.layout.dialog_medium_picker, null) - var isGridViewType = activity.config.viewTypeFiles == VIEW_TYPE_GRID + val viewType = activity.config.getFolderViewType(if (activity.config.showAll) SHOW_ALL else path) + var isGridViewType = viewType == VIEW_TYPE_GRID init { (view.media_grid.layoutManager as MyGridLayoutManager).apply { @@ -64,7 +65,7 @@ class PickMediumDialog(val activity: BaseSimpleActivity, val path: String, val c return shownMedia = media - val adapter = MediaAdapter(activity, shownMedia.clone() as ArrayList, null, true, false, view.media_grid, null) { + 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() 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 index 486b8886d..5e3c5f198 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/SlideshowDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/SlideshowDialog.kt @@ -5,7 +5,6 @@ import androidx.appcompat.app.AlertDialog import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.hideKeyboard import com.simplemobiletools.commons.extensions.setupDialogStuff -import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.gallery.pro.R import com.simplemobiletools.gallery.pro.extensions.config import com.simplemobiletools.gallery.pro.helpers.SLIDESHOW_DEFAULT_INTERVAL @@ -29,16 +28,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit activity.hideKeyboard(v) } - include_photos_holder.setOnClickListener { - interval_value.clearFocus() - include_photos.toggle() - } - - include_videos_holder.setOnClickListener { - interval_value.clearFocus() - include_videos.toggle() - } - include_gifs_holder.setOnClickListener { interval_value.clearFocus() include_gifs.toggle() @@ -73,11 +62,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit activity.setupDialogStuff(view, this) { hideKeyboard() getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - if (!view.include_photos.isChecked && !view.include_videos.isChecked && !view.include_gifs.isChecked) { - activity.toast(R.string.no_media_for_slideshow) - return@setOnClickListener - } - storeValues() callback() dismiss() @@ -90,8 +74,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit val config = activity.config view.apply { interval_value.setText(config.slideshowInterval.toString()) - include_photos.isChecked = config.slideshowIncludePhotos - include_videos.isChecked = config.slideshowIncludeVideos include_gifs.isChecked = config.slideshowIncludeGIFs random_order.isChecked = config.slideshowRandomOrder use_fade.isChecked = config.slideshowUseFade @@ -107,8 +89,6 @@ class SlideshowDialog(val activity: BaseSimpleActivity, val callback: () -> Unit activity.config.apply { slideshowInterval = interval.toInt() - slideshowIncludePhotos = view.include_photos.isChecked - slideshowIncludeVideos = view.include_videos.isChecked slideshowIncludeGIFs = view.include_gifs.isChecked slideshowRandomOrder = view.random_order.isChecked slideshowUseFade = view.use_fade.isChecked 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 index 0c8458d92..a8103b206 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Activity.kt @@ -3,6 +3,7 @@ package com.simplemobiletools.gallery.pro.extensions import android.app.Activity import android.content.Intent import android.provider.MediaStore +import android.util.DisplayMetrics import android.view.View import androidx.appcompat.app.AppCompatActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity @@ -79,7 +80,8 @@ fun SimpleActivity.launchAbout() { FAQItem(R.string.faq_12_title, R.string.faq_12_text), FAQItem(R.string.faq_13_title, R.string.faq_13_text), FAQItem(R.string.faq_14_title, R.string.faq_14_text), - FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons)) + FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons), + FAQItem(R.string.faq_6_title_commons, R.string.faq_6_text_commons)) startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true) } @@ -291,3 +293,15 @@ fun BaseSimpleActivity.updateFavoritePaths(fileDirItems: ArrayList, } }.start() } + +fun Activity.hasNavBar(): Boolean { + val display = windowManager.defaultDisplay + + val realDisplayMetrics = DisplayMetrics() + display.getRealMetrics(realDisplayMetrics) + + val displayMetrics = DisplayMetrics() + display.getMetrics(displayMetrics) + + return (realDisplayMetrics.widthPixels - displayMetrics.widthPixels > 0) || (realDisplayMetrics.heightPixels - displayMetrics.heightPixels > 0) +} 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 index c8a7f5b58..3f6b336de 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt @@ -35,6 +35,9 @@ 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 @@ -170,10 +173,6 @@ fun Context.getSortedDirectories(source: ArrayList): ArrayList o1.taken.compareTo(o2.taken) } - if (result == 0) { - result = AlphanumericComparator().compare(o1.path.toLowerCase(), o2.path.toLowerCase()) - } - if (sorting and SORT_DESCENDING != 0) { result *= -1 } @@ -627,3 +626,60 @@ fun Context.updateWidgets() { } } } + +// based on https://github.com/sannies/mp4parser/blob/master/examples/src/main/java/com/google/code/mp4parser/example/PrintStructure.java +fun Context.parseFileChannel(path: String, fc: FileChannel, level: Int, start: Long, end: Long, callback: () -> Unit) { + val FILE_CHANNEL_CONTAINERS = arrayListOf("moov", "trak", "mdia", "minf", "udta", "stbl") + try { + var iteration = 0 + var currEnd = end + fc.position(start) + if (currEnd <= 0) { + currEnd = start + fc.size() + } + + while (currEnd - fc.position() > 8) { + // just a check to avoid deadloop at some videos + if (iteration++ > 50) { + return + } + + val begin = fc.position() + val byteBuffer = ByteBuffer.allocate(8) + fc.read(byteBuffer) + byteBuffer.rewind() + val size = IsoTypeReader.readUInt32(byteBuffer) + val type = IsoTypeReader.read4cc(byteBuffer) + val newEnd = begin + size + + if (type == "uuid") { + val fis = FileInputStream(File(path)) + fis.skip(begin) + + val sb = StringBuilder() + val buffer = ByteArray(1024) + while (true) { + val n = fis.read(buffer) + if (n != -1) { + sb.append(String(buffer, 0, n)) + } else { + break + } + } + + val xmlString = sb.toString().toLowerCase() + if (xmlString.contains("gspherical:projectiontype>equirectangular") || xmlString.contains("gspherical:projectiontype=\"equirectangular\"")) { + callback.invoke() + } + return + } + + if (FILE_CHANNEL_CONTAINERS.contains(type)) { + parseFileChannel(path, fc, level + 1, begin + 8, newEnd, callback) + } + + fc.position(newEnd) + } + } catch (ignored: Exception) { + } +} 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 index 372bc7b0c..847d5f95a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/PhotoFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/PhotoFragment.kt @@ -18,12 +18,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.bumptech.glide.Glide -import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.Target import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.decoder.DecoderFactory @@ -55,7 +55,7 @@ import java.util.* class PhotoFragment : ViewPagerFragment() { private val DEFAULT_DOUBLE_TAP_ZOOM = 2f - private val ZOOMABLE_VIEW_LOAD_DELAY = 300L + private val ZOOMABLE_VIEW_LOAD_DELAY = 150L // devices with good displays, but the rest of the hardware not good enough for them private val WEIRD_DEVICES = arrayListOf( @@ -119,10 +119,7 @@ class PhotoFragment : ViewPagerFragment() { } } - if (ViewPagerActivity.screenWidth == 0 || ViewPagerActivity.screenHeight == 0) { - measureScreen() - } - + checkScreenDimensions() storeStateVariables() if (!isFragmentVisible && activity is PhotoActivity) { isFragmentVisible = true @@ -227,6 +224,12 @@ class PhotoFragment : ViewPagerFragment() { } } + private fun checkScreenDimensions() { + if (ViewPagerActivity.screenWidth == 0 || ViewPagerActivity.screenHeight == 0) { + measureScreen() + } + } + private fun measureScreen() { val metrics = DisplayMetrics() activity!!.windowManager.defaultDisplay.getRealMetrics(metrics) @@ -297,6 +300,7 @@ class PhotoFragment : ViewPagerFragment() { } private fun loadBitmap(degrees: Int = 0) { + checkScreenDimensions() var pathToLoad = if (medium.path.startsWith("content://")) medium.path else "file://${medium.path}" pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23") @@ -350,7 +354,7 @@ class PhotoFragment : ViewPagerFragment() { .listener(object : RequestListener { override fun onLoadFailed(e: GlideException?, model: Any?, target: com.bumptech.glide.request.target.Target?, isFirstResource: Boolean) = false - override fun onResourceReady(resource: Bitmap?, model: Any?, target: com.bumptech.glide.request.target.Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { + override fun onResourceReady(resource: Bitmap?, model: Any?, target: Target?, dataSource: com.bumptech.glide.load.DataSource?, isFirstResource: Boolean): Boolean { if (isFragmentVisible) { scheduleZoomableView() } 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 index 5d28ff477..586edfaa4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/VideoFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/VideoFragment.kt @@ -2,75 +2,33 @@ 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.view.animation.AnimationUtils -import android.widget.SeekBar -import android.widget.TextView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup 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.DefaultTrackSelector -import com.google.android.exoplayer2.trackselection.TrackSelectionArray -import com.google.android.exoplayer2.upstream.ContentDataSource -import com.google.android.exoplayer2.upstream.DataSource -import com.google.android.exoplayer2.upstream.DataSpec -import com.google.android.exoplayer2.upstream.FileDataSource -import com.google.android.exoplayer2.video.VideoListener import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.gallery.pro.R import com.simplemobiletools.gallery.pro.activities.PanoramaVideoActivity -import com.simplemobiletools.gallery.pro.activities.VideoActivity import com.simplemobiletools.gallery.pro.extensions.* -import com.simplemobiletools.gallery.pro.helpers.* +import com.simplemobiletools.gallery.pro.helpers.MEDIUM +import com.simplemobiletools.gallery.pro.helpers.PATH import com.simplemobiletools.gallery.pro.models.Medium import com.simplemobiletools.gallery.pro.views.MediaSideScroll -import kotlinx.android.synthetic.main.bottom_video_time_holder.view.* import kotlinx.android.synthetic.main.pager_video_item.view.* import java.io.File import java.io.FileInputStream -import java.nio.ByteBuffer -import java.nio.channels.FileChannel -class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, SeekBar.OnSeekBarChangeListener { - private val PROGRESS = "progress" - private val FILE_CHANNEL_CONTAINERS = arrayListOf("moov", "trak", "mdia", "minf", "udta", "stbl") - - private var mTextureView: TextureView? = null - private var mCurrTimeView: TextView? = null - private var mSeekBar: SeekBar? = null - private var mExoPlayer: SimpleExoPlayer? = null - private var mVideoSize = Point(0, 0) - private var mTimerHandler = Handler() - private var mHidePlayPauseHandler = Handler() - - private var mIsPlaying = false - private var mIsDragged = false +class VideoFragment : ViewPagerFragment() { private var mIsFullscreen = false - private var mIsFragmentVisible = false private var mWasFragmentInit = false - private var mIsExoPlayerInitialized = false private var mIsPanorama = false - private var mWasVideoStarted = false - private var mCurrTime = 0 - private var mDuration = 0 private var mStoredShowExtendedDetails = false private var mStoredHideExtendedDetails = false private var mStoredBottomActions = true private var mStoredExtendedDetails = 0 - private var mStoredRememberLastVideoPosition = false - private var mStoredLastVideoPath = "" - private var mStoredLastVideoPosition = 0 - private lateinit var mTimeHolder: View private lateinit var mBrightnessSideScroll: MediaSideScroll private lateinit var mVolumeSideScroll: MediaSideScroll @@ -81,30 +39,19 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S mView = inflater.inflate(R.layout.pager_video_item, container, false).apply { instant_prev_item.setOnClickListener { listener?.goToPrevItem() } instant_next_item.setOnClickListener { listener?.goToNextItem() } - video_curr_time.setOnClickListener { skip(false) } - video_duration.setOnClickListener { skip(true) } video_holder.setOnClickListener { toggleFullscreen() } video_preview.setOnClickListener { toggleFullscreen() } panorama_outline.setOnClickListener { openPanorama() } + video_play_outline.setOnClickListener { launchVideoPlayer() } - // adding an empty click listener just to avoid ripple animation at toggling fullscreen - video_seekbar.setOnClickListener { } - - mTimeHolder = video_time_holder mBrightnessSideScroll = video_brightness_controller mVolumeSideScroll = video_volume_controller - mCurrTimeView = video_curr_time if (context.config.allowDownGesture) { video_preview.setOnTouchListener { v, event -> handleEvent(event) false } - - video_surface.setOnTouchListener { v, event -> - handleEvent(event) - false - } } } @@ -112,46 +59,22 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S medium = arguments!!.getSerializable(MEDIUM) as Medium Glide.with(context!!).load(medium.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() - medium.path.getVideoResolution()?.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(medium.path).into(video_preview) - } + if (mIsPanorama) { + mView.apply { + panorama_outline.beVisible() + video_play_outline.beGone() + mVolumeSideScroll.beGone() + mBrightnessSideScroll.beGone() + Glide.with(context!!).load(medium.path).into(video_preview) } } if (!mIsPanorama) { - setupPlayer() - if (savedInstanceState != null) { - mCurrTime = savedInstanceState.getInt(PROGRESS) - } - - checkFullscreen() mWasFragmentInit = true - mExoPlayer = ExoPlayerFactory.newSimpleInstance(context) - mExoPlayer!!.seekParameters = SeekParameters.CLOSEST_SYNC - initExoPlayerListeners() - - if (mVideoSize.x != 0 && mVideoSize.y != 0) { - setVideoSize() - } - mView.apply { mBrightnessSideScroll.initialize(activity!!, slide_info, true, container) { x, y -> video_holder.performClick() @@ -160,22 +83,10 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S mVolumeSideScroll.initialize(activity!!, slide_info, false, container) { x, y -> video_holder.performClick() } - - video_surface.onGlobalLayout { - if (mIsFragmentVisible && context?.config?.autoplayVideos == true) { - playVideo() - } - } } } - setupVideoDuration() - if (mStoredRememberLastVideoPosition) { - setLastVideoSavedPosition() - } - updateInstantSwitchWidths() - return mView } @@ -197,47 +108,16 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S checkExtendedDetails() } - if (config.bottomActions != mStoredBottomActions) { - initTimeHolder() - } - - mTimeHolder.setBackgroundResource(if (config.bottomActions) 0 else R.drawable.gradient_background) storeStateVariables() } override fun onPause() { super.onPause() - pauseVideo() - if (mStoredRememberLastVideoPosition && mIsFragmentVisible && mWasVideoStarted) { - saveVideoProgress() - } - storeStateVariables() } - 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 && context?.config?.autoplayVideos == true) { - playVideo() - } - } - override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - setVideoSize() - initTimeHolder() checkExtendedDetails() updateInstantSwitchWidths() } @@ -248,365 +128,17 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S mStoredHideExtendedDetails = hideExtendedDetails mStoredExtendedDetails = extendedDetails mStoredBottomActions = bottomActions - mStoredRememberLastVideoPosition = rememberLastVideoPosition - mStoredLastVideoPath = lastVideoPath - mStoredLastVideoPosition = lastVideoPosition } } - private fun setupPlayer() { - if (activity == null) - return - - mView.video_play_outline.setOnClickListener { togglePlayPause() } - - mTextureView = mView.video_surface - mTextureView!!.setOnClickListener { toggleFullscreen() } - mTextureView!!.surfaceTextureListener = this - - checkExtendedDetails() - } - - private fun saveVideoProgress() { - if (!videoEnded()) { - mStoredLastVideoPosition = mExoPlayer!!.currentPosition.toInt() / 1000 - mStoredLastVideoPath = medium.path - } - - context!!.config.apply { - lastVideoPosition = mStoredLastVideoPosition - lastVideoPath = mStoredLastVideoPath - } - } - - private fun initExoPlayer() { - val isContentUri = medium.path.startsWith("content://") - val uri = if (isContentUri) Uri.parse(medium.path) else Uri.fromFile(File(medium.path)) - val dataSpec = DataSpec(uri) - val fileDataSource = if (isContentUri) ContentDataSource(context) else FileDataSource() - try { - fileDataSource.open(dataSpec) - } catch (e: Exception) { - activity?.showErrorToast(e) - } - - val factory = DataSource.Factory { fileDataSource } - val audioSource = ExtractorMediaSource(fileDataSource.uri, factory, DefaultExtractorsFactory(), null, null) - mExoPlayer!!.audioStreamType = C.STREAM_TYPE_MUSIC - mExoPlayer!!.prepare(audioSource) - } - - 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?) { - mIsExoPlayerInitialized = false - } - - override fun onLoadingChanged(isLoading: Boolean) {} - - override fun onPositionDiscontinuity(reason: Int) {} - - override fun onRepeatModeChanged(repeatMode: Int) {} - - override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {} - - override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {} - - override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { - mIsExoPlayerInitialized = playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED - 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 launchVideoPlayer() { + activity!!.openPath(medium.path, false) } private fun toggleFullscreen() { listener?.fragmentClicked() } - private fun setLastVideoSavedPosition() { - if (mStoredLastVideoPath == medium.path && mStoredLastVideoPosition > 0) { - setPosition(mStoredLastVideoPosition) - } - } - - private fun initTimeHolder() { - val left = 0 - val top = 0 - var right = 0 - var bottom = 0 - - if (hasNavBar()) { - if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { - bottom += context!!.navigationBarHeight - } else { - right += context!!.navigationBarWidth - bottom += context!!.navigationBarHeight - } - } - - if (context!!.config.bottomActions) { - bottom += resources.getDimension(R.dimen.bottom_actions_height).toInt() - } - - mTimeHolder.setPadding(left, top, right, bottom) - - mSeekBar = mView.video_seekbar - mSeekBar!!.setOnSeekBarChangeListener(this) - mTimeHolder.beInvisibleIf(mIsFullscreen) - } - - private fun hasNavBar(): Boolean { - val display = context!!.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) - } - - 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) - } - }) - } - - 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 - mHidePlayPauseHandler.removeCallbacksAndMessages(null) - if (mIsPlaying) { - playVideo() - } else { - pauseVideo() - } - } - - fun playVideo() { - if (mExoPlayer == null) { - return - } - - if (mView.video_preview.isVisible()) { - mView.video_preview.beGone() - initExoPlayer() - } - - val wasEnded = videoEnded() - if (wasEnded) { - setPosition(0) - } - - if (mStoredRememberLastVideoPosition) { - setLastVideoSavedPosition() - clearLastVideoSavedProgress() - } - - if (!wasEnded || context?.config?.loopVideos == false) { - mView.video_play_outline.setImageResource(R.drawable.ic_pause) - mView.video_play_outline.alpha = PLAY_PAUSE_VISIBLE_ALPHA - } - - schedulePlayPauseFadeOut() - mWasVideoStarted = true - mIsPlaying = true - mExoPlayer?.playWhenReady = true - activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - - private fun clearLastVideoSavedProgress() { - mStoredLastVideoPosition = 0 - mStoredLastVideoPath = "" - } - - private fun pauseVideo() { - if (mExoPlayer == null) { - return - } - - mIsPlaying = false - if (!videoEnded()) { - mExoPlayer?.playWhenReady = false - } - - mView.video_play_outline?.setImageResource(R.drawable.ic_play) - mView.video_play_outline?.alpha = PLAY_PAUSE_VISIBLE_ALPHA - schedulePlayPauseFadeOut() - activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - - private fun schedulePlayPauseFadeOut() { - mHidePlayPauseHandler.removeCallbacksAndMessages(null) - mHidePlayPauseHandler.postDelayed({ - mView.video_play_outline.animate().alpha(0f).start() - }, HIDE_PLAY_PAUSE_DELAY) - } - - 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() - } - - private fun setupVideoDuration() { - mDuration = medium.path.getVideoDuration() - setupTimeHolder() - setPosition(0) - } - - private fun videoPrepared() { - if (mDuration == 0) { - mDuration = (mExoPlayer!!.duration / 1000).toInt() - setupTimeHolder() - setPosition(mCurrTime) - - if (mIsFragmentVisible && (context!!.config.autoplayVideos)) { - playVideo() - } - } - } - - private fun videoCompleted() { - if (!isAdded || mExoPlayer == null) { - return - } - - mCurrTime = (mExoPlayer!!.duration / 1000).toInt() - if (listener?.videoEnded() == false && context!!.config.loopVideos) { - playVideo() - } else { - mSeekBar!!.progress = mSeekBar!!.max - mCurrTimeView!!.text = mDuration.getFormattedDuration() - pauseVideo() - } - } - - private fun cleanup() { - pauseVideo() - mCurrTimeView?.text = 0.getFormattedDuration() - releaseExoPlayer() - mSeekBar?.progress = 0 - mTimerHandler.removeCallbacksAndMessages(null) - mHidePlayPauseHandler.removeCallbacksAndMessages(null) - mTextureView = null - } - - private fun releaseExoPlayer() { - mExoPlayer?.stop() - Thread { - mExoPlayer?.release() - mExoPlayer = null - }.start() - } - - 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) { - Thread { - mExoPlayer?.setVideoSurface(Surface(mTextureView!!.surfaceTexture)) - }.start() - } - - private fun setVideoSize() { - if (activity == null || mTextureView == null) - 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 - } - } - private fun checkExtendedDetails() { if (context!!.config.showExtendedDetails) { mView.video_details.apply { @@ -628,118 +160,17 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S } } - private fun skip(forward: Boolean) { - if (mExoPlayer == null || mIsPanorama) { - return - } - - val curr = mExoPlayer!!.currentPosition - val twoPercents = Math.max((mExoPlayer!!.duration / 50).toInt(), MIN_SKIP_LENGTH) - val newProgress = if (forward) curr + twoPercents else curr - twoPercents - val roundProgress = Math.round(newProgress / 1000f) - val limitedProgress = Math.max(Math.min(mExoPlayer!!.duration.toInt(), roundProgress), 0) - setPosition(limitedProgress) - if (!mIsPlaying) { - togglePlayPause() - } - } - - override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { - if (mExoPlayer != null && fromUser) { - setPosition(progress) - } - } - - override fun onStartTrackingTouch(seekBar: SeekBar) { - if (mExoPlayer == null) - return - - mExoPlayer!!.playWhenReady = false - mIsDragged = true - } - - override fun onStopTrackingTouch(seekBar: SeekBar) { - if (mIsPanorama) { - openPanorama() - return - } - - if (mExoPlayer == null) - return - - if (mIsPlaying) { - mExoPlayer!!.playWhenReady = true - } else { - togglePlayPause() - } - - mIsDragged = false - } - private fun checkIfPanorama() { try { val fis = FileInputStream(File(medium.path)) - parseFileChannel(fis.channel, 0, 0, 0) + context!!.parseFileChannel(medium.path, fis.channel, 0, 0, 0) { + mIsPanorama = true + } } catch (ignored: Exception) { } catch (ignored: OutOfMemoryError) { } } - // based on https://github.com/sannies/mp4parser/blob/master/examples/src/main/java/com/google/code/mp4parser/example/PrintStructure.java - private fun parseFileChannel(fc: FileChannel, level: Int, start: Long, end: Long) { - 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(medium.path)) - fis.skip(begin) - - val sb = StringBuilder() - val buffer = ByteArray(1024) - while (true) { - val n = fis.read(buffer) - if (n != -1) { - sb.append(String(buffer, 0, n)) - } else { - break - } - } - - val xmlString = sb.toString().toLowerCase() - mIsPanorama = xmlString.contains("gspherical:projectiontype>equirectangular") || - xmlString.contains("gspherical:projectiontype=\"equirectangular\"") - return - } - - if (FILE_CHANNEL_CONTAINERS.contains(type)) { - parseFileChannel(fc, level + 1, begin + 8, newEnd) - } - - fc.position(newEnd) - } - } catch (ignored: Exception) { - } - } - private fun openPanorama() { Intent(context, PanoramaVideoActivity::class.java).apply { putExtra(PATH, medium.path) @@ -755,7 +186,6 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S override fun fullscreenToggled(isFullscreen: Boolean) { mIsFullscreen = isFullscreen - checkFullscreen() mView.video_details.apply { if (mStoredShowExtendedDetails && isVisible()) { animate().y(getExtendedDetailsY(height)) @@ -769,7 +199,6 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S private fun getExtendedDetailsY(height: Int): Float { val smallMargin = resources.getDimension(R.dimen.small_margin) - val fullscreenOffset = smallMargin + if (mIsFullscreen) 0 else mTimeHolder.height - return context!!.realScreenSize.y.toFloat() - height - fullscreenOffset + return context!!.realScreenSize.y.toFloat() - height - smallMargin } } 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 index cdb7ef840..1b479de80 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Config.kt @@ -59,6 +59,22 @@ class Config(context: Context) : BaseConfig(context) { fun hasCustomGrouping(path: String) = prefs.contains(GROUP_FOLDER_PREFIX + path.toLowerCase()) + fun saveFolderViewType(path: String, value: Int) { + if (path.isEmpty()) { + viewTypeFiles = value + } else { + prefs.edit().putInt(VIEW_TYPE_PREFIX + path.toLowerCase(), value).apply() + } + } + + fun getFolderViewType(path: String) = prefs.getInt(VIEW_TYPE_PREFIX + path.toLowerCase(), viewTypeFiles) + + fun removeFolderViewType(path: String) { + prefs.edit().remove(VIEW_TYPE_PREFIX + path.toLowerCase()).apply() + } + + fun hasCustomViewType(path: String) = prefs.contains(VIEW_TYPE_PREFIX + path.toLowerCase()) + var wasHideFolderTooltipShown: Boolean get() = prefs.getBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, false) set(wasShown) = prefs.edit().putBoolean(HIDE_FOLDER_TOOLTIP_SHOWN, wasShown).apply() @@ -137,7 +153,7 @@ class Config(context: Context) : BaseConfig(context) { set(includedFolders) = prefs.edit().remove(INCLUDED_FOLDERS).putStringSet(INCLUDED_FOLDERS, includedFolders).apply() var autoplayVideos: Boolean - get() = prefs.getBoolean(AUTOPLAY_VIDEOS, false) + get() = prefs.getBoolean(AUTOPLAY_VIDEOS, true) set(autoplay) = prefs.edit().putBoolean(AUTOPLAY_VIDEOS, autoplay).apply() var animateGifs: Boolean @@ -265,14 +281,6 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getInt(SLIDESHOW_INTERVAL, SLIDESHOW_DEFAULT_INTERVAL) set(slideshowInterval) = prefs.edit().putInt(SLIDESHOW_INTERVAL, slideshowInterval).apply() - var slideshowIncludePhotos: Boolean - get() = prefs.getBoolean(SLIDESHOW_INCLUDE_PHOTOS, true) - set(slideshowIncludePhotos) = prefs.edit().putBoolean(SLIDESHOW_INCLUDE_PHOTOS, slideshowIncludePhotos).apply() - - var slideshowIncludeVideos: Boolean - get() = prefs.getBoolean(SLIDESHOW_INCLUDE_VIDEOS, false) - set(slideshowIncludeVideos) = prefs.edit().putBoolean(SLIDESHOW_INCLUDE_VIDEOS, slideshowIncludeVideos).apply() - var slideshowIncludeGIFs: Boolean get() = prefs.getBoolean(SLIDESHOW_INCLUDE_GIFS, false) set(slideshowIncludeGIFs) = prefs.edit().putBoolean(SLIDESHOW_INCLUDE_GIFS, slideshowIncludeGIFs).apply() @@ -384,8 +392,10 @@ class Config(context: Context) : BaseConfig(context) { fun getEverShownFolders() = hashSetOf( internalStoragePath, - Environment.DIRECTORY_DCIM, - Environment.DIRECTORY_PICTURES + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath, + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath, + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath, + "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath}/Screenshots" ) var showRecycleBinAtFolders: Boolean 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 index 95a068eb0..16e61ddc8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/Constants.kt @@ -6,6 +6,7 @@ import com.simplemobiletools.commons.helpers.MONTH_SECONDS const val DIRECTORY_SORT_ORDER = "directory_sort_order" const val SORT_FOLDER_PREFIX = "sort_folder_" const val GROUP_FOLDER_PREFIX = "group_folder_" +const val VIEW_TYPE_PREFIX = "view_type_folder_" const val SHOW_HIDDEN_MEDIA = "show_hidden_media" const val TEMPORARILY_SHOW_HIDDEN = "temporarily_show_hidden" const val IS_THIRD_PARTY_INTENT = "is_third_party_intent" @@ -76,8 +77,6 @@ const val SHOW_WIDGET_FOLDER_NAME = "show_widget_folder_name" // slideshow const val SLIDESHOW_INTERVAL = "slideshow_interval" -const val SLIDESHOW_INCLUDE_PHOTOS = "slideshow_include_photos" -const val SLIDESHOW_INCLUDE_VIDEOS = "slideshow_include_videos" const val SLIDESHOW_INCLUDE_GIFS = "slideshow_include_gifs" const val SLIDESHOW_RANDOM_ORDER = "slideshow_random_order" const val SLIDESHOW_USE_FADE = "slideshow_use_fade" 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 index b57e37c70..3978d0a49 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MediaFetcher.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/helpers/MediaFetcher.kt @@ -19,22 +19,27 @@ class MediaFetcher(val context: Context) { var shouldStop = false fun getFilesFrom(curPath: String, isPickImage: Boolean, isPickVideo: Boolean, getProperDateTaken: Boolean, favoritePaths: ArrayList, - getVideoDurations: Boolean): ArrayList { + getVideoDurations: Boolean, sortMedia: Boolean = true): ArrayList { val filterMedia = context.config.filterMedia if (filterMedia == 0) { return ArrayList() } val curMedia = ArrayList() - if (curPath.startsWith(OTG_PATH)) { - val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia, favoritePaths, getVideoDurations) - curMedia.addAll(newMedia) + if (curPath.startsWith(OTG_PATH, true)) { + if (context.hasOTGConnected()) { + val newMedia = getMediaOnOTG(curPath, isPickImage, isPickVideo, filterMedia, favoritePaths, getVideoDurations) + curMedia.addAll(newMedia) + } } else { val newMedia = getMediaInFolder(curPath, isPickImage, isPickVideo, filterMedia, getProperDateTaken, favoritePaths, getVideoDurations) curMedia.addAll(newMedia) } - sortMedia(curMedia, context.config.getFileSorting(curPath)) + if (sortMedia) { + sortMedia(curMedia, context.config.getFileSorting(curPath)) + } + return curMedia } @@ -177,27 +182,27 @@ class MediaFetcher(val context: Context) { ArrayList() } + val doExtraCheck = context.config.doExtraCheck + val showHidden = context.config.shouldShowHidden + val dateTakens = if (getProperDateTaken && folder != FAVORITES && folder != RECYCLE_BIN) getFolderDateTakens(folder) else HashMap() + val files = when (folder) { - FAVORITES -> favoritePaths.map { File(it) }.toTypedArray() + FAVORITES -> favoritePaths.filter { showHidden || !it.contains("/.") }.map { File(it) }.toTypedArray() RECYCLE_BIN -> deletedMedia.map { File(it.path) }.toTypedArray() else -> File(folder).listFiles() ?: return media } - val doExtraCheck = context.config.doExtraCheck - val showHidden = context.config.shouldShowHidden - val dateTakens = if (getProperDateTaken) getFolderDateTakens(folder) else HashMap() - for (file in files) { if (shouldStop) { break } - val filename = file.name - val isImage = filename.isImageFast() - val isVideo = if (isImage) false else filename.isVideoFast() - val isGif = if (isImage || isVideo) false else filename.isGif() - val isRaw = if (isImage || isVideo || isGif) false else filename.isRawFast() - val isSvg = if (isImage || isVideo || isGif || isRaw) false else filename.isSvg() + val path = file.absolutePath + val isImage = path.isImageFast() + val isVideo = if (isImage) false else path.isVideoFast() + val isGif = if (isImage || isVideo) false else path.isGif() + val isRaw = if (isImage || isVideo || isGif) false else path.isRawFast() + val isSvg = if (isImage || isVideo || isGif || isRaw) false else path.isSvg() if (!isImage && !isVideo && !isGif && !isRaw && !isSvg) continue @@ -217,6 +222,7 @@ class MediaFetcher(val context: Context) { if (isSvg && filterMedia and TYPE_SVGS == 0) continue + val filename = file.name if (!showHidden && filename.startsWith('.')) continue @@ -224,7 +230,6 @@ class MediaFetcher(val context: Context) { if (size <= 0L || (doExtraCheck && !file.exists())) continue - val path = file.absolutePath if (folder == RECYCLE_BIN) { deletedMedia.firstOrNull { it.path == path }?.apply { media.add(this) @@ -364,8 +369,9 @@ class MediaFetcher(val context: Context) { else -> o1.taken.compareTo(o2.taken) } - if (result == 0) { - result = AlphanumericComparator().compare(o1.path.toLowerCase(), o2.path.toLowerCase()) + // do just a quick extra sorting if the original sorting is equal, does not need to be accurate in all cases + if (result == 0 && sorting and SORT_BY_NAME == 0 && sorting and SORT_BY_PATH == 0) { + result = o1.name.compareTo(o2.name) } if (sorting and SORT_DESCENDING != 0) { 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 index 916550f21..0c424b631 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/MediaSideScroll.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/views/MediaSideScroll.kt @@ -10,8 +10,8 @@ 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.activities.ViewPagerActivity import com.simplemobiletools.gallery.pro.extensions.audioManager import com.simplemobiletools.gallery.pro.helpers.CLICK_MAX_DURATION import com.simplemobiletools.gallery.pro.helpers.DRAG_THRESHOLD @@ -25,6 +25,7 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co private var mTouchDownValue = -1 private var mTempBrightness = 0 private var mLastTouchY = 0f + private var mViewHeight = 0 private var mIsBrightnessScroll = false private var mPassTouches = false private var dragThreshold = DRAG_THRESHOLD * context.resources.displayMetrics.density @@ -44,6 +45,9 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co mParentView = parentView mIsBrightnessScroll = isBrightness mSlideInfoText = activity.getString(if (isBrightness) R.string.brightness else R.string.volume) + onGlobalLayout { + mViewHeight = height + } } override fun dispatchTouchEvent(ev: MotionEvent): Boolean { @@ -80,7 +84,7 @@ class MediaSideScroll(context: Context, attrs: AttributeSet) : RelativeLayout(co val diffY = mTouchDownY - event.y if (Math.abs(diffY) > dragThreshold && Math.abs(diffY) > Math.abs(diffX)) { - var percent = ((diffY / ViewPagerActivity.screenHeight) * 100).toInt() * 3 + var percent = ((diffY / mViewHeight) * 100).toInt() * 3 percent = Math.min(100, Math.max(-100, percent)) if ((percent == 100 && event.y > mLastTouchY) || (percent == -100 && event.y < mLastTouchY)) { diff --git a/app/src/main/res/drawable-hdpi/ic_panorama.png b/app/src/main/res/drawable-hdpi/ic_panorama.png deleted file mode 100644 index 513420277..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_panorama.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_pause_outline.png b/app/src/main/res/drawable-hdpi/ic_pause_outline.png new file mode 100644 index 000000000..d6d51bfc9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_pause_outline.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_play_outline.png b/app/src/main/res/drawable-hdpi/ic_play_outline.png new file mode 100644 index 000000000..12be0724f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_play_outline.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_panorama.png b/app/src/main/res/drawable-xhdpi/ic_panorama.png deleted file mode 100644 index c7f48d89c..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_panorama.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_pause_outline.png b/app/src/main/res/drawable-xhdpi/ic_pause_outline.png new file mode 100644 index 000000000..11d8d1698 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pause_outline.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_outline.png b/app/src/main/res/drawable-xhdpi/ic_play_outline.png new file mode 100644 index 000000000..f99911f6c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_outline.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_panorama.png b/app/src/main/res/drawable-xxhdpi/ic_panorama.png deleted file mode 100644 index 17e7cde47..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_panorama.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_pause_outline.png b/app/src/main/res/drawable-xxhdpi/ic_pause_outline.png new file mode 100644 index 000000000..d6f0c72ce Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pause_outline.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_outline.png b/app/src/main/res/drawable-xxhdpi/ic_play_outline.png new file mode 100644 index 000000000..156d6ca62 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_play_outline.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_panorama.png b/app/src/main/res/drawable-xxxhdpi/ic_panorama.png deleted file mode 100644 index deaad2e1c..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_panorama.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_pause_outline.png b/app/src/main/res/drawable-xxxhdpi/ic_pause_outline.png new file mode 100644 index 000000000..162ed84d1 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_pause_outline.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_outline.png b/app/src/main/res/drawable-xxxhdpi/ic_play_outline.png new file mode 100644 index 000000000..77370d426 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_play_outline.png differ diff --git a/app/src/main/res/drawable/circle_black_background_with_inset.xml b/app/src/main/res/drawable/circle_black_background_with_inset.xml deleted file mode 100644 index 6bab48721..000000000 --- a/app/src/main/res/drawable/circle_black_background_with_inset.xml +++ /dev/null @@ -1,7 +0,0 @@ - - diff --git a/app/src/main/res/layout/activity_panorama_video.xml b/app/src/main/res/layout/activity_panorama_video.xml index 7b7919887..343496d24 100644 --- a/app/src/main/res/layout/activity_panorama_video.xml +++ b/app/src/main/res/layout/activity_panorama_video.xml @@ -11,12 +11,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"/> - + - - - - 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..8a0efeeb3 --- /dev/null +++ b/app/src/main/res/layout/activity_video_player.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/bottom_video_time_holder.xml b/app/src/main/res/layout/bottom_video_time_holder.xml index cdf1e0c53..69acead92 100644 --- a/app/src/main/res/layout/bottom_video_time_holder.xml +++ b/app/src/main/res/layout/bottom_video_time_holder.xml @@ -5,12 +5,25 @@ android:id="@+id/video_time_holder" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentBottom="true"> + android:layout_alignParentBottom="true" + android:background="@drawable/gradient_background"> + + + + diff --git a/app/src/main/res/layout/dialog_slideshow.xml b/app/src/main/res/layout/dialog_slideshow.xml index 48da05572..7915aa5dc 100644 --- a/app/src/main/res/layout/dialog_slideshow.xml +++ b/app/src/main/res/layout/dialog_slideshow.xml @@ -36,53 +36,11 @@ android:textCursorDrawable="@null" android:textSize="@dimen/normal_text_size"/> - - - - - - - - - - - - - - + android:padding="20dp" + android:src="@drawable/ic_play_outline"/> - 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..00e818ca3 --- /dev/null +++ b/app/src/main/res/menu/menu_video_player.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4f9e5a2c7..2f15a3572 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -218,7 +218,7 @@ - استوديو لعرض الصور والفيديو بدون اعلانات. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 372505031..e12e2ee27 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -149,7 +149,7 @@ System setting Device rotation Aspect ratio - Black background and status bar at fullscreen media + Black background and at fullscreen media Scroll thumbnails horizontally Automatically hide system UI at fullscreen media Delete empty folders after deleting their content @@ -216,7 +216,7 @@ - Şəkil və videolara baxmaq üçün reklamsız qalereya. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index df1bbe48b..555fd7dbd 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -216,7 +216,7 @@ - Una galeria per veure imatges i vídeos sense publicitat. + An offline gallery for managing your files without ads, respecting your privacy. Una galeria molt personalitzable capaç de mostrar molts tipus d\'imatge i de vídeo diferents, inclosos SVGs, RAWs, fotos panoràmiques i vídeos. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index e8706c85b..b27b77b50 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -133,8 +133,8 @@ Přípony - Folder shown on the widget: - Show folder name + Složka zobrazená na widgetu: + Zobrazit název složky Automaticky přehrávat videa @@ -144,12 +144,12 @@ Animovat náhledy souborů GIF Nastavit jas obrazovky na max při zobrazení médií Oříznout náhledy na čtverce - Show video durations + Zobrazit dobu trvání videí Otočit média podle Systémového nastavení Otočení zařízení Poměru stran - Černé pozadí a stavová lišta při médiích na celou obrazovku + Č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 @@ -175,7 +175,7 @@ Náhledy Celoobrazovkový režim Rozšířené vlastnosti - Spodní skční tlačítka + Spodní akční tlačítka Upravit viditelné spodní akční tlačítka @@ -216,7 +216,7 @@ - Galerie na prohlížení obrázků a videí bez reklam. + An offline gallery for managing your files without ads, respecting your privacy. Přizpůsobitelná galerie na zobrazování množství rozličných druhů obrázků a videí, včetně SVG, RAW souborů, panoramatických fotek a videí. @@ -227,7 +227,7 @@ 2. Prezentace 3. Podpora pro výřez v displeji 4. Připínání složek na vrch - 5. Filtování médií podle typu + 5. Filtrování médií podle typu 6. Odpadkový koš pro snadnou obnovu souborů 7. Uzamykání orientace celoobrazovkového režimu 8. Označování oblíbených položek pro snadný přístup diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 0557e2732..059977161 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -30,15 +30,15 @@ Brug standard orientering Fiks Dato Taget værdi Fikser… - Dates fixed successfully + Datoer fikset med succes - Filtrer medier + Filtrér medier Billeder Videoer GIF\'er - RAW images - SVGs + RAW billeder + SVG\'er Der blev ikke fundet nogen filer med det valgte filter. Skift filter @@ -67,7 +67,7 @@ Skalér valgte elementer og gem Bredde Højde - Bevar billedeforhold + Bevar billedforhold Indtast en gyldig opløsning @@ -88,8 +88,8 @@ Spejlvend vandret Spejlvend lodret Rediger med - Free - Other + Fri + Andet Simple Wallpaper @@ -111,7 +111,7 @@ Inkluder videoer Inkluder GIF\'er Tilfældig rækkefølge - Use fade animations + Brug udtonende animationer Kør baglæns Endeløs kørsel Slideshowet endte @@ -216,7 +216,7 @@ - Et galleri til visning af billeder og videoer uden reklamer. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0237128fb..ddae898bf 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -215,7 +215,7 @@ - Eine schlichte Galerie zum Betrachten von Bildern und Videos, ganz ohne Werbung. + An offline gallery for managing your files without ads, respecting your privacy. Eine stark anpassbare Galerie fähig zur Anzeige von diversen Bild- und Videoarten u. a. SVG, RAW, Panoramafotos und -videos. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 3c29ab646..bec488bcf 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -216,7 +216,7 @@ - Μία Gallery για την προβολή φωτογραφιών και βίντεο χωρίς διαφημίσεις. + An offline gallery for managing your files without ads, respecting your privacy. Μια εξαιρετικά προσαρμόσιμη Gallery ικανή να εμφανίζει πολλούς διαφορετικούς τύπους εικόνας και βίντεο, όπως SVGs, RAWs, πανοραμικές φωτογραφίες και βίντεο. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b10955ebc..16787f52d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -216,7 +216,7 @@ - Una galería para ver fotos y vídeos sin publicidad. + An offline gallery for managing your files without ads, respecting your privacy. Una galería altamente personalizable capaz de mostrar diferentes tipos de imágenes y videos, incluyendo SVG, RAW, fotos panorámicas y videos. diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 0ebaef866..5575db260 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -216,7 +216,7 @@ - Galleria kuvien ja videoiden katsomiseen ilman mainoksia. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5af44b0a6..89d4c2a6c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -144,7 +144,7 @@ GIFs animés sur les miniatures Luminosité maximale Recadrer les miniatures en carrés - Show video durations + Montrer la durée de la vidéo Pivoter l\'affichage selon Paramètres système Rotation de l\'appareil @@ -214,7 +214,7 @@ - Une galerie pour visionner photos et vidéos sans publicité. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 79e7592bc..919d02c66 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -216,7 +216,7 @@ - Unha galería para ver fotos e videos, pero non publicidade. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 8fee29889..b7452023b 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -216,7 +216,7 @@ - Galerija za gledanje fotografija i videozapisa bez oglasa. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index bdfeb0610..0d2833293 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -150,7 +150,7 @@ Rendszer beállítások Eszköz elforgatás Képarány - Fekete háttérszín és állapotsáv teljes képernyős médiánál + 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 @@ -217,7 +217,7 @@ Ezzel csak a kiválasztott mappák láthatók, mivel a kizárás és a befoglal - Galéria a fotók és videók hirdetések nélküli megtekintéséhez. + An offline gallery for managing your files without ads, respecting your privacy. Nagyon testreszabható galéria, amely alkalmas számos különböző kép- és videotípus megjelenítésére, beleértve az SVG-ket, RAW-t, panorámaképeket és videókat. diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 319a68155..9bf419404 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -216,7 +216,7 @@ - Aplikasi galeri untuk melihat foto dan video tanpa iklan. + An offline gallery for managing your files without ads, respecting your privacy. Aplikasi galeri dengan banyak kustomisasi dan mampu menampilkan banyak jenis gambar dan video termasuk SVG, RAW, panorama foto dan video. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index fc126811d..ba7f48e3b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,6 +1,6 @@ - Simple Gallery + Semplice Galleria Galleria Modifica Apri fotocamera @@ -29,38 +29,38 @@ Forza orizzontale Usa l\'orientamento predefinito Correggi valore Data acquisizione - Correzione… + Correzione in corso… Date aggiornate correttamente - Filtra i media + Filtra i file Immagini Video GIF Immagini RAW SVG - Nessun file trovato per il filtro selezionato. + 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 le cartelle escluse - Questo escluderà la selezione e le relative sottocartelle solo da Simple Gallery. Puoi gestire le cartelle escluse nelle impostazioni. + 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 - Pare che tu non abbia alcuna cartella nascosta con un file \".nomedia\". + Non si ha alcuna cartella nascosta con un file \".nomedia\". Includi cartelle 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. Ridimensiona @@ -75,11 +75,11 @@ Salva Ruota Percorso - Percorso immagine non valido - Modifica immagine fallita + Percorso dell\'immagine non valido + Modifica dell\'immagine fallita Modifica immagine con: - Editor immagini non trovato - Posizione file sconosciuta + Editor delle immagini non trovato + Posizione del file sconosciuta Impossibile sovrascrivere il file originale Ruota a sinistra Ruota a destra @@ -137,7 +137,7 @@ Mostra il nome della cartella - Riproduci i video automaticamente + Riproduci automaticamente i video Ricorda l\'ultimo stato di riproduzione dei video Visibilità nome del file Ripeti i video @@ -149,9 +149,9 @@ Impostazione di sistema Rotazione dispositivo Proporzioni - Sfondo e barra di stato neri con media a schermo intero + Sfondo e barra di stato neri con i file a schermo intero Scorri le miniature orizzontalmente - Nascondi UI di sistema con media a schermo intero + 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 @@ -172,7 +172,7 @@ Chiudi la visuale a schermo intero con un gesto verso il basso - Miniature + Anteprime Media a schermo intero Dettagli estesi Azioni inferiori @@ -183,68 +183,68 @@ Attiva / disattiva la visibilità dei file - Come posso impostare Simple Gallery come galleria predefinita del dispositivo? - Prima devi trovare l\'attuale galleria predefinita nella sezione App delle impostazioni di sistema, cerca un pulsante che dice qualcosa come \"App predefinite\", cliccalo, poi seleziona \"Ripristina predefiniti\". - La prossima volta che proverai ad aprire un\'immagine o video dovresti vedere un selettore di app, dove puoi scegliere Simple Gallery e renderla l\'app predefinita. + 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? - Puoi risolvere in 2 modi. Puoi reinstallare l\'app, oppure trova l\'app nelle impostazioni del dispositivo e seleziona \"Cancella dati\". Tutte le impostazioni verranno ripristinate, nessun file multimediale verrà rimosso. + 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? - Puoi toccare a lungo l\'album desiderato e selezionare l\'icona puntina nel menù azioni, ciò lo fisserà in cima. Puoi anche fissare varie cartelle, gli elementi fissati verranno ordinati dal metodo di ordinamento predefinito. + 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 cliccare sui testi di durata attuale o massima vicino alla barra di avanzamento, ciò avanzerà o riavvolgerà il video. + Si può cliccare sui testi di durata attuale o massima vicino alla barra di avanzamento, ciò avanzerà o riavvolgerà il video. Che differenza c\'è tra nascondere ed escludere una cartella? - Escludere impedisce la visualizzazione della cartella solo in Simple Gallery, 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, che puoi anche rimuovere successivamente con qualsiasi gestore di file. + 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. Perchè vengono mostrate cartelle con copertine o adesivi di musica? - Può succedere che tu veda apparire alcuni album insoliti. Puoi escluderli facilmente toccandoli a lungo e selezionando Escludi. Nella finestra successiva puoi quindi selezionare la cartella superiore, con la possibilità di impedire la visualizzazione anche di altri album correlati. + 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. + 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. Le immagini a schermo intero hanno strani artefatti, posso migliorarne la qualità in qualche modo? - Sì, c\'è un\'opzione nelle impostazioni che dice \"Sostituisci le immagini ingrandibili a fondo con altre di migliore qualità\", puoi usare quella. Ciò migliorerà la qualità delle immagini, ma saranno sfuocate quando proverai a ingrandirle troppo. + Sì, c\'è un\'opzione nelle impostazioni che dice \"Sostituisci le immagini ingrandibili a fondo con altre di migliore qualità\". Ciò migliorerà la qualità delle immagini, ma saranno sfuocate quando si proverà a ingrandirle troppo. Posso ritagliare le immagini con questa app? - Sì, puoi ritagliare le immagini nell\'editor, trascinando gli angoli dell\'immagine. Puoi usare l\'editor sia premendo a lungo la miniatura di un\'immagine e selezionando Modifica, o selezionando Modifica mentre la vedi a schermo intero. + Sì, 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, usa il menu \"Raggruppa per\" mentre visualizzi le miniature. Puoi raggruppare i file con diversi criteri, incluso la data di creazione. Se utilizzi la funzione \"Mostra tutti i contenuti\" puoi anche raggrupparli per cartelle. + 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. Puoi risolvere selezionando le miniature dei file e scegliendo \"Correggi valore Data acquisizione\". + 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 vuoi una qualità ancora maggiore, puoi attivare \"Mostra le immagini alla massima qualità possibile\" nelle impostazioni dell\'app, nella sezione \"Immagini ingrandibili a fondo\". + 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? - Puoi premere \"Mostra temporaneamente nascosti\" nel menu della schermata principale, oppure attivare \"Mostra gli elementi nascosti\" nelle impostazioni dell\'app per vedere l\'elemento nascosto. Se vuoi che resti visibile, premilo a lungo e seleziona \"Non nascondere\". Le cartelle vengono nascoste aggiungendo un file nascosto \".nomedia\" all\'interno di esse, puoi anche eliminare il file con qualsiasi gestore di file. + 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. - Una galleria per visualizzare foto e video senza pubblicità. + Una galleria per gestire i propri file senza pubblicità che rispetta la privacy. Una galleria altamente personalizzabile e capace di visualizzare tipi di file immagini e video differenti, fra cui SVG, RAW, foto panoramiche e video. - È open source, non contiene pubblicità e autorizzazioni superflue. + L\'applicazione non contiene pubblicità o permessi non necessari; è completamente opensource e la si può personalizzare con i propri colori preferiti. + Alcune funzionalità che vale la pena accennare: 1. Ricerca 2. Presentazione 3. Supporto al notch 4. Fissare le cartelle in alto - 5. Filtro dei file per tipo + 5. Filtro dei file per tipologia 6. Cestino per un recupero facile dei file 7. Blocco dell\'orientamento nella vista a schermo intero - 8. Selezionare file preferiti per un accesso facile - 9. Chisura rapida della vista schermo intero con un movimento verso il basso - 10. Un editor per modificare le immagini ed applicare filtri + 8. Selezione dei file preferiti per un accesso immediato + 9. Chisura rapida della vista a schermo intero con un movimento verso il basso + 10. Un editor per modificare le immagini e applicare filtri 11. Protezione con password per proteggere elementi nascosti o l\'intera applicazione 12. Cambio delle colonne delle anteprime con un movimento o tramite dei pulsanti nel menu - 13. Pulsanti rapidi per azioni personalizzabili nella vista schermo intero + 13. Pulsanti rapidi per azioni personalizzabili nella vista a schermo intero 14. Visualizzazione di determinati dettagli aggiuntivi nella vista a schermo intero 15. Molti modi per ordinare o raggruppare gli elementi, sia in ordine crescente che decrescente - 16. Cartelle nascoste (anche per altre applicazioni), cartelle escluse (solo per Simple Gallery) + 16. Cartelle nascoste (anche per altre applicazioni), cartelle escluse (solo per Semplice Galleria) - L\'autorizzazione per leggere le impronte digitali è necessaria per il blocco della visibilità degli elementi, dell\'intera applicazione o per proteggere alcuni file dalla loro eliminazione. - - Questa applicazione è solamente una di una serie più grande. Si possono trovare le altre su https://www.simplemobiletools.com + L\'autorizzazione per leggere le impronte digitali è necessaria per il blocco della visibilità, dell\'intera applicazione o per proteggere alcuni file dalla loro eliminazione. + + Questa è solamente una delle tante applicazioni della serie Simple Mobile Tools. Si possono trovare le altre su https://www.simplemobiletools.com - - 写真やビデオを見るためのギャラリー。広告はありません。 + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index aef49c472..440a533b6 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -216,7 +216,7 @@ - 광고없이 사진과 동영상을 볼 수 있는 갤러리. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index d791865cf..bdaefa6bb 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -216,7 +216,7 @@ - Galerija, skirta peržiūrėti nuotraukas ir vaizdo įrašus be reklamų. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 6b42ebde8..04ff031ca 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -216,7 +216,7 @@ - A gallery for viewing photos and videos without ads. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index df6819783..efab9aca5 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -216,7 +216,7 @@ - Een galerij voor afbeeldingen en video\'s, zonder advertenties. + Een privacyvriendelijke advertentievrije galerij voor afbeeldingen en video\'s. Een zeer goed aan te passen galerij voor afbeeldingen en video\'s in vele bestandsformaten, waaronder SVG, RAW, panoramafoto\'s en -video\'s. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 42ba52f06..fa1b6654f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -89,7 +89,7 @@ Przewróć w pionie Edytuj w: Wolne - Other + Inny Tapeta @@ -133,8 +133,8 @@ Rozszerzenia - Folder shown on the widget: - Show folder name + Folder wyświetlany na widżecie: + Pokaż nazwę folderu Odtwarzaj filmy automatycznie @@ -214,7 +214,7 @@ - Prosta galeria bez reklam do przeglądania obrazów i filmów. + An offline gallery for managing your files without ads, respecting your privacy. Wysoce konfigurowalna galeria obsługująca wiele formatów obrazów i filmów, w tym SVG, RAW oraz multimedia panoramiczne. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index cb971ca18..cb3587889 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -216,7 +216,7 @@ - Um aplicativo para visualizar fotos e vídeos. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index bff249a51..c3932e292 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -216,7 +216,7 @@ - Uma aplicação para ver fotografias e vídeos. + An offline gallery for managing your files without ads, respecting your privacy. Um aplicação capaz de mostrar diversos tipos de imagens e vídeos incluíndo SVG, RAW, fotos panorâmicas e vídeos. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b2f1c0ec2..3541f561f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -216,7 +216,7 @@ - Галерея для просмотра изображений и видео. Без рекламы. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index f6592eb1d..604d715f8 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -149,7 +149,7 @@ Systémového nastavenia Otočenia zariadenia Pomeru strán - Čierne pozadie a stavová lišta 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 @@ -216,7 +216,7 @@ - Galéria na prezeranie obrázkov a videí bez reklám. + Offline galéria na správu vašich súborov, rešpektujúca vaše súkromie. Nastaviteľná galéria na zobrazovanie množstva rozličných druhov obrázkov a videí, vrátane SVG, RAW súborov, panoramatických fotiek a videí. diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index e8b8941e8..fe0e6a3f4 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -215,7 +215,7 @@ 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. - Galerija za ogled fotografij in videoposnetkov brez reklam. + An offline gallery for managing your files without ads, respecting your privacy. Visoko prilagodljiva galerija, zmožna prikazovanja različnih tipov fotografij in videoposnetkov, vključno s SVGji, RAWi, panoramskimi fotografijami in videoposnetki. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 45da7bafb..bfa6dc3dc 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -216,7 +216,7 @@ - Ett galleri för att visa foton och videor utan reklam. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 33bdcfecb..bbd08e2fe 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -216,7 +216,7 @@ - Reklamsız fotoğrafları ve videoları görüntülemek için bir galeri. + An offline gallery for managing your files without ads, respecting your privacy. SVG\'ler, RAW\'lar, panoramik fotoğraflar ve videolar dahil olmak üzere birçok farklı resim ve video türünü gösterebilen son derece özelleştirilebilir bir galeri. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 24bd7ead0..e8d0a3f17 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -216,7 +216,7 @@ - Галерея для перегляду фото та відео без реклами. + Офлайн-галерея для керування файлами: без реклами та з повагою до приватності. Тонко налаштовувана галерея, здатна відображати зображення та відео різноманітних типів, включаючи SVG-зображення, RAW-зображення,панорамні фото і відео. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index e77503681..a266a88de 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -133,8 +133,8 @@ 扩展 - Folder shown on the widget: - Show folder name + 要在小部件上显示的文件夹: + 显示文件夹名称 自动播放 @@ -214,7 +214,7 @@ - 一个没有广告,用来观看照片及视频的相册。 + An offline gallery for managing your files without ads, respecting your privacy. 一个高度可定制的图库,支持很多的图像和视频类型,包括SVG,RAW,全景照片和视频。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index d6304564d..08c7be32b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -216,7 +216,7 @@ - 一個用來瀏覽相片和影片,且沒有廣告的相簿。 + 一個沒有廣告的離線相簿,用來管理你的檔案,並且尊重您的隱私。 一個高自訂性的相簿,能夠顯示許多不同的圖片和影片類型,包含SVGs、RAWs、全景相片和影片。 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 89072fb3e..b2135537c 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -8,6 +8,7 @@ 96dp 60dp 60dp + 60dp 50dp 72dp 64dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 808d89d8b..6fa8f41a3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,7 +149,7 @@ System setting Device rotation Aspect ratio - Black background and status bar at fullscreen media + Black background and at fullscreen media Scroll thumbnails horizontally Automatically hide system UI at fullscreen media Delete empty folders after deleting their content @@ -216,7 +216,7 @@ - A gallery for viewing photos and videos without ads. + An offline gallery for managing your files without ads, respecting your privacy. A highly customizable gallery capable of displaying many different image and video types including SVGs, RAWs, panoramic photos and videos. diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/app_6.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/app_6.jpg index a0f0752fe..db4b66408 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/app_6.jpg and b/fastlane/metadata/android/en-US/images/phoneScreenshots/app_6.jpg differ