diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/FullCoverDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/FullCoverDialog.kt new file mode 100644 index 0000000000..2cb136b490 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/FullCoverDialog.kt @@ -0,0 +1,251 @@ +package eu.kanade.tachiyomi.ui.manga + +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.app.Dialog +import android.graphics.Color +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.os.Build +import android.view.LayoutInflater +import android.view.View +import android.view.animation.DecelerateInterpolator +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.animation.addListener +import androidx.transition.ChangeBounds +import androidx.transition.ChangeImageTransform +import androidx.transition.TransitionManager +import androidx.transition.TransitionSet +import com.google.android.material.shape.CornerFamily +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.databinding.FullCoverDialogBinding +import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.view.animateBlur +import eu.kanade.tachiyomi.util.view.updateLayoutParams +import uy.kohesive.injekt.injectLazy + +class FullCoverDialog(val controller: MangaDetailsController, drawable: Drawable, val thumbView: View) : + Dialog(controller.activity!!, R.style.FullCoverDialogTheme) { + + val activity = controller.activity + val binding = FullCoverDialogBinding.inflate(LayoutInflater.from(context), null, false) + val preferences: PreferencesHelper by injectLazy() + + private val ratio = 5f.dpToPx + private val fullRatio = 0f + val shortAnimationDuration = ( + activity?.resources?.getInteger( + android.R.integer.config_shortAnimTime + ) ?: 0 + ).toLong() + + init { + setContentView(binding.root) + + binding.touchOutside.setOnClickListener { + onBackPressed() + } + binding.mangaCoverFull.setOnClickListener { + onBackPressed() + } + + binding.btnSave.setOnClickListener { + controller.saveCover() + } + binding.btnShare.setOnClickListener { + controller.shareCover() + } + + val expandedImageView = binding.mangaCoverFull + expandedImageView.shapeAppearanceModel = + expandedImageView.shapeAppearanceModel.toBuilder() + .setAllCorners(CornerFamily.ROUNDED, ratio) + .build() + + expandedImageView.setImageDrawable(drawable) + + val rect = Rect() + thumbView.getGlobalVisibleRect(rect) + val topInset = activity?.window?.decorView?.rootWindowInsets?.systemWindowInsetTop ?: 0 + val leftInset = activity?.window?.decorView?.rootWindowInsets?.systemWindowInsetLeft ?: 0 + val rightInset = activity?.window?.decorView?.rootWindowInsets?.systemWindowInsetRight ?: 0 + expandedImageView.updateLayoutParams { + height = thumbView.height + width = thumbView.width + topMargin = rect.top - topInset + leftMargin = rect.left - leftInset + rightMargin = rect.right - rightInset + bottomMargin = rect.bottom + horizontalBias = 0.0f + verticalBias = 0.0f + } + expandedImageView.requestLayout() + binding.btnShare.alpha = 0f + binding.btnSave.alpha = 0f + + expandedImageView.post { + // Hide the thumbnail and show the zoomed-in view. When the animation + // begins, it will position the zoomed-in view in the place of the + // thumbnail. + thumbView.alpha = 0f + val defMargin = 16.dpToPx + if (Build.VERSION.SDK_INT >= 31) { + activity?.window?.decorView?.animateBlur(1f, 20f, 50)?.start() + } + expandedImageView.updateLayoutParams { + height = 0 + width = 0 + topMargin = defMargin + leftMargin = defMargin + rightMargin = defMargin + bottomMargin = defMargin + horizontalBias = 0.5f + verticalBias = 0.5f + } + + // TransitionSet for the full cover because using animation for this SUCKS + val transitionSet = TransitionSet() + val bound = ChangeBounds() + transitionSet.addTransition(bound) + val changeImageTransform = ChangeImageTransform() + transitionSet.addTransition(changeImageTransform) + transitionSet.duration = shortAnimationDuration + TransitionManager.beginDelayedTransition(binding.root, transitionSet) + + AnimatorSet().apply { + val radiusAnimator = ValueAnimator.ofFloat(ratio, fullRatio).apply { + addUpdateListener { + val value = it.animatedValue as Float + expandedImageView.shapeAppearanceModel = + expandedImageView.shapeAppearanceModel.toBuilder() + .setAllCorners(CornerFamily.ROUNDED, value) + .build() + } + duration = shortAnimationDuration + } + val saveAnimator = ValueAnimator.ofFloat(binding.btnShare.alpha, 1f).apply { + addUpdateListener { + binding.btnShare.alpha = it.animatedValue as Float + binding.btnSave.alpha = it.animatedValue as Float + } + } + playTogether(radiusAnimator, saveAnimator) + duration = shortAnimationDuration + interpolator = DecelerateInterpolator() + start() + } + } + + window?.let { window -> + window.navigationBarColor = Color.TRANSPARENT + window.decorView.fitsSystemWindows = true + window.decorView.systemUiVisibility = window.decorView.systemUiVisibility + .rem(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + window.decorView.systemUiVisibility = window.decorView.systemUiVisibility + .rem(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) + } + } + } + + override fun cancel() { + super.cancel() + thumbView.alpha = 1f + } + + override fun dismiss() { + super.dismiss() + thumbView.alpha = 1f + } + + private fun animateBack() { + val rect2 = Rect() + thumbView.getGlobalVisibleRect(rect2) + binding.mangaCoverFull.isClickable = false + binding.touchOutside.isClickable = false + val expandedImageView = binding.mangaCoverFull + val topInset = activity?.window?.decorView?.rootWindowInsets?.systemWindowInsetTop ?: 0 + val leftInset = activity?.window?.decorView?.rootWindowInsets?.systemWindowInsetLeft ?: 0 + val rightInset = activity?.window?.decorView?.rootWindowInsets?.systemWindowInsetRight ?: 0 + expandedImageView.updateLayoutParams { + height = thumbView.height + width = thumbView.width + topMargin = rect2.top - topInset + leftMargin = rect2.left - leftInset + rightMargin = rect2.right - rightInset + bottomMargin = rect2.bottom + horizontalBias = 0.0f + verticalBias = 0.0f + } + + // Zoom out back to tc thumbnail + val transitionSet2 = TransitionSet() + val bound2 = ChangeBounds() + transitionSet2.addTransition(bound2) + val changeImageTransform2 = ChangeImageTransform() + transitionSet2.addTransition(changeImageTransform2) + transitionSet2.duration = shortAnimationDuration + TransitionManager.beginDelayedTransition(binding.root, transitionSet2) + + if (Build.VERSION.SDK_INT >= 31) { + activity?.window?.decorView?.animateBlur(20f, 0.1f, 50, true)?.apply { + startDelay = shortAnimationDuration - 100 + }?.start() + } + val attrs = window?.attributes + val ogDim = attrs?.dimAmount ?: 0.25f + + // AnimationSet for backdrop because idk how to use TransitionSet + AnimatorSet().apply { + val radiusAnimator = ValueAnimator.ofFloat(fullRatio, ratio).apply { + addUpdateListener { + val value = it.animatedValue as Float + expandedImageView.shapeAppearanceModel = + expandedImageView.shapeAppearanceModel.toBuilder() + .setAllCorners(CornerFamily.ROUNDED, value) + .build() + } + } + val dimAnimator = ValueAnimator.ofFloat(ogDim, 0f).apply { + addUpdateListener { + window?.setDimAmount(it.animatedValue as Float) + } + } + + val saveAnimator = ValueAnimator.ofFloat(binding.btnShare.alpha, 0f).apply { + addUpdateListener { + binding.btnShare.alpha = it.animatedValue as Float + binding.btnSave.alpha = it.animatedValue as Float + } + } + + playTogether(radiusAnimator, dimAnimator, saveAnimator) + + addListener( + onEnd = { + TransitionManager.endTransitions(binding.root) + thumbView.alpha = 1f + expandedImageView.post { + dismiss() + } + }, + onCancel = { + TransitionManager.endTransitions(binding.root) + thumbView.alpha = 1f + expandedImageView.post { + dismiss() + } + } + ) + interpolator = DecelerateInterpolator() + duration = shortAnimationDuration + }.start() + } + + override fun onBackPressed() { + if (binding.mangaCoverFull.isClickable) { + animateBack() + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index 120e3162bd..bb72d573d8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -1,9 +1,5 @@ package eu.kanade.tachiyomi.ui.manga -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.AnimatorSet -import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.app.Activity import android.content.ClipData @@ -12,9 +8,6 @@ import android.content.Context import android.content.Intent import android.content.res.Configuration import android.graphics.Color -import android.graphics.Rect -import android.graphics.RenderEffect -import android.graphics.Shader import android.graphics.drawable.BitmapDrawable import android.os.Build import android.os.Bundle @@ -25,7 +18,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.view.WindowInsets -import android.view.animation.DecelerateInterpolator import android.view.inputmethod.InputMethodManager import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode @@ -37,10 +29,6 @@ import androidx.palette.graphics.Palette import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import androidx.transition.ChangeBounds -import androidx.transition.ChangeImageTransform -import androidx.transition.TransitionManager -import androidx.transition.TransitionSet import coil.Coil import coil.imageLoader import coil.request.ImageRequest @@ -92,11 +80,9 @@ import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.moveCategories import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.ThemeUtil -import eu.kanade.tachiyomi.util.system.contextCompatDrawable import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getPrefTheme import eu.kanade.tachiyomi.util.system.getResourceColor -import eu.kanade.tachiyomi.util.system.isInNightMode import eu.kanade.tachiyomi.util.system.isOnline import eu.kanade.tachiyomi.util.system.isTablet import eu.kanade.tachiyomi.util.system.launchUI @@ -187,9 +173,6 @@ class MangaDetailsController : private var actionMode: ActionMode? = null - // Hold a reference to the current animator, so that it can be canceled mid-way. - private var currentAnimator: Animator? = null - var headerHeight = 0 var fullCoverActive = false @@ -516,14 +499,6 @@ class MangaDetailsController : activityBinding?.root?.clearFocus() } } - - override fun handleBack(): Boolean { - if (binding.mangaCoverFull.visibility == View.VISIBLE) { - binding.mangaCoverFull.performClick() - return true - } - return super.handleBack() - } //endregion fun isNotOnline(showSnackbar: Boolean = true): Boolean { @@ -822,15 +797,6 @@ class MangaDetailsController : //region action bar menu methods override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - if (fullCoverActive) { - colorToolbar(isColor = false) - activityBinding?.toolbar?.navigationIcon = - view?.context?.contextCompatDrawable(R.drawable.ic_arrow_back_24dp)?.apply { - setTint(Color.WHITE) - } - inflater.inflate(R.menu.manga_details_cover, menu) - return - } colorToolbar(binding.recycler.canScrollVertically(-1)) activityBinding?.toolbar?.navigationIcon = activityBinding?.toolbar?.navigationIcon?.mutate()?.apply { @@ -921,34 +887,36 @@ class MangaDetailsController : R.id.download_next, R.id.download_next_5, R.id.download_custom, R.id.download_unread, R.id.download_all -> downloadChapters( item.itemId ) - R.id.save -> { - if (presenter.saveCover()) { - activity?.toast(R.string.cover_saved) - } else { - activity?.toast(R.string.error_saving_cover) - } - } - R.id.share -> { - val cover = presenter.shareCover() - if (cover != null) { - val stream = cover.getUriCompat(activity!!) - val intent = Intent(Intent.ACTION_SEND).apply { - putExtra(Intent.EXTRA_STREAM, stream) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - clipData = ClipData.newRawUri(null, stream) - type = "image/*" - } - startActivity(Intent.createChooser(intent, activity?.getString(R.string.share))) - } else { - activity?.toast(R.string.error_sharing_cover) - } - } else -> return super.onOptionsItemSelected(item) } return true } //endregion + fun saveCover() { + if (presenter.saveCover()) { + activity?.toast(R.string.cover_saved) + } else { + activity?.toast(R.string.error_saving_cover) + } + } + + fun shareCover() { + val cover = presenter.shareCover() + if (cover != null) { + val stream = cover.getUriCompat(activity!!) + val intent = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, stream) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + clipData = ClipData.newRawUri(null, stream) + type = "image/*" + } + startActivity(Intent.createChooser(intent, activity?.getString(R.string.share))) + } else { + activity?.toast(R.string.error_sharing_cover) + } + } + override fun prepareToShareManga() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val request = ImageRequest.Builder(activity!!).data(manga).target( @@ -1410,185 +1378,22 @@ class MangaDetailsController : } override fun zoomImageFromThumb(thumbView: View) { - // If there's an animation in progress, cancel it immediately and proceed with this one. - currentAnimator?.cancel() - - // Load the high-resolution "zoomed-in" image. - val expandedImageView = binding.mangaCoverFull - val fullBackdrop = binding.fullBackdrop - - // Hide the thumbnail and show the zoomed-in view. When the animation - // begins, it will position the zoomed-in view in the place of the - // thumbnail. - thumbView.alpha = 0f - expandedImageView.visibility = View.VISIBLE - fullBackdrop.visibility = View.VISIBLE - - // Set the pivot point to 0 to match thumbnail - - binding.swipeRefresh.isEnabled = false - - val rect = Rect() - thumbView.getGlobalVisibleRect(rect) - expandedImageView.updateLayoutParams { - height = thumbView.height - width = thumbView.width - topMargin = rect.top - leftMargin = rect.left - rightMargin = rect.right - bottomMargin = rect.bottom - } - expandedImageView.requestLayout() - - val activity = activity as? MainActivity ?: return - val currColor = activity.drawerArrow?.color - if (!activity.isInNightMode()) { - activityBinding?.appBar?.context?.setTheme(R.style.ThemeOverlay_AppCompat_Dark_ActionBar) - - val iconPrimary = Color.WHITE - activityBinding?.toolbar?.setTitleTextColor(iconPrimary) - activity.drawerArrow?.color = iconPrimary - activityBinding?.toolbar?.overflowIcon?.setTint(iconPrimary) - activity.window.decorView.systemUiVisibility = - activity.window.decorView.systemUiVisibility.rem( - View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - ) - } + if (fullCoverActive) return fullCoverActive = true - activity.invalidateOptionsMenu() - - expandedImageView.post { - val defMargin = 16.dpToPx - expandedImageView.updateLayoutParams { - height = ViewGroup.LayoutParams.MATCH_PARENT - width = ViewGroup.LayoutParams.MATCH_PARENT - topMargin = defMargin + headerHeight - leftMargin = defMargin - rightMargin = defMargin - bottomMargin = defMargin + binding.recycler.paddingBottom - } - val shortAnimationDuration = ( - resources?.getInteger( - android.R.integer.config_shortAnimTime - ) ?: 0 - ).toLong() - - // TransitionSet for the full cover because using animation for this SUCKS - val transitionSet = TransitionSet() - val bound = ChangeBounds() - transitionSet.addTransition(bound) - val changeImageTransform = ChangeImageTransform() - transitionSet.addTransition(changeImageTransform) - transitionSet.duration = shortAnimationDuration - TransitionManager.beginDelayedTransition(binding.frameLayout, transitionSet) - - // AnimationSet for backdrop because idk how to use TransitionSet - currentAnimator = AnimatorSet().apply { - play( - ObjectAnimator.ofFloat(fullBackdrop, View.ALPHA, 0f, 0.5f) - ) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - binding.swipeRefresh.setRenderEffect( - RenderEffect.createBlurEffect( - 15f, - 15f, - Shader.TileMode.MIRROR - ) - ) - } - - duration = shortAnimationDuration - interpolator = DecelerateInterpolator() - addListener( - object : AnimatorListenerAdapter() { - - override fun onAnimationEnd(animation: Animator) { - TransitionManager.endTransitions(binding.frameLayout) - currentAnimator = null - } - - override fun onAnimationCancel(animation: Animator) { - TransitionManager.endTransitions(binding.frameLayout) - currentAnimator = null - } - } - ) - start() - } - - expandedImageView.setOnClickListener { - currentAnimator?.cancel() - - fullCoverActive = false - activity.invalidateOptionsMenu() - val rect2 = Rect() - thumbView.getGlobalVisibleRect(rect2) - expandedImageView.updateLayoutParams { - height = thumbView.height - width = thumbView.width - topMargin = rect2.top - leftMargin = rect2.left - rightMargin = rect2.right - bottomMargin = rect2.bottom - } - - // Zoom out back to tc thumbnail - val transitionSet2 = TransitionSet() - val bound2 = ChangeBounds() - transitionSet2.addTransition(bound2) - val changeImageTransform2 = ChangeImageTransform() - transitionSet2.addTransition(changeImageTransform2) - transitionSet2.duration = shortAnimationDuration - TransitionManager.beginDelayedTransition(binding.frameLayout, transitionSet2) - - // Animation to remove backdrop and hide the full cover - currentAnimator = AnimatorSet().apply { - play(ObjectAnimator.ofFloat(fullBackdrop, View.ALPHA, 0f)) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - binding.swipeRefresh.setRenderEffect(null) - } - duration = shortAnimationDuration - interpolator = DecelerateInterpolator() - - if (!activity.isInNightMode()) { - activityBinding?.appBar?.context?.setTheme( - activity.getPrefTheme(presenter.preferences).styleRes - ) - - val iconPrimary = currColor ?: Color.WHITE - activityBinding?.toolbar?.setTitleTextColor(iconPrimary) - activity.drawerArrow?.color = iconPrimary - activityBinding?.toolbar?.overflowIcon?.setTint(iconPrimary) - activity.window.decorView.systemUiVisibility = - activity.window.decorView.systemUiVisibility.or( - View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - ) - } - addListener( - object : AnimatorListenerAdapter() { - - override fun onAnimationEnd(animation: Animator) { - thumbView.alpha = 1f - expandedImageView.visibility = View.GONE - fullBackdrop.visibility = View.GONE - binding.swipeRefresh.isEnabled = true - currentAnimator = null - } - - override fun onAnimationCancel(animation: Animator) { - thumbView.alpha = 1f - expandedImageView.visibility = View.GONE - fullBackdrop.visibility = View.GONE - binding.swipeRefresh.isEnabled = true - currentAnimator = null - } - } - ) - start() - } - } + val expandedImageView = binding.mangaCoverFull + val fullCoverDialog = FullCoverDialog( + this, + expandedImageView.drawable, + thumbView + ) + fullCoverDialog.setOnDismissListener { + fullCoverActive = false } + fullCoverDialog.setOnCancelListener { + fullCoverActive = false + } + fullCoverDialog.show() + return } companion object { diff --git a/app/src/main/res/color/button_action_selector.xml b/app/src/main/res/color/button_action_selector.xml new file mode 100644 index 0000000000..f8e88896b6 --- /dev/null +++ b/app/src/main/res/color/button_action_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/full_cover_dialog.xml b/app/src/main/res/layout/full_cover_dialog.xml new file mode 100644 index 0000000000..8a79dbe9b3 --- /dev/null +++ b/app/src/main/res/layout/full_cover_dialog.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/manga_details_controller.xml b/app/src/main/res/layout/manga_details_controller.xml index c7c2f98e04..f706289cec 100644 --- a/app/src/main/res/layout/manga_details_controller.xml +++ b/app/src/main/res/layout/manga_details_controller.xml @@ -73,17 +73,6 @@ android:layout_height="match_parent" app:fastScrollerBubbleEnabled="true" /> - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 75bbe45876..b839876e0f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -197,6 +197,16 @@ @anim/fade_out_short + + + + + + +