Convert cover dialog view to compose (#7346)

This commit is contained in:
Ivan Iskandar
2022-06-21 09:31:36 +07:00
committed by GitHub
parent cb1830d747
commit 8fedd2d5f1
14 changed files with 833 additions and 306 deletions

View File

@@ -16,6 +16,30 @@ import eu.kanade.tachiyomi.databinding.ComposeControllerBinding
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import nucleus.presenter.Presenter
abstract class FullComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
NucleusController<ComposeControllerBinding, P>(bundle),
FullComposeContentController {
override fun createBinding(inflater: LayoutInflater) =
ComposeControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.root.apply {
consumeWindowInsets = false
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
TachiyomiTheme {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
ComposeContent()
}
}
}
}
}
}
/**
* Compose controller with a Nucleus presenter.
*/
@@ -97,6 +121,10 @@ abstract class SearchableComposeController<P : BasePresenter<*>>(bundle: Bundle?
}
}
interface FullComposeContentController {
@Composable fun ComposeContent()
}
interface ComposeContentController {
@Composable fun ComposeContent(nestedScrollInterop: NestedScrollConnection)
}

View File

@@ -43,6 +43,7 @@ import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.FabController
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.ui.base.controller.NoAppBarElevationController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
@@ -599,6 +600,7 @@ class MainActivity : BaseActivity() {
binding.fabLayout.rootFab.hide()
}
val isFullComposeController = internalTo is FullComposeController<*>
if (!isTablet()) {
// Save lift state
if (isPush) {
@@ -622,8 +624,16 @@ class MainActivity : BaseActivity() {
binding.root.isLiftAppBarOnScroll = internalTo !is NoAppBarElevationController
binding.appbar.isTransparentWhenNotLifted = internalTo is MangaController
binding.controllerContainer.overlapHeader = internalTo is MangaController
binding.appbar.isVisible = !isFullComposeController
binding.controllerContainer.enableScrollingBehavior(!isFullComposeController)
// TODO: Remove when MangaController is full compose
if (!isFullComposeController) {
binding.appbar.isTransparentWhenNotLifted = internalTo is MangaController
binding.controllerContainer.overlapHeader = internalTo is MangaController
}
} else {
binding.appbar.isVisible = !isFullComposeController
}
}

View File

@@ -1,11 +1,7 @@
package eu.kanade.tachiyomi.ui.manga
import android.app.Activity
import android.app.ActivityOptions
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
@@ -24,8 +20,6 @@ import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import coil.imageLoader
import coil.request.ImageRequest
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
@@ -45,8 +39,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.saver.Image
import eu.kanade.tachiyomi.data.saver.Location
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
@@ -61,12 +53,12 @@ import eu.kanade.tachiyomi.ui.base.controller.FabController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.ChangeMangaCoverDialog
import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
@@ -85,12 +77,9 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recent.history.HistoryController
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.hasCustomCover
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.shrinkOnScroll
import eu.kanade.tachiyomi.util.view.snack
@@ -115,7 +104,6 @@ class MangaController :
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
BaseChaptersAdapter.OnChapterClickListener,
ChangeMangaCoverDialog.Listener,
ChangeMangaCategoriesDialog.Listener,
DownloadCustomChaptersDialog.Listener,
DeleteChaptersDialog.Listener {
@@ -724,128 +712,9 @@ class MangaController :
}
}
/**
* Fetches the cover with Coil, turns it into Bitmap and does something with it (asynchronous)
* @param context The context for building and executing the ImageRequest
* @param coverHandler A function that describes what should be done with the Bitmap
*/
private fun useCoverAsBitmap(context: Context, coverHandler: (Bitmap) -> Unit) {
val req = ImageRequest.Builder(context)
.data(manga)
.target { result ->
val coverBitmap = (result as BitmapDrawable).bitmap
coverHandler(coverBitmap)
}
.build()
context.imageLoader.enqueue(req)
}
fun showFullCoverDialog() {
val manga = manga ?: return
MangaFullCoverDialog(this, manga)
.showDialog(router)
}
fun shareCover() {
try {
val manga = manga!!
val activity = activity!!
useCoverAsBitmap(activity) { coverBitmap ->
viewScope.launchIO {
val uri = presenter.saveImage(
image = Image.Cover(
bitmap = coverBitmap,
name = manga.title,
location = Location.Cache,
),
)
launchUI {
startActivity(uri.toShareIntent(activity))
}
}
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
activity?.toast(R.string.error_sharing_cover)
}
}
fun saveCover() {
try {
val manga = manga!!
val activity = activity!!
useCoverAsBitmap(activity) { coverBitmap ->
viewScope.launchIO {
presenter.saveImage(
image = Image.Cover(
bitmap = coverBitmap,
name = manga.title,
location = Location.Pictures.create(),
),
)
launchUI {
activity.toast(R.string.cover_saved)
}
}
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
activity?.toast(R.string.error_saving_cover)
}
}
fun changeCover() {
val manga = manga ?: return
if (manga.hasCustomCover(coverCache)) {
ChangeMangaCoverDialog(this, manga).showDialog(router)
} else {
openMangaCoverPicker(manga)
}
}
override fun openMangaCoverPicker(manga: Manga) {
if (manga.favorite) {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "image/*"
}
startActivityForResult(
Intent.createChooser(
intent,
resources?.getString(R.string.file_select_cover),
),
REQUEST_IMAGE_OPEN,
)
} else {
activity?.toast(R.string.notification_first_add_to_library)
}
destroyActionModeIfNeeded()
}
override fun deleteMangaCover(manga: Manga) {
presenter.deleteCustomCover(manga)
mangaInfoAdapter?.notifyItemChanged(0, manga)
destroyActionModeIfNeeded()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_IMAGE_OPEN) {
val dataUri = data?.data
if (dataUri == null || resultCode != Activity.RESULT_OK) return
val activity = activity ?: return
presenter.editCover(activity, dataUri)
}
}
fun onSetCoverSuccess() {
mangaInfoAdapter?.notifyItemChanged(0, this)
(router.backstack.lastOrNull()?.controller as? MangaFullCoverDialog)?.setImage(manga)
activity?.toast(R.string.cover_updated)
}
fun onSetCoverError(error: Throwable) {
activity?.toast(R.string.notification_cover_update_failed)
logcat(LogPriority.ERROR, error)
val mangaId = manga?.id ?: return
router.pushController(MangaFullCoverDialog(mangaId).withFadeTransaction())
}
/**

View File

@@ -1,7 +1,5 @@
package eu.kanade.tachiyomi.ui.manga
import android.content.Context
import android.net.Uri
import android.os.Bundle
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.domain.category.interactor.GetCategories
@@ -16,13 +14,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.saver.Image
import eu.kanade.tachiyomi.data.saver.ImageSaver
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
@@ -36,17 +31,14 @@ import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
import eu.kanade.tachiyomi.util.chapter.getChapterSort
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
import eu.kanade.tachiyomi.util.editCover
import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.updateCoverLastModified
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
@@ -61,7 +53,6 @@ import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.Date
import eu.kanade.domain.category.model.Category as DomainCategory
@@ -115,8 +106,6 @@ class MangaPresenter(
private val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
private val imageSaver: ImageSaver by injectLazy()
private var trackSubscription: Subscription? = null
private var searchTrackerJob: Job? = null
private var refreshTrackersJob: Job? = null
@@ -295,49 +284,6 @@ class MangaPresenter(
moveMangaToCategories(manga, listOfNotNull(category))
}
/**
* Save manga cover Bitmap to picture or temporary share directory.
*
* @param image the image with specified location
* @return flow Flow which emits the Uri which specifies where the image is saved when
*/
fun saveImage(image: Image): Uri {
return imageSaver.save(image)
}
/**
* Update cover with local file.
*
* @param context Context.
* @param data uri of the cover resource.
*/
fun editCover(context: Context, data: Uri) {
presenterScope.launchIO {
context.contentResolver.openInputStream(data)?.use {
try {
val result = manga.toDomainManga()!!.editCover(context, it)
launchUI { if (result) view?.onSetCoverSuccess() }
} catch (e: Exception) {
launchUI { view?.onSetCoverError(e) }
}
}
}
}
fun deleteCustomCover(manga: Manga) {
Observable
.fromCallable {
coverCache.deleteCustomCover(manga.id)
manga.updateCoverLastModified(db)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, _ -> view.onSetCoverSuccess() },
{ view, e -> view.onSetCoverError(e) },
)
}
// Manga info - end
// Chapters list - start

View File

@@ -1,118 +1,255 @@
package eu.kanade.tachiyomi.ui.manga.info
import android.app.Dialog
import android.graphics.drawable.ColorDrawable
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.os.Bundle
import android.util.TypedValue
import android.view.View
import androidx.core.graphics.ColorUtils
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.core.os.bundleOf
import androidx.core.view.WindowCompat
import coil.imageLoader
import coil.request.Disposable
import coil.request.ImageRequest
import dev.chrisbanes.insetter.applyInsetter
import coil.size.Size
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.presentation.manga.EditCoverAction
import eu.kanade.presentation.manga.components.MangaCoverDialog
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.ui.reader.viewer.ReaderPageImageView
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
import eu.kanade.tachiyomi.widget.TachiyomiFullscreenDialog
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.saver.Image
import eu.kanade.tachiyomi.data.saver.ImageSaver
import eu.kanade.tachiyomi.data.saver.Location
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.util.editCover
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class MangaFullCoverDialog : DialogController {
class MangaFullCoverDialog : FullComposeController<MangaFullCoverDialog.Presenter> {
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
}
private val mangaId: Long
@Suppress("unused")
constructor(bundle: Bundle) : super(bundle) {
val db = Injekt.get<DatabaseHelper>()
manga = db.getManga(bundle.getLong("mangaId")).executeAsBlocking()
constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA))
constructor(
mangaId: Long,
) : super(bundleOf(MANGA_EXTRA to mangaId)) {
this.mangaId = mangaId
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
binding = MangaFullCoverDialogBinding.inflate(activity!!.layoutInflater)
override fun createPresenter() = Presenter(mangaId)
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()
@Composable
override fun ComposeContent() {
val manga = presenter.manga.collectAsState().value
if (manga != null) {
MangaCoverDialog(
coverDataProvider = { manga },
isCustomCover = remember(manga) { manga.hasCustomCover() },
onShareClick = this::shareCover,
onSaveClick = this::saveCover,
onEditClick = this::changeCover,
onDismissRequest = router::popCurrentController,
)
} else {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
}
}
private fun shareCover() {
val activity = activity ?: return
viewScope.launchIO {
try {
val uri = presenter.saveCover(activity, temp = true) ?: return@launchIO
launchUI {
startActivity(uri.toShareIntent(activity))
}
} catch (e: Throwable) {
launchUI {
logcat(LogPriority.ERROR, e)
activity.toast(R.string.error_saving_cover)
}
true
}
menu?.findItem(R.id.action_edit_cover)?.isVisible = manga?.favorite ?: false
}
setImage(manga)
binding?.appbar?.applyInsetter {
type(navigationBars = true, statusBars = true) {
padding(left = true, top = true, right = true)
}
}
binding?.container?.onViewClicked = { dialog?.dismiss() }
binding?.container?.applyInsetter {
type(navigationBars = true) {
padding(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)
private fun saveCover() {
val activity = activity ?: return
viewScope.launchIO {
try {
presenter.saveCover(activity, temp = false)
launchUI {
activity.toast(R.string.cover_saved)
}
} catch (e: Throwable) {
launchUI {
logcat(LogPriority.ERROR, e)
activity.toast(R.string.error_saving_cover)
}
}
}
}
override fun onDetach(view: View) {
super.onDetach(view)
disposable?.dispose()
disposable = null
}
fun setImage(manga: Manga?) {
if (manga == null) return
val request = ImageRequest.Builder(applicationContext!!)
.data(manga)
.target {
binding?.container?.setImage(
it,
ReaderPageImageView.Config(
zoomDuration = 500,
private fun changeCover(action: EditCoverAction) {
when (action) {
EditCoverAction.EDIT -> {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "image/*"
}
startActivityForResult(
Intent.createChooser(
intent,
resources?.getString(R.string.file_select_cover),
),
REQUEST_IMAGE_OPEN,
)
}
.build()
EditCoverAction.DELETE -> presenter.deleteCustomCover()
}
}
disposable = applicationContext?.imageLoader?.enqueue(request)
private fun onSetCoverSuccess() {
activity?.toast(R.string.cover_updated)
}
private fun onSetCoverError(error: Throwable) {
activity?.toast(R.string.notification_cover_update_failed)
logcat(LogPriority.ERROR, error)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_IMAGE_OPEN) {
val dataUri = data?.data
if (dataUri == null || resultCode != Activity.RESULT_OK) return
val activity = activity ?: return
presenter.editCover(activity, dataUri)
}
}
class Presenter(
private val mangaId: Long,
private val getMangaById: GetMangaById = Injekt.get(),
) : nucleus.presenter.Presenter<MangaFullCoverDialog>() {
private var presenterScope: CoroutineScope = MainScope()
private val _mangaFlow = MutableStateFlow<Manga?>(null)
val manga = _mangaFlow.asStateFlow()
private val imageSaver by injectLazy<ImageSaver>()
private val coverCache by injectLazy<CoverCache>()
private val updateManga by injectLazy<UpdateManga>()
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
presenterScope.launchIO {
getMangaById.subscribe(mangaId)
.collect { _mangaFlow.value = it }
}
}
override fun onDestroy() {
super.onDestroy()
presenterScope.cancel()
}
/**
* Save manga cover Bitmap to picture or temporary share directory.
*
* @param context The context for building and executing the ImageRequest
* @return the uri to saved file
*/
suspend fun saveCover(context: Context, temp: Boolean): Uri? {
val manga = manga.value ?: return null
val req = ImageRequest.Builder(context)
.data(manga)
.size(Size.ORIGINAL)
.build()
val result = context.imageLoader.execute(req).drawable
// TODO: Handle animated cover
val bitmap = (result as? BitmapDrawable)?.bitmap ?: return null
return imageSaver.save(
Image.Cover(
bitmap = bitmap,
name = manga.title,
location = if (temp) Location.Cache else Location.Pictures.create(),
),
)
}
/**
* Update cover with local file.
*
* @param context Context.
* @param data uri of the cover resource.
*/
fun editCover(context: Context, data: Uri) {
val manga = manga.value ?: return
presenterScope.launchIO {
context.contentResolver.openInputStream(data)?.use {
val result = try {
manga.editCover(context, it, updateManga, coverCache)
} catch (e: Exception) {
view?.onSetCoverError(e)
false
}
launchUI { if (result) view?.onSetCoverSuccess() }
}
}
}
fun deleteCustomCover() {
val mangaId = manga.value?.id ?: return
presenterScope.launchIO {
try {
coverCache.deleteCustomCover(mangaId)
updateManga.awaitUpdateCoverLastModified(mangaId)
launchUI { view?.onSetCoverSuccess() }
} catch (e: Exception) {
launchUI { view?.onSetCoverError(e) }
}
}
}
}
companion object {
private const val MANGA_EXTRA = "mangaId"
/**
* Key to change the cover of a manga in [onActivityResult].
*/
private const val REQUEST_IMAGE_OPEN = 101
}
}

View File

@@ -6,7 +6,6 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -191,36 +190,9 @@ class MangaInfoHeaderAdapter(
}
.launchIn(controller.viewScope)
binding.mangaCover.longClicks()
.onEach {
showCoverOptionsDialog()
}
.launchIn(controller.viewScope)
setMangaInfo()
}
private fun showCoverOptionsDialog() {
val options = listOfNotNull(
R.string.action_share,
R.string.action_save,
// Can only edit cover for library manga
if (manga.favorite) R.string.action_edit else null,
).map(controller.activity!!::getString).toTypedArray()
MaterialAlertDialogBuilder(controller.activity!!)
.setTitle(R.string.manga_cover)
.setItems(options) { _, item ->
when (item) {
0 -> controller.shareCover()
1 -> controller.saveCover()
2 -> controller.changeCover()
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
/**
* Update the view with manga information.
*

View File

@@ -34,5 +34,18 @@ class TachiyomiChangeHandlerFrameLayout(
}
}
fun enableScrollingBehavior(enable: Boolean) {
(layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = if (enable) {
behavior.apply {
shouldHeaderOverlap = overlapHeader
}
} else null
if (!enable) {
// The behavior doesn't reset translationY when shouldHeaderOverlap is false
translationY = 0F
}
forceLayout()
}
override fun getBehavior() = TachiyomiScrollingViewBehavior()
}