Replace MotionLayout with full screen dialog (#5806)

* Remove MotionLayout and add full screen dialog for enlarged cover

* Address some of the review comments
This commit is contained in:
Andreas 2021-08-28 22:53:59 +02:00 committed by GitHub
parent eebfad5a95
commit e7d6dfff53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 237 additions and 234 deletions

View file

@ -30,6 +30,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import coil.imageLoader
import coil.request.ImageRequest
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
@ -73,6 +74,7 @@ import eu.kanade.tachiyomi.ui.manga.chapter.DeleteChaptersDialog
import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomChaptersDialog
import eu.kanade.tachiyomi.ui.manga.chapter.MangaChaptersHeaderAdapter
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChaptersAdapter
import eu.kanade.tachiyomi.ui.manga.info.MangaFullCoverDialog
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoHeaderAdapter
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.manga.track.TrackSearchDialog
@ -179,6 +181,8 @@ class MangaController :
private var trackSheet: TrackSheet? = null
private var dialog: MangaFullCoverDialog? = null
init {
setHasOptionsMenu(true)
}
@ -435,7 +439,6 @@ class MangaController :
// Hide options for non-library manga
menu.findItem(R.id.action_edit_categories).isVisible = presenter.manga.favorite && presenter.getCategories().isNotEmpty()
menu.findItem(R.id.action_edit_cover).isVisible = presenter.manga.favorite
menu.findItem(R.id.action_migrate).isVisible = presenter.manga.favorite
}
@ -446,10 +449,6 @@ class MangaController :
R.id.download_custom, R.id.download_unread, R.id.download_all
-> downloadChapters(item.itemId)
R.id.action_share_cover -> shareCover()
R.id.action_save_cover -> saveCover()
R.id.action_edit_cover -> changeCover()
R.id.action_edit_categories -> onCategoriesClick()
R.id.action_migrate -> migrateManga()
}
@ -728,6 +727,21 @@ class MangaController :
context.imageLoader.enqueue(req)
}
fun showFullCoverDialog() {
if (dialog != null) return
val manga = manga ?: return
dialog = MangaFullCoverDialog(this, manga)
dialog?.addLifecycleListener(
object : LifecycleListener() {
override fun postDestroy(controller: Controller) {
super.postDestroy(controller)
dialog = null
}
}
)
dialog?.showDialog(router)
}
fun shareCover() {
try {
val activity = activity!!
@ -800,6 +814,7 @@ class MangaController :
fun onSetCoverSuccess() {
mangaInfoAdapter?.notifyDataSetChanged()
dialog?.setImage(manga)
activity?.toast(R.string.cover_updated)
}

View file

@ -0,0 +1,119 @@
package eu.kanade.tachiyomi.ui.manga.info
import android.app.Dialog
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.util.TypedValue
import android.view.View
import androidx.core.graphics.ColorUtils
import androidx.core.os.bundleOf
import androidx.core.view.WindowCompat
import coil.imageLoader
import coil.request.Disposable
import coil.request.ImageRequest
import com.davemorrissey.labs.subscaleview.ImageSource
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.MangaFullCoverDialogBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
import eu.kanade.tachiyomi.widget.TachiyomiFullscreenDialog
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MangaFullCoverDialog : DialogController {
private var manga: Manga? = null
private var binding: MangaFullCoverDialogBinding? = null
private var disposable: Disposable? = null
private val mangaController
get() = targetController as MangaController
constructor(targetController: MangaController, manga: Manga) : super(bundleOf("mangaId" to manga.id)) {
this.targetController = targetController
this.manga = manga
}
@Suppress("unused")
constructor(bundle: Bundle) : super(bundle) {
val db = Injekt.get<DatabaseHelper>()
manga = db.getManga(bundle.getLong("mangaId")).executeAsBlocking()
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
binding = MangaFullCoverDialogBinding.inflate(activity!!.layoutInflater)
binding?.toolbar?.apply {
setNavigationOnClickListener { dialog?.dismiss() }
setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_share_cover -> mangaController.shareCover()
R.id.action_save_cover -> mangaController.saveCover()
R.id.action_edit_cover -> mangaController.changeCover()
}
true
}
menu?.findItem(R.id.action_edit_cover)?.isVisible = manga?.favorite ?: false
}
binding?.fullCover?.setOnClickListener {
dialog?.dismiss()
}
setImage(manga)
binding?.appbar?.applyInsetter {
type(navigationBars = true, statusBars = true) {
padding(left = true, top = true, right = true)
}
}
binding?.fullCover?.applyInsetter {
type(navigationBars = true) {
// Padding will make to image top align
// This is likely an issue with SubsamplingScaleImageView
margin(bottom = true)
}
}
return TachiyomiFullscreenDialog(activity!!, binding!!.root).apply {
val typedValue = TypedValue()
val theme = context.theme
theme.resolveAttribute(android.R.attr.colorBackground, typedValue, true)
window?.setBackgroundDrawable(ColorDrawable(ColorUtils.setAlphaComponent(typedValue.data, 230)))
}
}
override fun onAttach(view: View) {
super.onAttach(view)
dialog?.window?.let { window ->
window.setNavigationBarTransparentCompat(window.context)
WindowCompat.setDecorFitsSystemWindows(window, false)
}
}
override fun onDetach(view: View) {
super.onDetach(view)
disposable?.dispose()
disposable = null
}
fun setImage(manga: Manga?) {
val manga = manga ?: return
val request = ImageRequest.Builder(applicationContext!!)
.data(manga)
.target {
val bitmap = (it as BitmapDrawable).bitmap
binding?.fullCover?.setImage(ImageSource.cachedBitmap(bitmap))
}
.build()
disposable = applicationContext?.imageLoader?.enqueue(request)
}
}

View file

@ -3,11 +3,9 @@ package eu.kanade.tachiyomi.ui.manga.info
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import coil.target.ImageViewTarget
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
@ -87,15 +85,10 @@ class MangaInfoHeaderAdapter(
binding.mangaCover.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin += appBarHeight
}
binding.root.getConstraintSet(R.id.end)
?.setMargin(R.id.manga_cover, ConstraintLayout.LayoutParams.TOP, appBarHeight)
}
inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val headerTransition = binding.root.getTransition(R.id.manga_info_header_transition)
headerTransition.applySystemAnimatorScale(view.context)
val summaryTransition = binding.mangaSummarySection.getTransition(R.id.manga_summary_section_transition)
summaryTransition.applySystemAnimatorScale(view.context)
@ -199,6 +192,12 @@ class MangaInfoHeaderAdapter(
}
.launchIn(controller.viewScope)
binding.mangaCover.clicks()
.onEach {
controller.showFullCoverDialog()
}
.launchIn(controller.viewScope)
binding.mangaCover.longClicks()
.onEach {
showCoverOptionsDialog()
@ -286,17 +285,7 @@ class MangaInfoHeaderAdapter(
// Set cover if changed.
binding.backdrop.loadAnyAutoPause(manga)
binding.mangaCover.loadAnyAutoPause(manga) {
listener(
onSuccess = { request, _ ->
(request.target as? ImageViewTarget)?.drawable?.let { drawable ->
val ratio = drawable.minimumWidth / drawable.minimumHeight.toFloat()
binding.root.getConstraintSet(R.id.end)
?.setDimensionRatio(R.id.manga_cover, ratio.toString())
}
}
)
}
binding.mangaCover.loadAnyAutoPause(manga)
// Manga info section
val hasInfoContent = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()

View file

@ -7,7 +7,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatDialog
import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import androidx.core.view.WindowCompat
@ -21,6 +20,7 @@ import eu.kanade.tachiyomi.databinding.TrackSearchDialogBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
import eu.kanade.tachiyomi.widget.TachiyomiFullscreenDialog
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -142,9 +142,7 @@ class TrackSearchDialog : DialogController {
}
}
return AppCompatDialog(activity!!, R.style.ThemeOverlay_Tachiyomi_Dialog_Fullscreen).apply {
setContentView(binding!!.root)
}
return TachiyomiFullscreenDialog(activity!!, binding!!.root)
}
override fun onAttach(view: View) {

View file

@ -0,0 +1,13 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import android.view.View
import androidx.appcompat.app.AppCompatDialog
import eu.kanade.tachiyomi.R
class TachiyomiFullscreenDialog(context: Context, view: View) : AppCompatDialog(context, R.style.ThemeOverlay_Tachiyomi_Dialog_Fullscreen) {
init {
setContentView(view)
}
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
</vector>

View file

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layoutDescription="@xml/manga_info_header_scene_sw720dp"
tools:context=".ui.browse.source.browse.BrowseSourceController">
<ImageView
@ -288,4 +287,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:contentInsetStartWithNavigation="0dp"
app:menu="@menu/full_cover"
app:navigationIcon="@drawable/ic_close_24dp" />
</com.google.android.material.appbar.AppBarLayout>
<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
android:id="@+id/full_cover"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layoutDescription="@xml/manga_info_header_scene"
tools:context=".ui.browse.source.browse.BrowseSourceController">
<ImageView
@ -178,9 +177,10 @@
android:id="@+id/manga_summary_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutDescription="@xml/manga_summary_section_scene"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_actions"
app:layoutDescription="@xml/manga_summary_section_scene">
<TextView
android:id="@+id/manga_summary_text"
@ -285,4 +285,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_share_cover"
android:icon="@drawable/ic_share_24dp"
android:title="@string/action_share"
app:iconTint="?attr/colorOnToolbar"
app:showAsAction="always" />
<item
android:id="@+id/action_save_cover"
android:icon="@drawable/ic_save_24dp"
android:title="@string/action_save"
app:iconTint="?attr/colorOnToolbar"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_edit_cover"
android:icon="@drawable/ic_edit_24dp"
android:title="@string/action_edit"
app:iconTint="?attr/colorOnToolbar"
app:showAsAction="ifRoom" />
</menu>

View file

@ -37,23 +37,6 @@
</menu>
</item>
<item
android:id="@+id/cover_group"
android:title="@string/manga_cover"
app:showAsAction="never">
<menu>
<item
android:id="@+id/action_share_cover"
android:title="@string/action_share" />
<item
android:id="@+id/action_save_cover"
android:title="@string/action_save" />
<item
android:id="@+id/action_edit_cover"
android:title="@string/action_edit" />
</menu>
</item>
<item
android:id="@+id/action_edit_categories"
android:title="@string/action_edit_categories"

View file

@ -1,122 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
android:id="@+id/manga_info_header_transition"
motion:duration="@android:integer/config_mediumAnimTime">
<KeyFrameSet></KeyFrameSet>
<OnClick motion:targetId="@+id/manga_cover" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/manga_summary_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@id/manga_actions"
motion:visibilityMode="ignore" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/backdrop"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="-8dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="@+id/manga_cover"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:transitionEasing="cubic(0,1,0,1)" />
<Constraint
android:id="@+id/backdrop_overlay"
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_marginBottom="-16dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="@+id/backdrop"
motion:transitionEasing="cubic(0,1,0,1)" />
<Constraint
android:id="@+id/manga_cover"
android:layout_marginStart="0dp"
android:layout_marginTop="0dp"
motion:layout_constraintDimensionRatio="2:3"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/btn_favorite"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
motion:layout_constraintEnd_toStartOf="@+id/btn_tracking"
motion:layout_constraintHorizontal_bias="0.5"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@id/manga_detail"
android:layout_marginStart="16dp" />
<Constraint
android:id="@+id/manga_detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@id/manga_cover" />
<Constraint
android:id="@+id/btn_tracking"
android:layout_width="0dp"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toStartOf="@+id/btn_webview"
motion:layout_constraintHorizontal_bias="0.5"
motion:layout_constraintStart_toEndOf="@+id/btn_favorite"
motion:layout_constraintTop_toTopOf="@+id/btn_favorite"
motion:visibilityMode="ignore" />
<Constraint
android:id="@+id/btn_tracking"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="visible"
motion:layout_constraintEnd_toStartOf="@+id/btn_webview"
motion:layout_constraintHorizontal_bias="0.5"
motion:layout_constraintStart_toEndOf="@+id/btn_favorite"
motion:layout_constraintTop_toTopOf="@+id/btn_favorite"
motion:visibilityMode="ignore" />
<Constraint
android:id="@+id/btn_webview"
android:layout_width="0dp"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintHorizontal_bias="0.5"
motion:layout_constraintStart_toEndOf="@+id/btn_tracking"
motion:layout_constraintTop_toTopOf="@+id/btn_favorite"
motion:visibilityMode="ignore"
android:layout_marginEnd="16dp" />
<Constraint
android:id="@+id/manga_summary_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@id/manga_actions"
motion:visibilityMode="ignore" />
<Constraint
android:id="@+id/manga_actions"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
motion:layout_constraintTop_toBottomOf="@id/manga_detail"
motion:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" />
</ConstraintSet>
</MotionScene>

View file

@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
android:id="@+id/manga_info_header_transition"
motion:duration="@android:integer/config_mediumAnimTime">
<KeyFrameSet></KeyFrameSet>
<OnClick motion:targetId="@+id/manga_cover" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/manga_summary_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@id/manga_actions"
motion:visibilityMode="ignore" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/manga_summary_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@id/manga_actions"
motion:visibilityMode="ignore" />
<Constraint
android:id="@+id/manga_detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@+id/manga_cover" />
<Constraint
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintDimensionRatio="w,3:2"
android:layout_marginTop="0dp"
android:id="@+id/manga_cover" />
</ConstraintSet>
</MotionScene>