This commit is contained in:
Koitharu
2024-04-11 08:10:26 +03:00
parent c98d7561b8
commit 59dd53c025
38 changed files with 1162 additions and 3783 deletions

View File

@@ -95,7 +95,7 @@
</intent-filter>
</activity>
<activity
android:name="org.koitharu.kotatsu.details.ui.DetailsActivity2"
android:name="org.koitharu.kotatsu.details.ui.DetailsActivity"
android:exported="true">
<intent-filter>
<action android:name="${applicationId}.action.VIEW_MANGA" />

View File

@@ -1,170 +0,0 @@
package org.koitharu.kotatsu.bookmarks.ui.sheet
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager
import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.plus
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetPagesBinding
import org.koitharu.kotatsu.list.ui.GridSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
import javax.inject.Inject
import kotlin.math.roundToInt
@Deprecated("")
@AndroidEntryPoint
class BookmarksSheet :
BaseAdaptiveSheet<SheetPagesBinding>(),
AdaptiveSheetCallback,
OnListItemClickListener<Bookmark> {
private val viewModel by viewModels<BookmarksSheetViewModel>()
@Inject
lateinit var coil: ImageLoader
@Inject
lateinit var settings: AppSettings
private var bookmarksAdapter: BookmarksAdapter? = null
private var spanResolver: GridSpanResolver? = null
private val spanSizeLookup = SpanSizeLookup()
private val listCommitCallback = Runnable {
spanSizeLookup.invalidateCache()
}
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetPagesBinding {
return SheetPagesBinding.inflate(inflater, container, false)
}
override fun onViewBindingCreated(binding: SheetPagesBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
addSheetCallback(this)
spanResolver = GridSpanResolver(binding.root.resources)
bookmarksAdapter = BookmarksAdapter(
coil = coil,
lifecycleOwner = viewLifecycleOwner,
clickListener = this@BookmarksSheet,
headerClickListener = null,
)
viewBinding?.headerBar?.setTitle(R.string.bookmarks)
with(binding.recyclerView) {
addItemDecoration(TypedListSpacingDecoration(context, false))
adapter = bookmarksAdapter
addOnLayoutChangeListener(spanResolver)
spanResolver?.setGridSize(settings.gridSize / 100f, this)
(layoutManager as GridLayoutManager).spanSizeLookup = spanSizeLookup
}
viewModel.content.observe(viewLifecycleOwner, ::onThumbnailsChanged)
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
}
override fun onDestroyView() {
spanResolver = null
bookmarksAdapter = null
spanSizeLookup.invalidateCache()
super.onDestroyView()
}
override fun onItemClick(item: Bookmark, view: View) {
val listener = (parentFragment as? OnPageSelectListener) ?: (activity as? OnPageSelectListener)
if (listener != null) {
listener.onPageSelected(ReaderPage(item.toMangaPage(), item.page, item.chapterId))
} else {
val intent = IntentBuilder(view.context)
.manga(viewModel.manga)
.bookmark(item)
.incognito(true)
.build()
startActivity(intent)
}
dismiss()
}
override fun onStateChanged(sheet: View, newState: Int) {
viewBinding?.recyclerView?.isFastScrollerEnabled = newState == AdaptiveSheetBehavior.STATE_EXPANDED
}
private fun onThumbnailsChanged(list: List<ListModel>) {
val adapter = bookmarksAdapter ?: return
if (adapter.itemCount == 0) {
var position = list.indexOfFirst { it is PageThumbnail && it.isCurrent }
if (position > 0) {
val spanCount = spanResolver?.spanCount ?: 0
val offset = if (position > spanCount + 1) {
(resources.getDimensionPixelSize(R.dimen.manga_list_details_item_height) * 0.6).roundToInt()
} else {
position = 0
0
}
val scrollCallback = RecyclerViewScrollCallback(requireViewBinding().recyclerView, position, offset)
adapter.setItems(list, listCommitCallback + scrollCallback)
} else {
adapter.setItems(list, listCommitCallback)
}
} else {
adapter.setItems(list, listCommitCallback)
}
}
private inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() {
init {
isSpanIndexCacheEnabled = true
isSpanGroupIndexCacheEnabled = true
}
override fun getSpanSize(position: Int): Int {
val total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1
return when (bookmarksAdapter?.getItemViewType(position)) {
ListItemType.PAGE_THUMB.ordinal -> 1
else -> total
}
}
fun invalidateCache() {
invalidateSpanGroupIndexCache()
invalidateSpanIndexCache()
}
}
companion object {
const val ARG_MANGA = "manga"
private const val TAG = "BookmarksSheet"
fun show(fm: FragmentManager, manga: Manga) {
BookmarksSheet().withArgs(1) {
putParcelable(ARG_MANGA, ParcelableManga(manga))
}.showDistinct(fm, TAG)
}
}
}

View File

@@ -1,56 +0,0 @@
package org.koitharu.kotatsu.bookmarks.ui.sheet
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.parsers.util.SuspendLazy
import javax.inject.Inject
@Deprecated("")
@HiltViewModel
class BookmarksSheetViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
mangaRepositoryFactory: MangaRepository.Factory,
bookmarksRepository: BookmarksRepository,
) : BaseViewModel() {
val manga = savedStateHandle.require<ParcelableManga>(BookmarksSheet.ARG_MANGA).manga
private val chaptersLazy = SuspendLazy {
requireNotNull(manga.chapters ?: mangaRepositoryFactory.create(manga.source).getDetails(manga).chapters)
}
val content: StateFlow<List<ListModel>> = bookmarksRepository.observeBookmarks(manga)
.map { mapList(it) }
.withErrorHandling()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, listOf(LoadingFooter()))
private suspend fun mapList(bookmarks: List<Bookmark>): List<ListModel> {
val chapters = chaptersLazy.get()
val bookmarksMap = bookmarks.groupBy { it.chapterId }
val result = ArrayList<ListModel>(bookmarks.size + bookmarksMap.size)
for (chapter in chapters) {
val b = bookmarksMap[chapter.id]
if (b.isNullOrEmpty()) {
continue
}
result += ListHeader(chapter.name)
result.addAll(b)
}
return result
}
}

View File

@@ -43,6 +43,7 @@ import org.koitharu.kotatsu.core.util.AcraScreenLogger
import org.koitharu.kotatsu.core.util.IncognitoModeIndicator
import org.koitharu.kotatsu.core.util.ext.connectivityManager
import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageFetcher
import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.local.data.CbzFetcher
import org.koitharu.kotatsu.local.data.LocalStorageChanges
@@ -50,7 +51,6 @@ import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.reader.ui.thumbnails.MangaPageFetcher
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
import org.koitharu.kotatsu.settings.backup.BackupObserver
import org.koitharu.kotatsu.sync.domain.SyncController

View File

@@ -1,92 +0,0 @@
package org.koitharu.kotatsu.details.ui
import android.transition.TransitionManager
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.Insets
import androidx.core.view.setMargins
import androidx.core.view.updateLayoutParams
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.util.ext.getThemeDimensionPixelSize
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.databinding.ItemTipBinding
import com.google.android.material.R as materialR
class ButtonTip(
private val root: ViewGroup,
private val insetsDelegate: WindowInsetsDelegate,
private val viewModel: DetailsViewModel,
) : View.OnClickListener, WindowInsetsDelegate.WindowInsetsListener {
private var selfBinding = ItemTipBinding.inflate(LayoutInflater.from(root.context), root, false)
private val actionBarSize = root.context.getThemeDimensionPixelSize(materialR.attr.actionBarSize)
init {
selfBinding.textView.setText(R.string.details_button_tip)
selfBinding.imageViewIcon.setImageResource(R.drawable.ic_tap)
selfBinding.root.id = R.id.layout_tip
selfBinding.buttonClose.setOnClickListener(this)
}
override fun onClick(v: View?) {
remove()
}
override fun onWindowInsetsChanged(insets: Insets) {
if (root is CoordinatorLayout) {
selfBinding.root.updateLayoutParams<CoordinatorLayout.LayoutParams> {
bottomMargin = topMargin + insets.bottom + insets.top + actionBarSize
}
}
}
fun addToRoot() {
val lp: ViewGroup.LayoutParams = when (root) {
is CoordinatorLayout -> CoordinatorLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
).apply {
// anchorId = R.id.layout_bottom
// anchorGravity = Gravity.TOP
gravity = Gravity.BOTTOM
setMargins(root.resources.getDimensionPixelOffset(R.dimen.margin_normal))
bottomMargin += actionBarSize
}
is ConstraintLayout -> ConstraintLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
).apply {
width = root.resources.getDimensionPixelSize(R.dimen.m3_side_sheet_width)
setMargins(root.resources.getDimensionPixelOffset(R.dimen.margin_normal))
}
else -> ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
root.addView(selfBinding.root, lp)
if (root is ConstraintLayout) {
val cs = ConstraintSet()
cs.clone(root)
cs.connect(R.id.layout_tip, ConstraintSet.TOP, R.id.appbar, ConstraintSet.BOTTOM)
cs.connect(R.id.layout_tip, ConstraintSet.START, R.id.card_chapters, ConstraintSet.START)
cs.connect(R.id.layout_tip, ConstraintSet.END, R.id.card_chapters, ConstraintSet.END)
cs.applyTo(root)
}
insetsDelegate.addInsetsListener(this)
}
fun remove() {
if (root.context.isAnimationsEnabled) {
TransitionManager.beginDelayedTransition(root)
}
insetsDelegate.removeInsetsListener(this)
root.removeView(selfBinding.root)
viewModel.onButtonTipClosed()
}
}

View File

@@ -1,97 +0,0 @@
package org.koitharu.kotatsu.details.ui
import android.view.InputDevice
import android.view.MotionEvent
import android.view.View
import android.view.View.OnLayoutChangeListener
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.view.ActionMode
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout
import org.koitharu.kotatsu.core.ui.util.ActionModeListener
import org.koitharu.kotatsu.core.util.ext.doOnExpansionsChanged
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
class ChaptersBottomSheetMediator(
private val behavior: BottomSheetBehavior<*>,
private val pager: ViewPager2,
private val tabLayout: TabLayout,
) : OnBackPressedCallback(false),
ActionModeListener,
OnLayoutChangeListener, View.OnGenericMotionListener {
private var lockCounter = 0
init {
behavior.doOnExpansionsChanged { isExpanded ->
isEnabled = isExpanded
if (!isExpanded) {
unlock()
}
}
}
override fun handleOnBackPressed() {
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
override fun onActionModeStarted(mode: ActionMode) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
lock()
}
override fun onActionModeFinished(mode: ActionMode) {
unlock()
}
override fun onLayoutChange(
v: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int,
) {
val height = bottom - top
if (height != behavior.peekHeight) {
behavior.peekHeight = height
}
}
override fun onGenericMotion(v: View?, event: MotionEvent): Boolean {
if (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {
if (event.actionMasked == MotionEvent.ACTION_SCROLL) {
if (event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0f) {
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
} else {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
return true
}
}
return false
}
fun lock() {
lockCounter++
updateLock()
}
fun unlock() {
lockCounter--
if (lockCounter < 0) {
lockCounter = 0
}
updateLock()
}
private fun updateLock() {
behavior.isDraggable = lockCounter <= 0
pager.isUserInputEnabled = lockCounter <= 0
tabLayout.setTabsEnabled(lockCounter <= 0)
}
}

View File

@@ -1,73 +0,0 @@
package org.koitharu.kotatsu.details.ui
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R
import java.lang.ref.WeakReference
class ChaptersMenuProvider(
private val viewModel: DetailsViewModel,
private val bottomSheetMediator: ChaptersBottomSheetMediator?,
) : OnBackPressedCallback(false), MenuProvider, SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener {
private var searchItemRef: WeakReference<MenuItem>? = null
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_chapters, menu)
val searchMenuItem = menu.findItem(R.id.action_search)
searchMenuItem.setOnActionExpandListener(this)
val searchView = searchMenuItem.actionView as SearchView
searchView.setOnQueryTextListener(this)
searchView.setIconifiedByDefault(false)
searchView.queryHint = searchMenuItem.title
searchItemRef = WeakReference(searchMenuItem)
}
override fun onPrepareMenu(menu: Menu) {
menu.findItem(R.id.action_search)?.isVisible = viewModel.isChaptersEmpty.value == false
menu.findItem(R.id.action_reversed)?.isChecked = viewModel.isChaptersReversed.value == true
menu.findItem(R.id.action_grid_view)?.isChecked = viewModel.isChaptersInGridView.value == true
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_reversed -> {
viewModel.setChaptersReversed(!menuItem.isChecked)
true
}
R.id.action_grid_view-> {
viewModel.setChaptersInGridView(!menuItem.isChecked)
true
}
else -> false
}
override fun handleOnBackPressed() {
searchItemRef?.get()?.collapseActionView()
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
bottomSheetMediator?.lock()
isEnabled = true
return true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
isEnabled = false
(item.actionView as? SearchView)?.setQuery("", false)
viewModel.performChapterSearch(null)
bottomSheetMediator?.unlock()
return true
}
override fun onQueryTextSubmit(query: String?): Boolean = false
override fun onQueryTextChange(newText: String?): Boolean {
viewModel.performChapterSearch(newText)
return true
}
}

View File

@@ -8,93 +8,118 @@ import android.text.style.DynamicDrawableSpan
import android.text.style.ForegroundColorSpan
import android.text.style.ImageSpan
import android.text.style.RelativeSizeSpan
import android.transition.AutoTransition
import android.transition.Slide
import android.transition.TransitionManager
import android.view.Gravity
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.ViewTreeObserver
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.view.MenuHost
import androidx.core.text.method.LinkMovementMethodCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.preference.PreferenceManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import coil.ImageLoader
import coil.request.ImageRequest
import coil.request.SuccessResult
import coil.transform.CircleCropTransformation
import coil.util.CoilUtils
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.filterNotNull
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.iconResId
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.image.ChipIconTarget
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.doOnExpansionsChanged
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ViewBadge
import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.core.util.ext.measureHeight
import org.koitharu.kotatsu.core.util.ext.menuView
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
import org.koitharu.kotatsu.core.util.ext.isTextTruncated
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.recyclerView
import org.koitharu.kotatsu.core.util.ext.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.core.util.ext.setNavigationIconSafe
import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.details.data.ReadingTime
import org.koitharu.kotatsu.details.service.MangaPrefetchService
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.details.ui.pager.DetailsPagerAdapter
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet
import org.koitharu.kotatsu.details.ui.related.RelatedMangaActivity
import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingItemDecoration
import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteSheet
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
import org.koitharu.kotatsu.local.ui.info.LocalInfoDialog
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.thumbnails.PagesThumbnailsSheet
import java.lang.ref.WeakReference
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
import javax.inject.Inject
import com.google.android.material.R as materialR
@Deprecated("")
@AndroidEntryPoint
class DetailsActivity :
BaseActivity<ActivityDetailsBinding>(),
View.OnClickListener,
NoModalBottomSheetOwner,
View.OnLongClickListener,
PopupMenu.OnMenuItemClickListener {
View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, View.OnLayoutChangeListener,
ViewTreeObserver.OnDrawListener, ChipsView.OnChipClickListener, OnListItemClickListener<Bookmark> {
@Inject
lateinit var appShortcutManager: AppShortcutManager
lateinit var shortcutManager: AppShortcutManager
@Inject
lateinit var settings: AppSettings
lateinit var coil: ImageLoader
private var buttonTip: WeakReference<ButtonTip>? = null
@Inject
lateinit var tagHighlighter: ListExtraProvider
private val viewModel: DetailsViewModel by viewModels()
val secondaryMenuHost: MenuHost
get() = viewBinding.toolbarChapters ?: this
var bottomSheetMediator: ChaptersBottomSheetMediator? = null
private set
private lateinit var chaptersBadge: ViewBadge
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -106,78 +131,157 @@ class DetailsActivity :
viewBinding.buttonRead.setOnClickListener(this)
viewBinding.buttonRead.setOnLongClickListener(this)
viewBinding.buttonRead.setOnContextClickListenerCompat(this)
viewBinding.buttonDropdown.setOnClickListener(this)
viewBinding.buttonChapters?.setOnClickListener(this)
viewBinding.infoLayout.chipBranch.setOnClickListener(this)
viewBinding.infoLayout.chipSize.setOnClickListener(this)
viewBinding.infoLayout.chipSource.setOnClickListener(this)
viewBinding.infoLayout.chipFavorite.setOnClickListener(this)
viewBinding.infoLayout.chipAuthor.setOnClickListener(this)
viewBinding.infoLayout.chipTime.setOnClickListener(this)
viewBinding.imageViewCover.setOnClickListener(this)
viewBinding.buttonDescriptionMore.setOnClickListener(this)
viewBinding.buttonScrobblingMore.setOnClickListener(this)
viewBinding.buttonRelatedMore.setOnClickListener(this)
viewBinding.infoLayout.chipSource.setOnClickListener(this)
viewBinding.infoLayout.chipSize.setOnClickListener(this)
viewBinding.textViewDescription.addOnLayoutChangeListener(this)
viewBinding.textViewDescription.viewTreeObserver.addOnDrawListener(this)
viewBinding.textViewDescription.movementMethod = LinkMovementMethodCompat.getInstance()
viewBinding.chipsTags.onChipClickListener = this
viewBinding.recyclerViewRelated.addItemDecoration(
SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)),
)
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
if (viewBinding.layoutBottom != null) {
val behavior = BottomSheetBehavior.from(checkNotNull(viewBinding.layoutBottom))
val bsMediator = ChaptersBottomSheetMediator(behavior, viewBinding.pager, viewBinding.tabs)
actionModeDelegate.addListener(bsMediator)
checkNotNull(viewBinding.layoutBsHeader).addOnLayoutChangeListener(bsMediator)
onBackPressedDispatcher.addCallback(bsMediator)
bottomSheetMediator = bsMediator
behavior.doOnExpansionsChanged(::onChaptersSheetStateChanged)
viewBinding.toolbarChapters?.setNavigationOnClickListener {
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
viewBinding.toolbarChapters?.setOnGenericMotionListener(bsMediator)
}
initPager()
chaptersBadge = ViewBadge(viewBinding.buttonChapters ?: viewBinding.buttonRead, this)
viewModel.manga.filterNotNull().observe(this, ::onMangaUpdated)
viewModel.details.filterNotNull().observe(this, ::onMangaUpdated)
viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved)
viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged)
viewModel.onError.observeEvent(this, DetailsErrorObserver(this, viewModel, exceptionResolver))
viewModel.onActionDone.observeEvent(
this,
ReversibleActionObserver(viewBinding.containerDetails, viewBinding.layoutBottom),
)
viewModel.onShowTip.observeEvent(this) { showTip() }
viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.scrollView, null))
viewModel.historyInfo.observe(this, ::onHistoryChanged)
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
viewModel.scrobblingInfo.observe(this, ::onScrobblingInfoChanged)
viewModel.localSize.observe(this, ::onLocalSizeChanged)
viewModel.relatedManga.observe(this, ::onRelatedMangaChanged)
// viewModel.chapters.observe(this, ::onChaptersChanged)
viewModel.readingTime.observe(this, ::onReadingTimeChanged)
viewModel.selectedBranch.observe(this) {
viewBinding.toolbarChapters?.subtitle = it
viewBinding.textViewSubtitle?.textAndVisible = it
viewBinding.infoLayout.chipBranch.text = it.ifNullOrEmpty { getString(R.string.system_default) }
}
val chaptersMenuInvalidator = MenuInvalidator(viewBinding.toolbarChapters ?: this)
viewModel.isChaptersReversed.observe(this, chaptersMenuInvalidator)
viewModel.isChaptersEmpty.observe(this, chaptersMenuInvalidator)
viewModel.favouriteCategories.observe(this, ::onFavoritesChanged)
val menuInvalidator = MenuInvalidator(this)
viewModel.favouriteCategories.observe(this, menuInvalidator)
viewModel.isStatsAvailable.observe(this, menuInvalidator)
viewModel.remoteManga.observe(this, menuInvalidator)
viewModel.branches.observe(this) {
viewBinding.buttonDropdown.isVisible = it.size > 1
viewBinding.infoLayout.chipBranch.isVisible = it.size > 1
}
viewModel.chapters.observe(this, PrefetchObserver(this))
viewModel.onDownloadStarted.observeEvent(
this,
DownloadStartedObserver(viewBinding.containerDetails),
DownloadStartedObserver(viewBinding.scrollView),
)
addMenuProvider(
DetailsMenuProvider(
activity = this,
viewModel = viewModel,
snackbarHost = viewBinding.pager,
appShortcutManager = appShortcutManager,
snackbarHost = viewBinding.scrollView,
appShortcutManager = shortcutManager,
),
)
}
override fun getBottomSheetCollapsedHeight(): Int {
return viewBinding.layoutBsHeader?.measureHeight() ?: 0
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_read -> openReader(isIncognitoMode = false)
R.id.button_dropdown -> showBranchPopupMenu(v)
R.id.chip_branch -> showBranchPopupMenu(v)
R.id.button_chapters -> {
ChaptersPagesSheet.show(supportFragmentManager)
}
R.id.chip_author -> {
val manga = viewModel.manga.value ?: return
startActivity(
SearchActivity.newIntent(
context = v.context,
source = manga.source,
query = manga.author ?: return,
),
)
}
R.id.chip_source -> {
val manga = viewModel.manga.value ?: return
startActivity(
MangaListActivity.newIntent(
context = v.context,
source = manga.source,
),
)
}
R.id.chip_size -> {
val manga = viewModel.manga.value ?: return
LocalInfoDialog.show(supportFragmentManager, manga)
}
R.id.chip_favorite -> {
val manga = viewModel.manga.value ?: return
FavoriteSheet.show(supportFragmentManager, manga)
}
R.id.chip_time -> {
if (viewModel.isStatsAvailable.value) {
val manga = viewModel.manga.value ?: return
MangaStatsSheet.show(supportFragmentManager, manga)
} else {
// TODO
}
}
R.id.imageView_cover -> {
val manga = viewModel.manga.value ?: return
startActivity(
ImageActivity.newIntent(
v.context,
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
manga.source,
),
scaleUpActivityOptionsOf(v),
)
}
R.id.button_description_more -> {
val tv = viewBinding.textViewDescription
TransitionManager.beginDelayedTransition(tv.parentView)
if (tv.maxLines in 1 until Integer.MAX_VALUE) {
tv.maxLines = Integer.MAX_VALUE
} else {
tv.maxLines = resources.getInteger(R.integer.details_description_lines)
}
}
R.id.button_scrobbling_more -> {
val manga = viewModel.manga.value ?: return
ScrobblingSelectorSheet.show(supportFragmentManager, manga, null)
}
R.id.button_related_more -> {
val manga = viewModel.manga.value ?: return
startActivity(RelatedMangaActivity.newIntent(v.context, manga))
}
}
}
override fun onChipClick(chip: Chip, data: Any?) {
val tag = data as? MangaTag ?: return
startActivity(MangaListActivity.newIntent(this, setOf(tag)))
}
override fun onLongClick(v: View): Boolean = when (v.id) {
R.id.button_read -> {
buttonTip?.get()?.remove()
buttonTip = null
val menu = PopupMenu(v.context, v)
menu.inflate(R.menu.popup_read)
menu.menu.findItem(R.id.action_forget)?.isVisible = viewModel.historyInfo.value.run {
@@ -204,46 +308,203 @@ class DetailsActivity :
true
}
R.id.action_pages_thumbs -> {
val history = viewModel.historyInfo.value.history
PagesThumbnailsSheet.show(
fm = supportFragmentManager,
manga = viewModel.manga.value ?: return false,
chapterId = history?.chapterId
?: viewModel.chapters.value.firstOrNull()?.chapter?.id
?: return false,
currentPage = history?.page ?: 0,
)
true
}
else -> false
}
}
private fun onChaptersSheetStateChanged(isExpanded: Boolean) {
val toolbar = viewBinding.toolbarChapters ?: return
if (isAnimationsEnabled) {
val transition = AutoTransition()
transition.duration = getAnimationDuration(R.integer.config_shorterAnimTime)
TransitionManager.beginDelayedTransition(toolbar, transition)
}
if (isExpanded) {
toolbar.setNavigationIconSafe(materialR.drawable.abc_ic_clear_material)
} else {
toolbar.navigationIcon = null
}
toolbar.menuView?.isVisible = isExpanded
viewBinding.buttonRead.isGone = isExpanded
override fun onItemClick(item: Bookmark, view: View) {
startActivity(
IntentBuilder(view.context).bookmark(item).incognito(true).build(),
)
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
}
private fun onMangaUpdated(manga: Manga) {
title = manga.title
val hasChapters = !manga.chapters.isNullOrEmpty()
viewBinding.buttonRead.isEnabled = hasChapters
invalidateOptionsMenu()
showBottomSheet(manga.chapters != null)
viewBinding.groupHeader?.isVisible = hasChapters
override fun onDraw() {
viewBinding.run {
buttonDescriptionMore.isVisible = textViewDescription.maxLines == Int.MAX_VALUE ||
textViewDescription.isTextTruncated
}
}
override fun onLayoutChange(
v: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
with(viewBinding) {
buttonDescriptionMore.isVisible = textViewDescription.isTextTruncated
}
}
private fun onChaptersChanged(chapters: List<ChapterListItem>?) {
// TODO
}
private fun onFavoritesChanged(categories: Set<FavouriteCategory>) {
val chip = viewBinding.infoLayout.chipFavorite
chip.setChipIconResource(if (categories.isEmpty()) R.drawable.ic_heart_outline else R.drawable.ic_heart)
chip.text = if (categories.isEmpty()) {
getString(R.string.add_to_favourites)
} else {
if (categories.size == 1) {
categories.first().title.ellipsize(FAV_LABEL_LIMIT)
}
buildString(FAV_LABEL_LIMIT + 6) {
for ((i, cat) in categories.withIndex()) {
if (i == 0) {
append(cat.title.ellipsize(FAV_LABEL_LIMIT - 4))
} else if (length + cat.title.length > FAV_LABEL_LIMIT) {
append(", ")
append(getString(R.string.list_ellipsize_pattern, categories.size - i))
break
} else {
append(", ")
append(cat.title)
}
}
}
}
}
private fun onReadingTimeChanged(time: ReadingTime?) {
val chip = viewBinding.infoLayout.chipTime
chip.textAndVisible = time?.formatShort(chip.resources)
}
private fun onDescriptionChanged(description: CharSequence?) {
val tv = viewBinding.textViewDescription
if (description.isNullOrBlank()) {
tv.setText(R.string.no_description)
} else {
tv.text = description
}
}
private fun onLocalSizeChanged(size: Long) {
val chip = viewBinding.infoLayout.chipSize
if (size == 0L) {
chip.isVisible = false
} else {
chip.text = FileSize.BYTES.format(chip.context, size)
chip.isVisible = true
}
}
private fun onRelatedMangaChanged(related: List<MangaItemModel>) {
if (related.isEmpty()) {
viewBinding.groupRelated.isVisible = false
return
}
val rv = viewBinding.recyclerViewRelated
@Suppress("UNCHECKED_CAST")
val adapter = (rv.adapter as? BaseListAdapter<ListModel>) ?: BaseListAdapter<ListModel>()
.addDelegate(
ListItemType.MANGA_GRID,
mangaGridItemAD(
coil, this,
StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)),
) { item, view ->
startActivity(newIntent(view.context, item))
},
).also { rv.adapter = it }
adapter.items = related
viewBinding.groupRelated.isVisible = true
}
private fun onLoadingStateChanged(isLoading: Boolean) {
val button = viewBinding.buttonChapters ?: return
if (isLoading) {
button.setImageDrawable(
CircularProgressDrawable(this).also {
it.setStyle(CircularProgressDrawable.LARGE)
it.setColorSchemeColors(getThemeColor(materialR.attr.colorControlNormal))
it.start()
},
)
} else {
button.setImageResource(R.drawable.ic_list_sheet)
}
}
private fun onScrobblingInfoChanged(scrobblings: List<ScrobblingInfo>) {
var adapter = viewBinding.recyclerViewScrobbling.adapter as? ScrollingInfoAdapter
viewBinding.groupScrobbling.isGone = scrobblings.isEmpty()
if (adapter != null) {
adapter.items = scrobblings
} else {
adapter = ScrollingInfoAdapter(this, coil, supportFragmentManager)
adapter.items = scrobblings
viewBinding.recyclerViewScrobbling.adapter = adapter
viewBinding.recyclerViewScrobbling.addItemDecoration(ScrobblingItemDecoration())
}
}
private fun onMangaUpdated(details: MangaDetails) {
with(viewBinding) {
val manga = details.toManga()
val hasChapters = !manga.chapters.isNullOrEmpty()
// Main
loadCover(manga)
textViewTitle.text = manga.title
textViewSubtitle.textAndVisible = manga.altTitle
infoLayout.chipAuthor.textAndVisible = manga.author
if (manga.hasRating) {
ratingBar.rating = manga.rating * ratingBar.numStars
ratingBar.isVisible = true
} else {
ratingBar.isVisible = false
}
manga.state?.let { state ->
textViewState.textAndVisible = resources.getString(state.titleResId)
imageViewState.setImageResource(state.iconResId)
} ?: run {
textViewState.isVisible = false
imageViewState.isVisible = false
}
if (manga.source == MangaSource.LOCAL || manga.source == MangaSource.DUMMY) {
infoLayout.chipSource.isVisible = false
} else {
infoLayout.chipSource.text = manga.source.title
infoLayout.chipSource.isVisible = true
}
textViewNsfw.isVisible = manga.isNsfw
// Chips
bindTags(manga)
textViewDescription.text = details.description.ifNullOrEmpty { getString(R.string.no_description) }
viewBinding.infoLayout.chipSource.also { chip ->
ImageRequest.Builder(this@DetailsActivity)
.data(manga.source.faviconUri())
.lifecycle(this@DetailsActivity)
.crossfade(false)
.size(resources.getDimensionPixelSize(materialR.dimen.m3_chip_icon_size))
.target(ChipIconTarget(chip))
.placeholder(R.drawable.ic_web)
.fallback(R.drawable.ic_web)
.error(R.drawable.ic_web)
.source(manga.source)
.transformations(CircleCropTransformation())
.allowRgb565(true)
.enqueueWith(coil)
}
buttonChapters?.isEnabled = hasChapters
title = manga.title
buttonRead.isEnabled = hasChapters
invalidateOptionsMenu()
}
}
private fun onMangaRemoved(manga: Manga) {
@@ -260,32 +521,25 @@ class DetailsActivity :
left = insets.left,
right = insets.right,
)
if (insets.bottom > 0) {
window.setNavigationBarTransparentCompat(
this,
viewBinding.layoutBottom?.elevation ?: 0f,
0.9f,
)
}
viewBinding.cardChapters?.updateLayoutParams<MarginLayoutParams> {
bottomMargin = insets.bottom + marginEnd
}
viewBinding.dragHandle?.updateLayoutParams<MarginLayoutParams> {
bottomMargin = insets.top
val baseOffset = leftMargin
bottomMargin = insets.bottom + baseOffset
topMargin = insets.bottom + baseOffset
}
viewBinding.scrollView.updatePadding(
bottom = insets.bottom,
)
}
private fun onHistoryChanged(info: HistoryInfo) {
with(viewBinding.buttonRead) {
if (info.history != null) {
setText(R.string._continue)
setIconResource(if (info.isIncognitoMode) R.drawable.ic_incognito else R.drawable.ic_play)
setTitle(R.string._continue)
} else {
setText(R.string.read)
setIconResource(if (info.isIncognitoMode) R.drawable.ic_incognito else R.drawable.ic_play)
setTitle(R.string.read)
}
}
val text = when {
viewBinding.buttonRead.subtitle = when {
!info.isValid -> getString(R.string.loading_)
info.currentChapter >= 0 -> getString(
R.string.chapter_d_of_d,
@@ -300,20 +554,11 @@ class DetailsActivity :
info.totalChapters,
)
}
viewBinding.toolbarChapters?.title = text
viewBinding.textViewTitle?.text = text
viewBinding.buttonRead.setProgress(info.history?.percent?.coerceIn(0f, 1f) ?: 0f, true)
}
private fun onNewChaptersChanged(count: Int) {
val tab = viewBinding.tabs.getTabAt(0) ?: return
if (count == 0) {
tab.removeBadge()
} else {
val badge = tab.orCreateBadge
badge.horizontalOffsetWithText = -resources.getDimensionPixelOffset(R.dimen.margin_small)
badge.number = count
badge.isVisible = true
}
chaptersBadge.counter = count
}
private fun showBranchPopupMenu(v: View) {
@@ -348,8 +593,11 @@ class DetailsActivity :
append(branch.count.toString())
}
}
menu.menu.add(Menu.NONE, Menu.NONE, i, title)
val item = menu.menu.add(R.id.group_branches, Menu.NONE, i, title)
item.isCheckable = true
item.isChecked = branch.isSelected
}
menu.menu.setGroupCheckable(R.id.group_branches, true, true)
menu.setOnMenuItemClickListener {
viewModel.setSelectedBranch(branches.getOrNull(it.order)?.name)
true
@@ -361,7 +609,7 @@ class DetailsActivity :
val manga = viewModel.manga.value ?: return
val chapterId = viewModel.historyInfo.value.history?.chapterId
if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) {
Snackbar.make(viewBinding.containerDetails, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)
Snackbar.make(viewBinding.scrollView, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)
.show()
} else {
startActivity(
@@ -377,24 +625,47 @@ class DetailsActivity :
}
}
private fun initPager() {
val adapter = DetailsPagerAdapter(this, settings)
viewBinding.pager.recyclerView?.isNestedScrollingEnabled = false
viewBinding.pager.offscreenPageLimit = 1
viewBinding.pager.adapter = adapter
TabLayoutMediator(viewBinding.tabs, viewBinding.pager, adapter).attach()
viewBinding.pager.setCurrentItem(settings.defaultDetailsTab, false)
viewBinding.tabs.isVisible = adapter.itemCount > 1
private fun bindTags(manga: Manga) {
viewBinding.chipsTags.isVisible = manga.tags.isNotEmpty()
viewBinding.chipsTags.setChips(
manga.tags.map { tag ->
ChipsView.ChipModel(
title = tag.title,
tint = tagHighlighter.getTagTint(tag),
icon = 0,
data = tag,
isCheckable = false,
isChecked = false,
)
},
)
}
private fun showBottomSheet(isVisible: Boolean) {
val view = viewBinding.layoutBottom ?: return
if (view.isVisible == isVisible) return
val transition = Slide(Gravity.BOTTOM)
transition.addTarget(view)
transition.interpolator = AccelerateDecelerateInterpolator()
TransitionManager.beginDelayedTransition(viewBinding.root as ViewGroup, transition)
view.isVisible = isVisible
private fun loadCover(manga: Manga) {
val imageUrl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }
val lastResult = CoilUtils.result(viewBinding.imageViewCover)
if (lastResult is SuccessResult && lastResult.request.data == imageUrl) {
return
}
val request = ImageRequest.Builder(this)
.target(viewBinding.imageViewCover)
.size(CoverSizeResolver(viewBinding.imageViewCover))
.data(imageUrl)
.tag(manga.source)
.crossfade(this)
.lifecycle(this)
.placeholderMemoryCacheKey(manga.coverUrl)
val previousDrawable = lastResult?.drawable
if (previousDrawable != null) {
request.fallback(previousDrawable)
.placeholder(previousDrawable)
.error(previousDrawable)
} else {
request.fallback(R.drawable.ic_placeholder)
.placeholder(R.drawable.ic_placeholder)
.error(R.drawable.ic_error_placeholder)
}
request.enqueueWith(coil)
}
private class PrefetchObserver(
@@ -415,34 +686,18 @@ class DetailsActivity :
}
}
private fun showTip() {
val tip = ButtonTip(viewBinding.root as ViewGroup, insetsDelegate, viewModel)
tip.addToRoot()
buttonTip = WeakReference(tip)
}
companion object {
const val TIP_BUTTON = "btn_read"
private const val KEY_NEW_ACTIVITY = "new_details_screen"
private const val FAV_LABEL_LIMIT = 10
fun newIntent(context: Context, manga: Manga): Intent {
return getActivityIntent(context)
return Intent(context, DetailsActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga))
}
fun newIntent(context: Context, mangaId: Long): Intent {
return getActivityIntent(context)
return Intent(context, DetailsActivity::class.java)
.putExtra(MangaIntent.KEY_ID, mangaId)
}
private fun getActivityIntent(context: Context): Intent {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val useNewActivity = prefs.getBoolean(KEY_NEW_ACTIVITY, true)
return Intent(
context,
if (useNewActivity) DetailsActivity2::class.java else DetailsActivity::class.java,
)
}
}
}

View File

@@ -1,712 +0,0 @@
package org.koitharu.kotatsu.details.ui
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.text.style.DynamicDrawableSpan
import android.text.style.ForegroundColorSpan
import android.text.style.ImageSpan
import android.text.style.RelativeSizeSpan
import android.transition.TransitionManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewTreeObserver
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.text.method.LinkMovementMethodCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import coil.ImageLoader
import coil.request.ImageRequest
import coil.request.SuccessResult
import coil.transform.CircleCropTransformation
import coil.util.CoilUtils
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.filterNotNull
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.iconResId
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.image.ChipIconTarget
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ViewBadge
import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
import org.koitharu.kotatsu.core.util.ext.isTextTruncated
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ActivityDetailsNewBinding
import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.details.data.ReadingTime
import org.koitharu.kotatsu.details.service.MangaPrefetchService
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet
import org.koitharu.kotatsu.details.ui.related.RelatedMangaActivity
import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingItemDecoration
import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteSheet
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
import org.koitharu.kotatsu.local.ui.info.LocalInfoDialog
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint
class DetailsActivity2 :
BaseActivity<ActivityDetailsNewBinding>(),
View.OnClickListener,
View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, View.OnLayoutChangeListener,
ViewTreeObserver.OnDrawListener, ChipsView.OnChipClickListener, OnListItemClickListener<Bookmark> {
@Inject
lateinit var shortcutManager: AppShortcutManager
@Inject
lateinit var coil: ImageLoader
@Inject
lateinit var tagHighlighter: ListExtraProvider
private val viewModel: DetailsViewModel by viewModels()
var bottomSheetMediator: ChaptersBottomSheetMediator? = null
private set
private lateinit var chaptersBadge: ViewBadge
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityDetailsNewBinding.inflate(layoutInflater))
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setDisplayShowTitleEnabled(false)
}
viewBinding.buttonRead.setOnClickListener(this)
viewBinding.buttonRead.setOnLongClickListener(this)
viewBinding.buttonRead.setOnContextClickListenerCompat(this)
viewBinding.buttonChapters?.setOnClickListener(this)
viewBinding.infoLayout.chipBranch.setOnClickListener(this)
viewBinding.infoLayout.chipSize.setOnClickListener(this)
viewBinding.infoLayout.chipSource.setOnClickListener(this)
viewBinding.infoLayout.chipFavorite.setOnClickListener(this)
viewBinding.infoLayout.chipAuthor.setOnClickListener(this)
viewBinding.infoLayout.chipTime.setOnClickListener(this)
viewBinding.imageViewCover.setOnClickListener(this)
viewBinding.buttonDescriptionMore.setOnClickListener(this)
viewBinding.buttonScrobblingMore.setOnClickListener(this)
viewBinding.buttonRelatedMore.setOnClickListener(this)
viewBinding.infoLayout.chipSource.setOnClickListener(this)
viewBinding.infoLayout.chipSize.setOnClickListener(this)
viewBinding.textViewDescription.addOnLayoutChangeListener(this)
viewBinding.textViewDescription.viewTreeObserver.addOnDrawListener(this)
viewBinding.textViewDescription.movementMethod = LinkMovementMethodCompat.getInstance()
viewBinding.chipsTags.onChipClickListener = this
viewBinding.recyclerViewRelated.addItemDecoration(
SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)),
)
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
chaptersBadge = ViewBadge(viewBinding.buttonChapters ?: viewBinding.buttonRead, this)
viewModel.details.filterNotNull().observe(this, ::onMangaUpdated)
viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved)
viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged)
viewModel.onError.observeEvent(
this,
SnackbarErrorObserver(viewBinding.scrollView, null, exceptionResolver) {
if (it) viewModel.reload()
},
)
viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.scrollView, null))
viewModel.historyInfo.observe(this, ::onHistoryChanged)
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
viewModel.scrobblingInfo.observe(this, ::onScrobblingInfoChanged)
viewModel.localSize.observe(this, ::onLocalSizeChanged)
viewModel.relatedManga.observe(this, ::onRelatedMangaChanged)
// viewModel.chapters.observe(this, ::onChaptersChanged)
viewModel.readingTime.observe(this, ::onReadingTimeChanged)
viewModel.selectedBranch.observe(this) {
viewBinding.infoLayout.chipBranch.text = it.ifNullOrEmpty { getString(R.string.system_default) }
}
viewModel.favouriteCategories.observe(this, ::onFavoritesChanged)
val menuInvalidator = MenuInvalidator(this)
viewModel.isStatsAvailable.observe(this, menuInvalidator)
viewModel.remoteManga.observe(this, menuInvalidator)
viewModel.branches.observe(this) {
viewBinding.infoLayout.chipBranch.isVisible = it.size > 1
}
viewModel.chapters.observe(this, PrefetchObserver(this))
viewModel.onDownloadStarted.observeEvent(
this,
DownloadStartedObserver(viewBinding.scrollView),
)
addMenuProvider(
DetailsMenuProvider(
activity = this,
viewModel = viewModel,
snackbarHost = viewBinding.scrollView,
appShortcutManager = shortcutManager,
),
)
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_read -> openReader(isIncognitoMode = false)
R.id.chip_branch -> showBranchPopupMenu(v)
R.id.button_chapters -> {
ChaptersPagesSheet.show(supportFragmentManager)
}
R.id.chip_author -> {
val manga = viewModel.manga.value ?: return
startActivity(
SearchActivity.newIntent(
context = v.context,
source = manga.source,
query = manga.author ?: return,
),
)
}
R.id.chip_source -> {
val manga = viewModel.manga.value ?: return
startActivity(
MangaListActivity.newIntent(
context = v.context,
source = manga.source,
),
)
}
R.id.chip_size -> {
val manga = viewModel.manga.value ?: return
LocalInfoDialog.show(supportFragmentManager, manga)
}
R.id.chip_favorite -> {
val manga = viewModel.manga.value ?: return
FavoriteSheet.show(supportFragmentManager, manga)
}
R.id.chip_time -> {
if (viewModel.isStatsAvailable.value) {
val manga = viewModel.manga.value ?: return
MangaStatsSheet.show(supportFragmentManager, manga)
} else {
// TODO
}
}
R.id.imageView_cover -> {
val manga = viewModel.manga.value ?: return
startActivity(
ImageActivity.newIntent(
v.context,
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
manga.source,
),
scaleUpActivityOptionsOf(v),
)
}
R.id.button_description_more -> {
val tv = viewBinding.textViewDescription
TransitionManager.beginDelayedTransition(tv.parentView)
if (tv.maxLines in 1 until Integer.MAX_VALUE) {
tv.maxLines = Integer.MAX_VALUE
} else {
tv.maxLines = resources.getInteger(R.integer.details_description_lines)
}
}
R.id.button_scrobbling_more -> {
val manga = viewModel.manga.value ?: return
ScrobblingSelectorSheet.show(supportFragmentManager, manga, null)
}
R.id.button_related_more -> {
val manga = viewModel.manga.value ?: return
startActivity(RelatedMangaActivity.newIntent(v.context, manga))
}
}
}
override fun onChipClick(chip: Chip, data: Any?) {
val tag = data as? MangaTag ?: return
startActivity(MangaListActivity.newIntent(this, setOf(tag)))
}
override fun onLongClick(v: View): Boolean = when (v.id) {
R.id.button_read -> {
val menu = PopupMenu(v.context, v)
menu.inflate(R.menu.popup_read)
menu.menu.findItem(R.id.action_forget)?.isVisible = viewModel.historyInfo.value.run {
!isIncognitoMode && history != null
}
menu.setOnMenuItemClickListener(this)
menu.setForceShowIcon(true)
menu.show()
true
}
else -> false
}
override fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_incognito -> {
openReader(isIncognitoMode = true)
true
}
R.id.action_forget -> {
viewModel.removeFromHistory()
true
}
else -> false
}
}
override fun onItemClick(item: Bookmark, view: View) {
startActivity(
IntentBuilder(view.context).bookmark(item).incognito(true).build(),
)
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
}
override fun onDraw() {
viewBinding.run {
buttonDescriptionMore.isVisible = textViewDescription.maxLines == Int.MAX_VALUE ||
textViewDescription.isTextTruncated
}
}
override fun onLayoutChange(
v: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
with(viewBinding) {
buttonDescriptionMore.isVisible = textViewDescription.isTextTruncated
}
}
private fun onChaptersChanged(chapters: List<ChapterListItem>?) {
// TODO
}
private fun onFavoritesChanged(categories: Set<FavouriteCategory>) {
val chip = viewBinding.infoLayout.chipFavorite
chip.setChipIconResource(if (categories.isEmpty()) R.drawable.ic_heart_outline else R.drawable.ic_heart)
chip.text = if (categories.isEmpty()) {
getString(R.string.add_to_favourites)
} else {
if (categories.size == 1) {
categories.first().title.ellipsize(FAV_LABEL_LIMIT)
}
buildString(FAV_LABEL_LIMIT + 6) {
for ((i, cat) in categories.withIndex()) {
if (i == 0) {
append(cat.title.ellipsize(FAV_LABEL_LIMIT - 4))
} else if (length + cat.title.length > FAV_LABEL_LIMIT) {
append(", ")
append(getString(R.string.list_ellipsize_pattern, categories.size - i))
break
} else {
append(", ")
append(cat.title)
}
}
}
}
}
private fun onReadingTimeChanged(time: ReadingTime?) {
val chip = viewBinding.infoLayout.chipTime
chip.textAndVisible = time?.formatShort(chip.resources)
}
private fun onDescriptionChanged(description: CharSequence?) {
val tv = viewBinding.textViewDescription
if (description.isNullOrBlank()) {
tv.setText(R.string.no_description)
} else {
tv.text = description
}
}
private fun onLocalSizeChanged(size: Long) {
val chip = viewBinding.infoLayout.chipSize
if (size == 0L) {
chip.isVisible = false
} else {
chip.text = FileSize.BYTES.format(chip.context, size)
chip.isVisible = true
}
}
private fun onRelatedMangaChanged(related: List<MangaItemModel>) {
if (related.isEmpty()) {
viewBinding.groupRelated.isVisible = false
return
}
val rv = viewBinding.recyclerViewRelated
@Suppress("UNCHECKED_CAST")
val adapter = (rv.adapter as? BaseListAdapter<ListModel>) ?: BaseListAdapter<ListModel>()
.addDelegate(
ListItemType.MANGA_GRID,
mangaGridItemAD(
coil, this,
StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)),
) { item, view ->
startActivity(DetailsActivity.newIntent(view.context, item))
},
).also { rv.adapter = it }
adapter.items = related
viewBinding.groupRelated.isVisible = true
}
private fun onLoadingStateChanged(isLoading: Boolean) {
val button = viewBinding.buttonChapters ?: return
if (isLoading) {
button.setImageDrawable(
CircularProgressDrawable(this).also {
it.setStyle(CircularProgressDrawable.LARGE)
it.setColorSchemeColors(getThemeColor(materialR.attr.colorControlNormal))
it.start()
},
)
} else {
button.setImageResource(R.drawable.ic_list_sheet)
}
}
private fun onScrobblingInfoChanged(scrobblings: List<ScrobblingInfo>) {
var adapter = viewBinding.recyclerViewScrobbling.adapter as? ScrollingInfoAdapter
viewBinding.groupScrobbling.isGone = scrobblings.isEmpty()
if (adapter != null) {
adapter.items = scrobblings
} else {
adapter = ScrollingInfoAdapter(this, coil, supportFragmentManager)
adapter.items = scrobblings
viewBinding.recyclerViewScrobbling.adapter = adapter
viewBinding.recyclerViewScrobbling.addItemDecoration(ScrobblingItemDecoration())
}
}
private fun onMangaUpdated(details: MangaDetails) {
with(viewBinding) {
val manga = details.toManga()
val hasChapters = !manga.chapters.isNullOrEmpty()
// Main
loadCover(manga)
textViewTitle.text = manga.title
textViewSubtitle.textAndVisible = manga.altTitle
infoLayout.chipAuthor.textAndVisible = manga.author
if (manga.hasRating) {
ratingBar.rating = manga.rating * ratingBar.numStars
ratingBar.isVisible = true
} else {
ratingBar.isVisible = false
}
manga.state?.let { state ->
textViewState.textAndVisible = resources.getString(state.titleResId)
imageViewState.setImageResource(state.iconResId)
} ?: run {
textViewState.isVisible = false
imageViewState.isVisible = false
}
if (manga.source == MangaSource.LOCAL || manga.source == MangaSource.DUMMY) {
infoLayout.chipSource.isVisible = false
} else {
infoLayout.chipSource.text = manga.source.title
infoLayout.chipSource.isVisible = true
}
textViewNsfw.isVisible = manga.isNsfw
// Chips
bindTags(manga)
textViewDescription.text = details.description.ifNullOrEmpty { getString(R.string.no_description) }
viewBinding.infoLayout.chipSource.also { chip ->
ImageRequest.Builder(this@DetailsActivity2)
.data(manga.source.faviconUri())
.lifecycle(this@DetailsActivity2)
.crossfade(false)
.size(resources.getDimensionPixelSize(materialR.dimen.m3_chip_icon_size))
.target(ChipIconTarget(chip))
.placeholder(R.drawable.ic_web)
.fallback(R.drawable.ic_web)
.error(R.drawable.ic_web)
.source(manga.source)
.transformations(CircleCropTransformation())
.allowRgb565(true)
.enqueueWith(coil)
}
buttonChapters?.isEnabled = hasChapters
title = manga.title
buttonRead.isEnabled = hasChapters
invalidateOptionsMenu()
}
}
private fun onMangaRemoved(manga: Manga) {
Toast.makeText(
this,
getString(R.string._s_deleted_from_local_storage, manga.title),
Toast.LENGTH_SHORT,
).show()
finishAfterTransition()
}
override fun onWindowInsetsChanged(insets: Insets) {
viewBinding.root.updatePadding(
left = insets.left,
right = insets.right,
)
viewBinding.cardChapters?.updateLayoutParams<MarginLayoutParams> {
val baseOffset = leftMargin
bottomMargin = insets.bottom + baseOffset
topMargin = insets.bottom + baseOffset
}
viewBinding.scrollView.updatePadding(
bottom = insets.bottom,
)
}
private fun onHistoryChanged(info: HistoryInfo) {
with(viewBinding.buttonRead) {
if (info.history != null) {
setTitle(R.string._continue)
} else {
setTitle(R.string.read)
}
}
viewBinding.buttonRead.subtitle = when {
!info.isValid -> getString(R.string.loading_)
info.currentChapter >= 0 -> getString(
R.string.chapter_d_of_d,
info.currentChapter + 1,
info.totalChapters,
)
info.totalChapters == 0 -> getString(R.string.no_chapters)
else -> resources.getQuantityString(
R.plurals.chapters,
info.totalChapters,
info.totalChapters,
)
}
viewBinding.buttonRead.setProgress(info.history?.percent?.coerceIn(0f, 1f) ?: 0f, true)
}
private fun onNewChaptersChanged(count: Int) {
chaptersBadge.counter = count
}
private fun showBranchPopupMenu(v: View) {
val menu = PopupMenu(v.context, v)
val branches = viewModel.branches.value
for ((i, branch) in branches.withIndex()) {
val title = buildSpannedString {
if (branch.isCurrent) {
inSpans(
ImageSpan(
this@DetailsActivity2,
R.drawable.ic_current_chapter,
DynamicDrawableSpan.ALIGN_BASELINE,
),
) {
append(' ')
}
append(' ')
}
append(branch.name ?: getString(R.string.system_default))
append(' ')
append(' ')
inSpans(
ForegroundColorSpan(
v.context.getThemeColor(
android.R.attr.textColorSecondary,
Color.LTGRAY,
),
),
RelativeSizeSpan(0.74f),
) {
append(branch.count.toString())
}
}
val item = menu.menu.add(R.id.group_branches, Menu.NONE, i, title)
item.isCheckable = true
item.isChecked = branch.isSelected
}
menu.menu.setGroupCheckable(R.id.group_branches, true, true)
menu.setOnMenuItemClickListener {
viewModel.setSelectedBranch(branches.getOrNull(it.order)?.name)
true
}
menu.show()
}
private fun openReader(isIncognitoMode: Boolean) {
val manga = viewModel.manga.value ?: return
val chapterId = viewModel.historyInfo.value.history?.chapterId
if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) {
Snackbar.make(viewBinding.scrollView, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)
.show()
} else {
startActivity(
IntentBuilder(this)
.manga(manga)
.branch(viewModel.selectedBranchValue)
.incognito(isIncognitoMode)
.build(),
)
if (isIncognitoMode) {
Toast.makeText(this, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
}
}
}
private fun bindTags(manga: Manga) {
viewBinding.chipsTags.isVisible = manga.tags.isNotEmpty()
viewBinding.chipsTags.setChips(
manga.tags.map { tag ->
ChipsView.ChipModel(
title = tag.title,
tint = tagHighlighter.getTagTint(tag),
icon = 0,
data = tag,
isCheckable = false,
isChecked = false,
)
},
)
}
private fun loadCover(manga: Manga) {
val imageUrl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }
val lastResult = CoilUtils.result(viewBinding.imageViewCover)
if (lastResult is SuccessResult && lastResult.request.data == imageUrl) {
return
}
val request = ImageRequest.Builder(this)
.target(viewBinding.imageViewCover)
.size(CoverSizeResolver(viewBinding.imageViewCover))
.data(imageUrl)
.tag(manga.source)
.crossfade(this)
.lifecycle(this)
.placeholderMemoryCacheKey(manga.coverUrl)
val previousDrawable = lastResult?.drawable
if (previousDrawable != null) {
request.fallback(previousDrawable)
.placeholder(previousDrawable)
.error(previousDrawable)
} else {
request.fallback(R.drawable.ic_placeholder)
.placeholder(R.drawable.ic_placeholder)
.error(R.drawable.ic_error_placeholder)
}
request.enqueueWith(coil)
}
private class PrefetchObserver(
private val context: Context,
) : FlowCollector<List<ChapterListItem>?> {
private var isCalled = false
override suspend fun emit(value: List<ChapterListItem>?) {
if (value.isNullOrEmpty()) {
return
}
if (!isCalled) {
isCalled = true
val item = value.find { it.isCurrent } ?: value.first()
MangaPrefetchService.prefetchPages(context, item.chapter)
}
}
}
companion object {
private const val FAV_LABEL_LIMIT = 10
fun newIntent(context: Context, manga: Manga): Intent {
return Intent(context, DetailsActivity2::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga))
}
fun newIntent(context: Context, mangaId: Long): Intent {
return Intent(context, DetailsActivity2::class.java)
.putExtra(MangaIntent.KEY_ID, mangaId)
}
}
}

View File

@@ -16,7 +16,7 @@ class DetailsErrorObserver(
private val viewModel: DetailsViewModel,
resolver: ExceptionResolver?,
) : ErrorObserver(
activity.viewBinding.containerDetails, null, resolver,
activity.viewBinding.scrollView, null, resolver,
{ isResolved ->
if (isResolved) {
viewModel.reload()

View File

@@ -1,424 +0,0 @@
package org.koitharu.kotatsu.details.ui
import android.os.Bundle
import android.transition.TransitionManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.Toast
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.text.method.LinkMovementMethodCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import coil.ImageLoader
import coil.request.ImageRequest
import coil.request.SuccessResult
import coil.util.CoilUtils
import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.filterNotNull
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
import org.koitharu.kotatsu.bookmarks.ui.sheet.BookmarksSheet
import org.koitharu.kotatsu.core.model.countChaptersByBranch
import org.koitharu.kotatsu.core.model.iconResId
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.drawableTop
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
import org.koitharu.kotatsu.core.util.ext.isTextTruncated
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.resolveDp
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.showOrHide
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.FragmentDetailsBinding
import org.koitharu.kotatsu.details.data.ReadingTime
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.details.ui.related.RelatedMangaActivity
import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingItemDecoration
import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
import org.koitharu.kotatsu.local.ui.info.LocalInfoDialog
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity
import javax.inject.Inject
@AndroidEntryPoint
class DetailsFragment :
BaseFragment<FragmentDetailsBinding>(),
View.OnClickListener,
ChipsView.OnChipClickListener,
OnListItemClickListener<Bookmark>, ViewTreeObserver.OnDrawListener, View.OnLayoutChangeListener {
@Inject
lateinit var coil: ImageLoader
@Inject
lateinit var tagHighlighter: ListExtraProvider
private val viewModel by activityViewModels<DetailsViewModel>()
override fun onCreateViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
) = FragmentDetailsBinding.inflate(inflater, container, false)
override fun onViewBindingCreated(binding: FragmentDetailsBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
binding.textViewAuthor.setOnClickListener(this)
binding.imageViewCover.setOnClickListener(this)
binding.buttonDescriptionMore.setOnClickListener(this)
binding.buttonBookmarksMore.setOnClickListener(this)
binding.buttonScrobblingMore.setOnClickListener(this)
binding.buttonRelatedMore.setOnClickListener(this)
binding.infoLayout.textViewSource.setOnClickListener(this)
binding.infoLayout.textViewSize.setOnClickListener(this)
binding.textViewDescription.addOnLayoutChangeListener(this)
binding.textViewDescription.viewTreeObserver.addOnDrawListener(this)
binding.textViewDescription.movementMethod = LinkMovementMethodCompat.getInstance()
binding.chipsTags.onChipClickListener = this
binding.recyclerViewRelated.addItemDecoration(
SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)),
)
TitleScrollCoordinator(binding.textViewTitle).attach(binding.scrollView)
viewModel.manga.filterNotNull().observe(viewLifecycleOwner, ::onMangaUpdated)
viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged)
viewModel.historyInfo.observe(viewLifecycleOwner, ::onHistoryChanged)
viewModel.bookmarks.observe(viewLifecycleOwner, ::onBookmarksChanged)
viewModel.scrobblingInfo.observe(viewLifecycleOwner, ::onScrobblingInfoChanged)
viewModel.description.observe(viewLifecycleOwner, ::onDescriptionChanged)
viewModel.localSize.observe(viewLifecycleOwner, ::onLocalSizeChanged)
viewModel.relatedManga.observe(viewLifecycleOwner, ::onRelatedMangaChanged)
viewModel.chapters.observe(viewLifecycleOwner, ::onChaptersChanged)
viewModel.readingTime.observe(viewLifecycleOwner, ::onReadingTimeChanged)
}
override fun onItemClick(item: Bookmark, view: View) {
startActivity(
ReaderActivity.IntentBuilder(view.context).bookmark(item).incognito(true).build(),
)
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
}
override fun onItemLongClick(item: Bookmark, view: View): Boolean {
val menu = PopupMenu(view.context, view)
menu.inflate(R.menu.popup_bookmark)
menu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.action_remove -> viewModel.removeBookmark(item)
}
true
}
menu.show()
return true
}
override fun onDraw() {
viewBinding?.run {
buttonDescriptionMore.isVisible = textViewDescription.maxLines == Int.MAX_VALUE ||
textViewDescription.isTextTruncated
}
}
override fun onLayoutChange(
v: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
with(viewBinding ?: return) {
buttonDescriptionMore.isVisible = textViewDescription.isTextTruncated
}
}
private fun onMangaUpdated(manga: Manga) {
with(requireViewBinding()) {
// Main
loadCover(manga)
textViewTitle.text = manga.title
textViewSubtitle.textAndVisible = manga.altTitle
textViewAuthor.textAndVisible = manga.author
if (manga.hasRating) {
ratingBar.rating = manga.rating * ratingBar.numStars
ratingBar.isVisible = true
} else {
ratingBar.isVisible = false
}
infoLayout.textViewState.apply {
manga.state?.let { state ->
textAndVisible = resources.getString(state.titleResId)
drawableTop = ContextCompat.getDrawable(context, state.iconResId)
} ?: run {
isVisible = false
}
}
if (manga.source == MangaSource.LOCAL || manga.source == MangaSource.DUMMY) {
infoLayout.textViewSource.isVisible = false
} else {
infoLayout.textViewSource.text = manga.source.title
infoLayout.textViewSource.isVisible = true
}
infoLayout.textViewNsfw.isVisible = manga.isNsfw
// Chips
bindTags(manga)
}
}
private fun onChaptersChanged(chapters: List<ChapterListItem>?) {
val infoLayout = requireViewBinding().infoLayout
if (chapters.isNullOrEmpty()) {
infoLayout.textViewChapters.isVisible = false
} else {
val count = chapters.countChaptersByBranch()
infoLayout.textViewChapters.isVisible = true
val chaptersText = resources.getQuantityString(R.plurals.chapters, count, count)
infoLayout.textViewChapters.text = chaptersText
}
}
private fun onReadingTimeChanged(time: ReadingTime?) {
val binding = viewBinding ?: return
if (time == null) {
binding.approximateReadTimeLayout.isVisible = false
return
}
binding.approximateReadTime.text = time.format(resources)
binding.approximateReadTimeTitle.setText(
if (time.isContinue) R.string.approximate_remaining_time else R.string.approximate_reading_time,
)
binding.approximateReadTimeLayout.isVisible = true
}
private fun onDescriptionChanged(description: CharSequence?) {
val tv = requireViewBinding().textViewDescription
if (description.isNullOrBlank()) {
tv.setText(R.string.no_description)
} else {
tv.text = description
}
}
private fun onLocalSizeChanged(size: Long) {
val textView = requireViewBinding().infoLayout.textViewSize
if (size == 0L) {
textView.isVisible = false
} else {
textView.text = FileSize.BYTES.format(textView.context, size)
textView.isVisible = true
}
}
private fun onRelatedMangaChanged(related: List<MangaItemModel>) {
if (related.isEmpty()) {
requireViewBinding().groupRelated.isVisible = false
return
}
val rv = viewBinding?.recyclerViewRelated ?: return
@Suppress("UNCHECKED_CAST")
val adapter = (rv.adapter as? BaseListAdapter<ListModel>) ?: BaseListAdapter<ListModel>()
.addDelegate(
ListItemType.MANGA_GRID,
mangaGridItemAD(
coil, viewLifecycleOwner,
StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)),
) { item, view ->
startActivity(DetailsActivity.newIntent(view.context, item))
},
).also { rv.adapter = it }
adapter.items = related
requireViewBinding().groupRelated.isVisible = true
}
private fun onHistoryChanged(history: HistoryInfo) {
requireViewBinding().progressView.setPercent(history.history?.percent ?: PROGRESS_NONE, animate = true)
}
private fun onLoadingStateChanged(isLoading: Boolean) {
requireViewBinding().progressBar.showOrHide(isLoading)
}
private fun onBookmarksChanged(bookmarks: List<Bookmark>) {
var adapter = requireViewBinding().recyclerViewBookmarks.adapter as? BookmarksAdapter
requireViewBinding().groupBookmarks.isGone = bookmarks.isEmpty()
if (adapter != null) {
adapter.items = bookmarks
} else {
adapter = BookmarksAdapter(coil, viewLifecycleOwner, this)
adapter.items = bookmarks
requireViewBinding().recyclerViewBookmarks.adapter = adapter
val spacing = resources.getDimensionPixelOffset(R.dimen.bookmark_list_spacing)
requireViewBinding().recyclerViewBookmarks.addItemDecoration(SpacingItemDecoration(spacing))
}
}
private fun onScrobblingInfoChanged(scrobblings: List<ScrobblingInfo>) {
var adapter = requireViewBinding().recyclerViewScrobbling.adapter as? ScrollingInfoAdapter
requireViewBinding().groupScrobbling.isGone = scrobblings.isEmpty()
if (adapter != null) {
adapter.items = scrobblings
} else {
adapter = ScrollingInfoAdapter(viewLifecycleOwner, coil, childFragmentManager)
adapter.items = scrobblings
requireViewBinding().recyclerViewScrobbling.adapter = adapter
requireViewBinding().recyclerViewScrobbling.addItemDecoration(ScrobblingItemDecoration())
}
}
override fun onClick(v: View) {
val manga = viewModel.manga.value ?: return
when (v.id) {
R.id.textView_author -> {
startActivity(
SearchActivity.newIntent(
context = v.context,
source = manga.source,
query = manga.author ?: return,
),
)
}
R.id.textView_source -> {
startActivity(
MangaListActivity.newIntent(
context = v.context,
source = manga.source,
),
)
}
R.id.textView_size -> {
LocalInfoDialog.show(parentFragmentManager, manga)
}
R.id.imageView_cover -> {
startActivity(
ImageActivity.newIntent(
v.context,
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
manga.source,
),
scaleUpActivityOptionsOf(v),
)
}
R.id.button_description_more -> {
val tv = requireViewBinding().textViewDescription
TransitionManager.beginDelayedTransition(tv.parentView)
if (tv.maxLines in 1 until Integer.MAX_VALUE) {
tv.maxLines = Integer.MAX_VALUE
} else {
tv.maxLines = resources.getInteger(R.integer.details_description_lines)
}
}
R.id.button_scrobbling_more -> {
ScrobblingSelectorSheet.show(parentFragmentManager, manga, null)
}
R.id.button_bookmarks_more -> {
BookmarksSheet.show(parentFragmentManager, manga)
}
R.id.button_related_more -> {
startActivity(RelatedMangaActivity.newIntent(v.context, manga))
}
}
}
override fun onChipClick(chip: Chip, data: Any?) {
val tag = data as? MangaTag ?: return
startActivity(MangaListActivity.newIntent(requireContext(), setOf(tag)))
}
override fun onWindowInsetsChanged(insets: Insets) {
requireViewBinding().root.updatePadding(
bottom = (
(activity as? NoModalBottomSheetOwner)?.getBottomSheetCollapsedHeight()
?.plus(insets.bottom)?.plus(resources.resolveDp(16))
)
?: insets.bottom,
)
}
private fun bindTags(manga: Manga) {
requireViewBinding().chipsTags.setChips(
manga.tags.map { tag ->
ChipsView.ChipModel(
title = tag.title,
tint = tagHighlighter.getTagTint(tag),
icon = 0,
data = tag,
isCheckable = false,
isChecked = false,
)
},
)
}
private fun loadCover(manga: Manga) {
val imageUrl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }
val lastResult = CoilUtils.result(requireViewBinding().imageViewCover)
if (lastResult is SuccessResult && lastResult.request.data == imageUrl) {
return
}
val request = ImageRequest.Builder(context ?: return)
.target(requireViewBinding().imageViewCover)
.size(CoverSizeResolver(requireViewBinding().imageViewCover))
.data(imageUrl)
.tag(manga.source)
.crossfade(requireContext())
.lifecycle(viewLifecycleOwner)
.placeholderMemoryCacheKey(manga.coverUrl)
val previousDrawable = lastResult?.drawable
if (previousDrawable != null) {
request.fallback(previousDrawable)
.placeholder(previousDrawable)
.error(previousDrawable)
} else {
request.fallback(R.drawable.ic_placeholder)
.placeholder(R.drawable.ic_placeholder)
.error(R.drawable.ic_error_placeholder)
}
request.enqueueWith(coil)
}
}

View File

@@ -20,7 +20,6 @@ import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.download.ui.dialog.DownloadOption
import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteSheet
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
@@ -35,7 +34,6 @@ class DetailsMenuProvider(
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_details, menu)
menu.findItem(R.id.action_favourite).isVisible = activity is DetailsActivity
}
override fun onPrepareMenu(menu: Menu) {
@@ -48,9 +46,6 @@ class DetailsMenuProvider(
menu.findItem(R.id.action_scrobbling).isVisible = viewModel.isScrobblingAvailable
menu.findItem(R.id.action_online).isVisible = viewModel.remoteManga.value != null
menu.findItem(R.id.action_stats).isVisible = viewModel.isStatsAvailable.value
menu.findItem(R.id.action_favourite).setIcon(
if (viewModel.favouriteCategories.value.isNotEmpty()) R.drawable.ic_heart else R.drawable.ic_heart_outline,
)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
@@ -66,12 +61,6 @@ class DetailsMenuProvider(
}
}
R.id.action_favourite -> {
viewModel.manga.value?.let {
FavoriteSheet.show(activity.supportFragmentManager, it)
}
}
R.id.action_delete -> {
val title = viewModel.manga.value?.title.orEmpty()
MaterialAlertDialogBuilder(activity)

View File

@@ -11,8 +11,6 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -247,12 +245,6 @@ class DetailsViewModel @Inject constructor(
localStorageChanges
.collect { onDownloadComplete(it) }
}
launchJob(Dispatchers.Default) {
if (settings.isTipEnabled(DetailsActivity.TIP_BUTTON)) {
manga.filterNot { it?.chapters.isNullOrEmpty() }.first()
onShowTip.call(Unit)
}
}
launchJob(Dispatchers.Default) {
val manga = details.firstOrNull { !it?.chapters.isNullOrEmpty() } ?: return@launchJob
val h = history.firstOrNull()
@@ -363,10 +355,6 @@ class DetailsViewModel @Inject constructor(
onSelectChapter.call(chapter.chapter.id)
}
fun onButtonTipClosed() {
settings.closeTip(DetailsActivity.TIP_BUTTON)
}
fun removeFromHistory() {
launchJob(Dispatchers.Default) {
val handle = historyRepository.delete(setOf(mangaId))

View File

@@ -5,12 +5,11 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.details.ui.pager.bookmarks.MangaBookmarksFragment
import org.koitharu.kotatsu.details.ui.pager.chapters.ChaptersFragment
import org.koitharu.kotatsu.details.ui.pager.pages.PagesFragment
class DetailsPagerAdapter2(
class ChaptersPagesAdapter(
fragment: Fragment,
val isPagesTabEnabled: Boolean,
) : FragmentStateAdapter(fragment),

View File

@@ -39,7 +39,7 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
disableFitToContents()
val args = arguments ?: Bundle.EMPTY
val adapter = DetailsPagerAdapter2(this, args.getBoolean(ARG_SHOW_PAGES, settings.isPagesTabEnabled))
val adapter = ChaptersPagesAdapter(this, args.getBoolean(ARG_SHOW_PAGES, settings.isPagesTabEnabled))
binding.pager.recyclerView?.isNestedScrollingEnabled = false
binding.pager.offscreenPageLimit = adapter.itemCount
binding.pager.adapter = adapter

View File

@@ -1,38 +0,0 @@
package org.koitharu.kotatsu.details.ui.pager
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.details.ui.pager.chapters.ChaptersFragment
import org.koitharu.kotatsu.details.ui.pager.pages.PagesFragment
class DetailsPagerAdapter(
activity: FragmentActivity,
settings: AppSettings,
) : FragmentStateAdapter(activity),
TabLayoutMediator.TabConfigurationStrategy {
val isPagesTabEnabled = settings.isPagesTabEnabled
override fun getItemCount(): Int = if (isPagesTabEnabled) 2 else 1
override fun createFragment(position: Int): Fragment = when (position) {
0 -> ChaptersFragment()
1 -> PagesFragment()
else -> throw IllegalArgumentException("Invalid position $position")
}
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
tab.setText(
when (position) {
0 -> R.string.chapters
1 -> R.string.pages
else -> 0
},
)
}
}

View File

@@ -10,7 +10,6 @@ import androidx.appcompat.view.ActionMode
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
@@ -28,8 +27,6 @@ import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
import org.koitharu.kotatsu.details.ui.ChaptersMenuProvider
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.details.ui.DetailsViewModel
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
@@ -96,12 +93,6 @@ class ChaptersFragment :
viewModel.onSelectChapter.observeEvent(viewLifecycleOwner) {
selectionController?.onItemLongClick(it)
}
val detailsActivity = activity as? DetailsActivity
if (detailsActivity != null) {
val menuProvider = ChaptersMenuProvider(viewModel, detailsActivity.bottomSheetMediator)
activity?.onBackPressedDispatcher?.addCallback(menuProvider)
detailsActivity.secondaryMenuHost.addMenuProvider(menuProvider, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
}
override fun onDestroyView() {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.reader.ui.thumbnails
package org.koitharu.kotatsu.details.ui.pager.pages
import android.content.Context
import android.webkit.MimeTypeMap

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.reader.ui.thumbnails
package org.koitharu.kotatsu.details.ui.pager.pages
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.reader.ui.thumbnails.adapter
package org.koitharu.kotatsu.details.ui.pager.pages
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
@@ -15,7 +15,6 @@ import org.koitharu.kotatsu.core.util.ext.setTextColorAttr
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemPageThumbBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
import com.google.android.material.R as materialR
fun pageThumbnailAD(

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.reader.ui.thumbnails.adapter
package org.koitharu.kotatsu.details.ui.pager.pages
import android.content.Context
import androidx.lifecycle.LifecycleOwner
@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
class PageThumbnailAdapter(
coil: ImageLoader,

View File

@@ -37,8 +37,6 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
import javax.inject.Inject
import kotlin.math.roundToInt

View File

@@ -16,7 +16,6 @@ import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.reader.domain.ChaptersLoader
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
import javax.inject.Inject
@HiltViewModel

View File

@@ -1,124 +0,0 @@
package org.koitharu.kotatsu.reader.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.findById
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.databinding.SheetChaptersBinding
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
import org.koitharu.kotatsu.details.ui.mapChapters
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.pager.chapters.ChapterGridSpanHelper
import org.koitharu.kotatsu.details.ui.withVolumeHeaders
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.parsers.model.MangaChapter
import java.time.Instant
import javax.inject.Inject
import kotlin.math.roundToInt
@Deprecated("Use ChaptersPagesSheet instead")
@AndroidEntryPoint
class ChaptersSheet : BaseAdaptiveSheet<SheetChaptersBinding>(),
OnListItemClickListener<ChapterListItem> {
@Inject
lateinit var settings: AppSettings
private val viewModel: ReaderViewModel by activityViewModels()
override fun onCreateViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
) = SheetChaptersBinding.inflate(inflater, container, false)
override fun onViewBindingCreated(binding: SheetChaptersBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
val manga = viewModel.manga
if (manga == null) {
dismissAllowingStateLoss()
return
}
val state = viewModel.getCurrentState()
val currentChapter = state?.let { manga.allChapters.findById(it.chapterId) }
val chapters = manga.mapChapters(
history = state?.let {
MangaHistory(
createdAt = Instant.now(),
updatedAt = Instant.now(),
chapterId = it.chapterId,
page = it.page,
scroll = it.scroll,
percent = PROGRESS_NONE,
)
},
newCount = 0,
branch = currentChapter?.branch,
bookmarks = listOf(),
isGrid = settings.isChaptersGridView,
).withVolumeHeaders(binding.root.context)
if (chapters.isEmpty()) {
dismissAllowingStateLoss()
return
}
val currentPosition = if (currentChapter != null) {
chapters.indexOfFirst { it is ChapterListItem && it.chapter.id == currentChapter.id }
} else {
-1
}
binding.recyclerView.addItemDecoration(TypedListSpacingDecoration(binding.recyclerView.context, true))
binding.recyclerView.adapter = ChaptersAdapter(this).also { adapter ->
if (currentPosition >= 0) {
val targetPosition = (currentPosition - 1).coerceAtLeast(0)
val offset =
(resources.getDimensionPixelSize(R.dimen.chapter_list_item_height) * 0.6).roundToInt()
adapter.setItems(
chapters, RecyclerViewScrollCallback(binding.recyclerView, targetPosition, offset),
)
} else {
adapter.items = chapters
}
}
ChapterGridSpanHelper.attach(binding.recyclerView)
binding.recyclerView.layoutManager = if (settings.isChaptersGridView) {
GridLayoutManager(context, ChapterGridSpanHelper.getSpanCount(binding.recyclerView)).apply {
spanSizeLookup = ChapterGridSpanHelper.SpanSizeLookup(binding.recyclerView)
}
} else {
LinearLayoutManager(context)
}
}
override fun onItemClick(item: ChapterListItem, view: View) {
((parentFragment as? OnChapterChangeListener)
?: (activity as? OnChapterChangeListener))?.let {
dismiss()
it.onChapterSelected(item.chapter)
}
}
fun interface OnChapterChangeListener {
fun onChapterSelected(chapter: MangaChapter): Boolean
}
companion object {
private const val TAG = "ChaptersBottomSheet"
fun show(fm: FragmentManager) = ChaptersSheet().showDistinct(fm, TAG)
}
}

View File

@@ -61,16 +61,13 @@ import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
import org.koitharu.kotatsu.reader.ui.tapgrid.TapGridDispatcher
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@AndroidEntryPoint
class ReaderActivity :
BaseFullscreenActivity<ActivityReaderBinding>(),
ChaptersSheet.OnChapterChangeListener,
TapGridDispatcher.OnGridTouchListener,
OnPageSelectListener,
ReaderConfigSheet.Callback,
ReaderControlDelegate.OnInteractionListener,
OnApplyWindowInsetsListener,
@@ -119,7 +116,7 @@ class ReaderActivity :
controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this)
viewBinding.slider.setLabelFormatter(PageLabelFormatter())
viewBinding.zoomControl.listener = this
ReaderSliderListener(this, viewModel).attachToSlider(viewBinding.slider)
ReaderSliderListener(viewModel, this).attachToSlider(viewBinding.slider)
insetsDelegate.interceptingWindowInsetsListener = this
idlingDetector.bindToLifecycle(this)

View File

@@ -8,7 +8,6 @@ import androidx.fragment.app.FragmentActivity
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet
import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet
import org.koitharu.kotatsu.reader.ui.thumbnails.PagesThumbnailsSheet
import org.koitharu.kotatsu.settings.SettingsActivity
class ReaderBottomMenuProvider(

View File

@@ -2,11 +2,10 @@ package org.koitharu.kotatsu.reader.ui
import com.google.android.material.slider.Slider
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
class ReaderSliderListener(
private val pageSelectListener: OnPageSelectListener,
private val viewModel: ReaderViewModel,
private val callback: ReaderNavigationCallback,
) : Slider.OnChangeListener, Slider.OnSliderTouchListener {
private var isChanged = false
@@ -43,6 +42,6 @@ class ReaderSliderListener(
val pages = viewModel.getCurrentChapterPages()
val page = pages?.getOrNull(index) ?: return
val chapterId = viewModel.getCurrentState()?.chapterId ?: return
pageSelectListener.onPageSelected(ReaderPage(page, index, chapterId))
callback.onPageSelected(ReaderPage(page, index, chapterId))
}
}

View File

@@ -1,9 +0,0 @@
package org.koitharu.kotatsu.reader.ui.thumbnails
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
@Deprecated("")
fun interface OnPageSelectListener {
fun onPageSelected(page: ReaderPage): Boolean
}

View File

@@ -1,195 +0,0 @@
package org.koitharu.kotatsu.reader.ui.thumbnails
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.plus
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.showOrHide
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetPagesBinding
import org.koitharu.kotatsu.list.ui.GridSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
import javax.inject.Inject
import kotlin.math.roundToInt
@Deprecated("Use ChaptersPagesSheet instead")
@AndroidEntryPoint
class PagesThumbnailsSheet :
BaseAdaptiveSheet<SheetPagesBinding>(),
AdaptiveSheetCallback,
OnListItemClickListener<PageThumbnail> {
private val viewModel by viewModels<PagesThumbnailsViewModel>()
@Inject
lateinit var coil: ImageLoader
@Inject
lateinit var settings: AppSettings
private var thumbnailsAdapter: PageThumbnailAdapter? = null
private var spanResolver: GridSpanResolver? = null
private var scrollListener: ScrollListener? = null
private val spanSizeLookup = SpanSizeLookup()
private val listCommitCallback = Runnable {
spanSizeLookup.invalidateCache()
}
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetPagesBinding {
return SheetPagesBinding.inflate(inflater, container, false)
}
override fun onViewBindingCreated(binding: SheetPagesBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
addSheetCallback(this)
spanResolver = GridSpanResolver(binding.root.resources)
thumbnailsAdapter = PageThumbnailAdapter(
coil = coil,
lifecycleOwner = viewLifecycleOwner,
clickListener = this@PagesThumbnailsSheet,
)
with(binding.recyclerView) {
addItemDecoration(TypedListSpacingDecoration(context, false))
adapter = thumbnailsAdapter
addOnLayoutChangeListener(spanResolver)
spanResolver?.setGridSize(settings.gridSize / 100f, this)
addOnScrollListener(ScrollListener().also { scrollListener = it })
(layoutManager as GridLayoutManager).spanSizeLookup = spanSizeLookup
}
viewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged)
viewModel.branch.observe(viewLifecycleOwner, ::updateTitle)
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.isLoading.observe(viewLifecycleOwner) { binding.progressBar.showOrHide(it) }
}
override fun onDestroyView() {
spanResolver = null
scrollListener = null
thumbnailsAdapter = null
spanSizeLookup.invalidateCache()
super.onDestroyView()
}
override fun onItemClick(item: PageThumbnail, view: View) {
val listener = (parentFragment as? OnPageSelectListener) ?: (activity as? OnPageSelectListener)
if (listener != null) {
listener.onPageSelected(item.page)
} else {
val state = ReaderState(item.page.chapterId, item.page.index, 0)
val intent = IntentBuilder(view.context).manga(viewModel.manga).state(state).build()
startActivity(intent)
}
dismiss()
}
override fun onStateChanged(sheet: View, newState: Int) {
viewBinding?.recyclerView?.isFastScrollerEnabled = newState == AdaptiveSheetBehavior.STATE_EXPANDED
}
private fun updateTitle(branch: String?) {
val mangaName = viewModel.manga.title
viewBinding?.headerBar?.title = if (branch != null) {
getString(R.string.manga_branch_title_template, mangaName, branch)
} else {
mangaName
}
}
private fun onThumbnailsChanged(list: List<ListModel>) {
val adapter = thumbnailsAdapter ?: return
if (adapter.itemCount == 0) {
var position = list.indexOfFirst { it is PageThumbnail && it.isCurrent }
if (position > 0) {
val spanCount = spanResolver?.spanCount ?: 0
val offset = if (position > spanCount + 1) {
(resources.getDimensionPixelSize(R.dimen.manga_list_details_item_height) * 0.6).roundToInt()
} else {
position = 0
0
}
val scrollCallback = RecyclerViewScrollCallback(requireViewBinding().recyclerView, position, offset)
adapter.setItems(list, listCommitCallback + scrollCallback)
} else {
adapter.setItems(list, listCommitCallback)
}
} else {
adapter.setItems(list, listCommitCallback)
}
}
private inner class ScrollListener : BoundsScrollListener(3, 3) {
override fun onScrolledToStart(recyclerView: RecyclerView) {
viewModel.loadPrevChapter()
}
override fun onScrolledToEnd(recyclerView: RecyclerView) {
viewModel.loadNextChapter()
}
}
private inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() {
init {
isSpanIndexCacheEnabled = true
isSpanGroupIndexCacheEnabled = true
}
override fun getSpanSize(position: Int): Int {
val total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1
return when (thumbnailsAdapter?.getItemViewType(position)) {
ListItemType.PAGE_THUMB.ordinal -> 1
else -> total
}
}
fun invalidateCache() {
invalidateSpanGroupIndexCache()
invalidateSpanIndexCache()
}
}
companion object {
const val ARG_MANGA = "manga"
const val ARG_CURRENT_PAGE = "current"
const val ARG_CHAPTER_ID = "chapter_id"
private const val TAG = "PagesThumbnailsSheet"
fun show(fm: FragmentManager, manga: Manga, chapterId: Long, currentPage: Int = -1) {
PagesThumbnailsSheet().withArgs(3) {
putParcelable(ARG_MANGA, ParcelableManga(manga))
putLong(ARG_CHAPTER_ID, chapterId)
putInt(ARG_CURRENT_PAGE, currentPage)
}.showDistinct(fm, TAG)
}
}
}

View File

@@ -1,97 +0,0 @@
package org.koitharu.kotatsu.reader.ui.thumbnails
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.koitharu.kotatsu.core.model.findById
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.firstNotNull
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.reader.domain.ChaptersLoader
import javax.inject.Inject
@HiltViewModel
class PagesThumbnailsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val chaptersLoader: ChaptersLoader,
detailsLoadUseCase: DetailsLoadUseCase,
) : BaseViewModel() {
private val currentPageIndex: Int =
savedStateHandle[PagesThumbnailsSheet.ARG_CURRENT_PAGE] ?: -1
private val initialChapterId: Long = savedStateHandle[PagesThumbnailsSheet.ARG_CHAPTER_ID] ?: 0L
val manga = savedStateHandle.require<ParcelableManga>(PagesThumbnailsSheet.ARG_MANGA).manga
private val mangaDetails = detailsLoadUseCase(MangaIntent.of(manga)).map {
val b = manga.chapters?.findById(initialChapterId)?.branch
branch.value = b
it.filterChapters(b)
}.withErrorHandling()
.stateIn(viewModelScope, SharingStarted.Lazily, null)
private var loadingJob: Job
private var loadingPrevJob: Job? = null
private var loadingNextJob: Job? = null
val thumbnails = MutableStateFlow<List<ListModel>>(emptyList())
val branch = MutableStateFlow<String?>(null)
init {
loadingJob = launchLoadingJob(Dispatchers.Default) {
chaptersLoader.init(checkNotNull(mangaDetails.first { x -> x?.isLoaded == true }))
chaptersLoader.loadSingleChapter(initialChapterId)
updateList()
}
}
fun loadPrevChapter() {
if (loadingJob.isActive || loadingPrevJob?.isActive == true) {
return
}
loadingPrevJob = loadPrevNextChapter(isNext = false)
}
fun loadNextChapter() {
if (loadingJob.isActive || loadingNextJob?.isActive == true) {
return
}
loadingNextJob = loadPrevNextChapter(isNext = true)
}
private fun loadPrevNextChapter(isNext: Boolean): Job = launchLoadingJob(Dispatchers.Default) {
val currentId = (if (isNext) chaptersLoader.last() else chaptersLoader.first()).chapterId
chaptersLoader.loadPrevNextChapter(mangaDetails.firstNotNull(), currentId, isNext)
updateList()
}
private fun updateList() {
val snapshot = chaptersLoader.snapshot()
val pages = buildList(snapshot.size + chaptersLoader.size + 2) {
var previousChapterId = 0L
for (page in snapshot) {
if (page.chapterId != previousChapterId) {
chaptersLoader.peekChapter(page.chapterId)?.let {
add(ListHeader(it.name))
}
previousChapterId = page.chapterId
}
this += PageThumbnail(
isCurrent = page.chapterId == initialChapterId && page.index == currentPageIndex,
page = page,
)
}
}
thumbnails.value = pages
}
}

View File

@@ -3,9 +3,11 @@
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:id="@+id/layout_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".details.ui.DetailsActivity">
tools:context=".details.ui.DetailsActivity"
tools:viewBindingType="android.view.ViewGroup">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
@@ -13,92 +15,377 @@
android:layout_height="wrap_content"
android:elevation="0dp"
android:fitsSystemWindows="true"
android:stateListAnimator="@null"
app:elevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="420dp"
app:layout_constraintWidth_percent="0.5"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="noScroll"
tools:menu="@menu/opt_details">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_read"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
android:enabled="false"
android:text="@string/read"
android:textAllCaps="false"
app:iconGravity="textStart"
app:iconPadding="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
tools:enabled="true"
tools:icon="@drawable/ic_read" />
<ImageView
android:id="@+id/button_dropdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/translations"
android:padding="@dimen/margin_small"
android:src="@drawable/ic_expand_more" />
<LinearLayout
android:id="@+id/group_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/textView_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?textAppearanceTitleMedium"
tools:ignore="InconsistentLayout"
tools:text="@string/chapter_d_of_d" />
<TextView
android:id="@+id/textView_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?textAppearanceTitleSmall"
android:textColor="?android:textColorSecondary"
android:visibility="gone"
tools:text="English"
tools:visibility="visible" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_details"
android:name="org.koitharu.kotatsu.details.ui.DetailsFragment"
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:clipToPadding="false"
android:scrollIndicators="top"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/appbar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
app:layout_constraintWidth_percent="0.5"
tools:layout="@layout/fragment_details" />
app:layout_constraintTop_toBottomOf="@id/appbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="?colorSecondaryContainer"
android:foreground="?selectableItemBackground"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="H,13:18"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.3"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
tools:background="@tools:sample/backgrounds/scenic[5]"
tools:ignore="ContentDescription,UnusedAttribute" />
<TextView
android:id="@+id/textView_nsfw"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_indicator_offset"
android:background="@drawable/bg_chip"
android:backgroundTint="@color/warning"
android:gravity="center"
android:paddingHorizontal="4dp"
android:paddingVertical="2dp"
android:text="@string/nsfw"
android:textAlignment="center"
android:textAppearance="?textAppearanceLabelMedium"
android:textColor="?colorOnError"
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="5"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_title"
tools:text="@tools:sample/lorem[12]" />
<ImageView
android:id="@+id/imageView_state"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginVertical="0.5dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@id/textView_state"
app:layout_constraintDimensionRatio="1"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="@id/textView_state"
tools:src="@drawable/ic_state_ongoing" />
<TextView
android:id="@+id/textView_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:drawablePadding="4dp"
android:gravity="center_vertical"
android:labelFor="@id/imageView_state"
android:singleLine="true"
android:textColor="?colorTertiary"
android:textStyle="bold"
app:drawableTint="?colorTertiary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/imageView_state"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle"
app:layout_constraintWidth_default="wrap"
tools:text="@string/state_ongoing" />
<RatingBar
android:id="@+id/rating_bar"
style="?ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:isIndicator="true"
android:max="1"
android:numStars="5"
android:stepSize="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_state"
tools:rating="4" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:barrierMargin="@dimen/margin_normal"
app:constraint_referenced_ids="imageView_cover,rating_bar" />
<include
android:id="@+id/info_layout"
layout="@layout/layout_details_chips"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_header" />
<org.koitharu.kotatsu.core.ui.widgets.ProgressButton
android:id="@+id/button_read"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginHorizontal="@dimen/screen_padding"
android:layout_marginTop="@dimen/margin_normal"
android:foreground="?selectableItemBackground"
android:gravity="center"
android:paddingHorizontal="6dp"
android:paddingVertical="8dp"
android:textColor="?colorOnPrimaryContainer"
app:baseColor="?colorSecondaryContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/info_layout"
app:progressColor="?colorPrimary"
app:subtitleTextAppearance="?textAppearanceBodySmall"
app:titleTextAppearance="?textAppearanceButton"
tools:max="100"
tools:progress="40"
tools:subtitle="12 chapters"
tools:title="@string/read" />
<TextView
android:id="@+id/textView_description_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/description"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_description_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_read" />
<Button
android:id="@+id/button_description_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/more"
app:layout_constraintBaseline_toBaselineOf="@id/textView_description_title"
app:layout_constraintEnd_toEndOf="parent" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_normal"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2"
android:maxLines="@integer/details_description_lines"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_description_title"
tools:ignore="UnusedAttribute"
tools:text="@tools:sample/lorem/random" />
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
android:id="@+id/chips_tags"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_small"
android:paddingStart="@dimen/screen_padding"
android:paddingEnd="@dimen/screen_padding"
app:chipSpacingHorizontal="6dp"
app:chipSpacingVertical="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_description" />
<TextView
android:id="@+id/textView_scrobbling_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/tracking"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_scrobbling_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chips_tags" />
<Button
android:id="@+id/button_scrobbling_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/manage"
app:layout_constraintBaseline_toBaselineOf="@id/textView_scrobbling_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_scrobbling"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:nestedScrollingEnabled="false"
android:orientation="vertical"
android:overScrollMode="never"
android:scrollbars="none"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_scrobbling_title"
tools:itemCount="2"
tools:listitem="@layout/item_scrobbling_info"
tools:visibility="visible" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:showAnimationBehavior="inward"
app:trackCornerRadius="0dp"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_scrobbling"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_scrobbling,textView_scrobbling_title,button_scrobbling_more"
tools:visibility="visible" />
<TextView
android:id="@+id/textView_related_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/related_manga"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_related_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recyclerView_scrobbling" />
<Button
android:id="@+id/button_related_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/show_all"
android:visibility="gone"
app:layout_constraintBaseline_toBaselineOf="@id/textView_related_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_related"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_related_title"
tools:listitem="@layout/item_manga_grid" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_related"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_related,textView_related_title,button_related_more"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_chapters"
@@ -110,29 +397,14 @@
android:layout_marginBottom="@dimen/side_card_offset"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/container_details"
app:layout_constraintTop_toBottomOf="@id/appbar">
app:layout_constraintStart_toEndOf="@id/appbar"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_side"
android:name="org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
style="@style/Widget.Material3.TabLayout.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
app:tabUnboundedRipple="false" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
android:layout_height="match_parent" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,410 +0,0 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".details.ui.DetailsActivity2"
tools:viewBindingType="android.view.ViewGroup">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:elevation="0dp"
android:fitsSystemWindows="true"
app:elevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="420dp"
app:layout_constraintWidth_percent="0.5"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:scrollIndicators="top"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/appbar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="?colorSecondaryContainer"
android:foreground="?selectableItemBackground"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="H,13:18"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.3"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
tools:background="@tools:sample/backgrounds/scenic[5]"
tools:ignore="ContentDescription,UnusedAttribute" />
<TextView
android:id="@+id/textView_nsfw"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_indicator_offset"
android:background="@drawable/bg_chip"
android:backgroundTint="@color/warning"
android:gravity="center"
android:paddingHorizontal="4dp"
android:paddingVertical="2dp"
android:text="@string/nsfw"
android:textAlignment="center"
android:textAppearance="?textAppearanceLabelMedium"
android:textColor="?colorOnError"
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="5"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_title"
tools:text="@tools:sample/lorem[12]" />
<ImageView
android:id="@+id/imageView_state"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginVertical="0.5dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@id/textView_state"
app:layout_constraintDimensionRatio="1"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="@id/textView_state"
tools:src="@drawable/ic_state_ongoing" />
<TextView
android:id="@+id/textView_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:drawablePadding="4dp"
android:gravity="center_vertical"
android:labelFor="@id/imageView_state"
android:singleLine="true"
android:textColor="?colorTertiary"
android:textStyle="bold"
app:drawableTint="?colorTertiary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/imageView_state"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle"
app:layout_constraintWidth_default="wrap"
tools:text="@string/state_ongoing" />
<RatingBar
android:id="@+id/rating_bar"
style="?ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:isIndicator="true"
android:max="1"
android:numStars="5"
android:stepSize="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_state"
tools:rating="4" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:barrierMargin="@dimen/margin_normal"
app:constraint_referenced_ids="imageView_cover,rating_bar" />
<include
android:id="@+id/info_layout"
layout="@layout/layout_details_chips"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_header" />
<org.koitharu.kotatsu.core.ui.widgets.ProgressButton
android:id="@+id/button_read"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginHorizontal="@dimen/screen_padding"
android:layout_marginTop="@dimen/margin_normal"
android:foreground="?selectableItemBackground"
android:gravity="center"
android:paddingHorizontal="6dp"
android:paddingVertical="8dp"
android:textColor="?colorOnPrimaryContainer"
app:baseColor="?colorSecondaryContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/info_layout"
app:progressColor="?colorPrimary"
app:subtitleTextAppearance="?textAppearanceBodySmall"
app:titleTextAppearance="?textAppearanceButton"
tools:max="100"
tools:progress="40"
tools:subtitle="12 chapters"
tools:title="@string/read" />
<TextView
android:id="@+id/textView_description_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/description"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_description_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_read" />
<Button
android:id="@+id/button_description_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/more"
app:layout_constraintBaseline_toBaselineOf="@id/textView_description_title"
app:layout_constraintEnd_toEndOf="parent" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_normal"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2"
android:maxLines="@integer/details_description_lines"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_description_title"
tools:ignore="UnusedAttribute"
tools:text="@tools:sample/lorem/random" />
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
android:id="@+id/chips_tags"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_small"
android:paddingStart="@dimen/screen_padding"
android:paddingEnd="@dimen/screen_padding"
app:chipSpacingHorizontal="6dp"
app:chipSpacingVertical="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_description" />
<TextView
android:id="@+id/textView_scrobbling_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/tracking"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_scrobbling_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chips_tags" />
<Button
android:id="@+id/button_scrobbling_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/manage"
app:layout_constraintBaseline_toBaselineOf="@id/textView_scrobbling_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_scrobbling"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:nestedScrollingEnabled="false"
android:orientation="vertical"
android:overScrollMode="never"
android:scrollbars="none"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_scrobbling_title"
tools:itemCount="2"
tools:listitem="@layout/item_scrobbling_info"
tools:visibility="visible" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:showAnimationBehavior="inward"
app:trackCornerRadius="0dp"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_scrobbling"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_scrobbling,textView_scrobbling_title,button_scrobbling_more"
tools:visibility="visible" />
<TextView
android:id="@+id/textView_related_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/related_manga"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_related_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recyclerView_scrobbling" />
<Button
android:id="@+id/button_related_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/show_all"
android:visibility="gone"
app:layout_constraintBaseline_toBaselineOf="@id/textView_related_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_related"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_related_title"
tools:listitem="@layout/item_manga_grid" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_related"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_related,textView_related_title,button_related_more"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_chapters"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/side_card_offset"
android:layout_marginTop="2dp"
android:layout_marginEnd="@dimen/side_card_offset"
android:layout_marginBottom="@dimen/side_card_offset"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/appbar"
app:layout_constraintTop_toTopOf="parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_side"
android:name="org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,115 +1,393 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
<LinearLayout
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:id="@+id/layout_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".details.ui.DetailsActivity">
android:clipToPadding="false"
android:orientation="vertical"
tools:viewBindingType="android.view.ViewGroup">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="0dp"
android:fitsSystemWindows="true"
android:stateListAnimator="@null"
app:elevation="0dp"
app:liftOnScroll="false">
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_scrollFlags="noScroll"
tools:ignore="PrivateResource" />
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_details"
android:name="org.koitharu.kotatsu.details.ui.DetailsFragment"
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:layout="@layout/fragment_details" />
android:clipToPadding="false"
android:scrollIndicators="top"
android:scrollbars="vertical">
<LinearLayout
android:id="@+id/layout_bottom"
style="@style/Widget.Material3.BottomSheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
app:behavior_hideable="false"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.BottomSheet"
tools:visibility="visible">
<FrameLayout
android:id="@+id/layout_bs_header"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="match_parent">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/dragHandle"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_gravity="top"
android:paddingTop="0dp"
android:paddingBottom="32dp"
tools:layout_marginBottom="14dp" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="?colorSecondaryContainer"
android:foreground="?selectableItemBackground"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="H,13:18"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.3"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
tools:background="@tools:sample/backgrounds/scenic[5]"
tools:ignore="ContentDescription,UnusedAttribute" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_chapters"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_gravity="bottom"
android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar"
tools:menu="@menu/opt_chapters">
<TextView
android:id="@+id/textView_nsfw"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_indicator_offset"
android:background="@drawable/bg_chip"
android:backgroundTint="@color/warning"
android:gravity="center"
android:paddingHorizontal="4dp"
android:paddingVertical="2dp"
android:text="@string/nsfw"
android:textAlignment="center"
android:textAppearance="?textAppearanceLabelMedium"
android:textColor="?colorOnError"
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
<ImageView
android:id="@+id/button_dropdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/translations"
android:padding="@dimen/margin_small"
android:src="@drawable/ic_expand_more" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="5"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_read"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
android:enabled="false"
android:text="@string/read"
android:textAllCaps="false"
app:iconGravity="textStart"
app:iconPadding="8dp"
tools:enabled="true"
tools:icon="@drawable/ic_read" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_title"
tools:text="@tools:sample/lorem[12]" />
</com.google.android.material.appbar.MaterialToolbar>
<ImageView
android:id="@+id/imageView_state"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginVertical="0.5dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@id/textView_state"
app:layout_constraintDimensionRatio="1"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="@id/textView_state"
tools:src="@drawable/ic_state_ongoing" />
</FrameLayout>
<TextView
android:id="@+id/textView_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:drawablePadding="4dp"
android:gravity="center_vertical"
android:labelFor="@id/imageView_state"
android:singleLine="true"
android:textColor="?colorTertiary"
android:textStyle="bold"
app:drawableTint="?colorTertiary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/imageView_state"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle"
app:layout_constraintWidth_default="wrap"
tools:text="@string/state_ongoing" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
style="@style/Widget.Material3.TabLayout.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
app:tabUnboundedRipple="false" />
<RatingBar
android:id="@+id/rating_bar"
style="?ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:isIndicator="true"
android:max="1"
android:numStars="5"
android:stepSize="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_state"
tools:rating="4" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:barrierMargin="@dimen/margin_normal"
app:constraint_referenced_ids="imageView_cover,rating_bar" />
</LinearLayout>
<include
android:id="@+id/info_layout"
layout="@layout/layout_details_chips"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_header" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<org.koitharu.kotatsu.core.ui.widgets.ProgressButton
android:id="@+id/button_read"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="@dimen/margin_normal"
android:layout_marginEnd="12dp"
android:foreground="?selectableItemBackground"
android:gravity="center"
android:paddingHorizontal="6dp"
android:paddingVertical="8dp"
android:textColor="?colorOnPrimaryContainer"
app:baseColor="?colorSecondaryContainer"
app:layout_constraintEnd_toStartOf="@id/button_chapters"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/info_layout"
app:progressColor="?colorPrimary"
app:subtitleTextAppearance="?textAppearanceBodySmall"
app:titleTextAppearance="?textAppearanceButton"
tools:max="100"
tools:progress="40"
tools:subtitle="12 chapters"
tools:title="@string/read" />
<ImageView
android:id="@+id/button_chapters"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_circle_button"
android:backgroundTint="?colorSecondaryContainer"
android:contentDescription="@string/chapters"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="@id/button_read"
app:layout_constraintDimensionRatio="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/button_read"
app:srcCompat="@drawable/ic_list_sheet" />
<TextView
android:id="@+id/textView_description_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/description"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_description_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_read" />
<Button
android:id="@+id/button_description_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/more"
app:layout_constraintBaseline_toBaselineOf="@id/textView_description_title"
app:layout_constraintEnd_toEndOf="parent" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_normal"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2"
android:maxLines="@integer/details_description_lines"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_description_title"
tools:ignore="UnusedAttribute"
tools:text="@tools:sample/lorem/random" />
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
android:id="@+id/chips_tags"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_small"
android:paddingStart="@dimen/screen_padding"
android:paddingEnd="@dimen/screen_padding"
app:chipSpacingHorizontal="6dp"
app:chipSpacingVertical="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_description" />
<TextView
android:id="@+id/textView_scrobbling_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/tracking"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_scrobbling_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chips_tags" />
<Button
android:id="@+id/button_scrobbling_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/manage"
app:layout_constraintBaseline_toBaselineOf="@id/textView_scrobbling_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_scrobbling"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:nestedScrollingEnabled="false"
android:orientation="vertical"
android:overScrollMode="never"
android:scrollbars="none"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_scrobbling_title"
tools:itemCount="2"
tools:listitem="@layout/item_scrobbling_info"
tools:visibility="visible" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:showAnimationBehavior="inward"
app:trackCornerRadius="0dp"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_scrobbling"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_scrobbling,textView_scrobbling_title,button_scrobbling_more"
tools:visibility="visible" />
<TextView
android:id="@+id/textView_related_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/related_manga"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_related_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recyclerView_scrobbling" />
<Button
android:id="@+id/button_related_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/show_all"
android:visibility="gone"
app:layout_constraintBaseline_toBaselineOf="@id/textView_related_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_related"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_related_title"
tools:listitem="@layout/item_manga_grid" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_related"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_related,textView_related_title,button_related_more"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@@ -1,393 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:id="@+id/layout_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
tools:viewBindingType="android.view.ViewGroup">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollIndicators="top"
android:scrollbars="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="?colorSecondaryContainer"
android:foreground="?selectableItemBackground"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="H,13:18"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.3"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
tools:background="@tools:sample/backgrounds/scenic[5]"
tools:ignore="ContentDescription,UnusedAttribute" />
<TextView
android:id="@+id/textView_nsfw"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_indicator_offset"
android:background="@drawable/bg_chip"
android:backgroundTint="@color/warning"
android:gravity="center"
android:paddingHorizontal="4dp"
android:paddingVertical="2dp"
android:text="@string/nsfw"
android:textAlignment="center"
android:textAppearance="?textAppearanceLabelMedium"
android:textColor="?colorOnError"
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="5"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_title"
tools:text="@tools:sample/lorem[12]" />
<ImageView
android:id="@+id/imageView_state"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginVertical="0.5dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@id/textView_state"
app:layout_constraintDimensionRatio="1"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="@id/textView_state"
tools:src="@drawable/ic_state_ongoing" />
<TextView
android:id="@+id/textView_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:drawablePadding="4dp"
android:gravity="center_vertical"
android:labelFor="@id/imageView_state"
android:singleLine="true"
android:textColor="?colorTertiary"
android:textStyle="bold"
app:drawableTint="?colorTertiary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/imageView_state"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle"
app:layout_constraintWidth_default="wrap"
tools:text="@string/state_ongoing" />
<RatingBar
android:id="@+id/rating_bar"
style="?ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:isIndicator="true"
android:max="1"
android:numStars="5"
android:stepSize="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_state"
tools:rating="4" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:barrierMargin="@dimen/margin_normal"
app:constraint_referenced_ids="imageView_cover,rating_bar" />
<include
android:id="@+id/info_layout"
layout="@layout/layout_details_chips"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_header" />
<org.koitharu.kotatsu.core.ui.widgets.ProgressButton
android:id="@+id/button_read"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="@dimen/margin_normal"
android:layout_marginEnd="12dp"
android:foreground="?selectableItemBackground"
android:gravity="center"
android:paddingHorizontal="6dp"
android:paddingVertical="8dp"
android:textColor="?colorOnPrimaryContainer"
app:baseColor="?colorSecondaryContainer"
app:layout_constraintEnd_toStartOf="@id/button_chapters"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/info_layout"
app:progressColor="?colorPrimary"
app:subtitleTextAppearance="?textAppearanceBodySmall"
app:titleTextAppearance="?textAppearanceButton"
tools:max="100"
tools:progress="40"
tools:subtitle="12 chapters"
tools:title="@string/read" />
<ImageView
android:id="@+id/button_chapters"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_circle_button"
android:backgroundTint="?colorSecondaryContainer"
android:contentDescription="@string/chapters"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="@id/button_read"
app:layout_constraintDimensionRatio="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/button_read"
app:srcCompat="@drawable/ic_list_sheet" />
<TextView
android:id="@+id/textView_description_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/description"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_description_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_read" />
<Button
android:id="@+id/button_description_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/more"
app:layout_constraintBaseline_toBaselineOf="@id/textView_description_title"
app:layout_constraintEnd_toEndOf="parent" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_normal"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2"
android:maxLines="@integer/details_description_lines"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_description_title"
tools:ignore="UnusedAttribute"
tools:text="@tools:sample/lorem/random" />
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
android:id="@+id/chips_tags"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_small"
android:paddingStart="@dimen/screen_padding"
android:paddingEnd="@dimen/screen_padding"
app:chipSpacingHorizontal="6dp"
app:chipSpacingVertical="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_description" />
<TextView
android:id="@+id/textView_scrobbling_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/tracking"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_scrobbling_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chips_tags" />
<Button
android:id="@+id/button_scrobbling_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/manage"
app:layout_constraintBaseline_toBaselineOf="@id/textView_scrobbling_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_scrobbling"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:nestedScrollingEnabled="false"
android:orientation="vertical"
android:overScrollMode="never"
android:scrollbars="none"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_scrobbling_title"
tools:itemCount="2"
tools:listitem="@layout/item_scrobbling_info"
tools:visibility="visible" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:showAnimationBehavior="inward"
app:trackCornerRadius="0dp"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_scrobbling"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_scrobbling,textView_scrobbling_title,button_scrobbling_more"
tools:visibility="visible" />
<TextView
android:id="@+id/textView_related_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/related_manga"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_related_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recyclerView_scrobbling" />
<Button
android:id="@+id/button_related_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/show_all"
android:visibility="gone"
app:layout_constraintBaseline_toBaselineOf="@id/textView_related_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_related"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_related_title"
tools:listitem="@layout/item_manga_grid" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_related"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_related,textView_related_title,button_related_more"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@@ -1,410 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
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:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollIndicators="top"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="?colorSecondaryContainer"
android:foreground="?selectableItemBackground"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="H,13:18"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.3"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
tools:background="@tools:sample/backgrounds/scenic[5]"
tools:ignore="ContentDescription,UnusedAttribute" />
<org.koitharu.kotatsu.history.ui.util.ReadingProgressView
android:id="@+id/progressView"
android:layout_width="@dimen/card_indicator_size"
android:layout_height="@dimen/card_indicator_size"
android:layout_margin="@dimen/card_indicator_offset"
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="5"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_title"
tools:text="@tools:sample/lorem[12]" />
<TextView
android:id="@+id/textView_author"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:background="@drawable/list_selector"
android:padding="2dp"
android:singleLine="true"
android:textColor="?attr/colorTertiary"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle"
app:layout_constraintWidth_default="wrap"
tools:text="@tools:sample/full_names" />
<RatingBar
android:id="@+id/rating_bar"
style="?ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:isIndicator="true"
android:max="1"
android:numStars="5"
android:stepSize="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_author"
tools:rating="4" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:barrierMargin="8dp"
app:constraint_referenced_ids="imageView_cover,rating_bar" />
<include
android:id="@+id/info_layout"
layout="@layout/layout_details_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_header" />
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
android:id="@+id/chips_tags"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:chipSpacingHorizontal="6dp"
app:chipSpacingVertical="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/info_layout" />
<LinearLayout
android:id="@+id/approximate_read_time_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_normal"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chips_tags"
tools:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_timelapse"
app:tint="?attr/colorControlNormal" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/approximate_read_time_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingVertical="2dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyLarge"
tools:text="@string/approximate_reading_time" />
<TextView
android:id="@+id/approximate_read_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="2dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="12 minutes" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/textView_description_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/description"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_description_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/approximate_read_time_layout" />
<Button
android:id="@+id/button_description_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/more"
app:layout_constraintBaseline_toBaselineOf="@id/textView_description_title"
app:layout_constraintEnd_toEndOf="parent" />
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_normal"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2"
android:maxLines="@integer/details_description_lines"
android:paddingBottom="@dimen/margin_normal"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_description_title"
tools:ignore="UnusedAttribute"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/textView_bookmarks_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/bookmarks"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_bookmarks_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_description" />
<Button
android:id="@+id/button_bookmarks_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/show_all"
app:layout_constraintBaseline_toBaselineOf="@id/textView_bookmarks_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_bookmarks"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_bookmarks_title"
tools:listitem="@layout/item_bookmark" />
<TextView
android:id="@+id/textView_scrobbling_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/tracking"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_scrobbling_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recyclerView_bookmarks" />
<Button
android:id="@+id/button_scrobbling_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/manage"
app:layout_constraintBaseline_toBaselineOf="@id/textView_scrobbling_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_scrobbling"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:nestedScrollingEnabled="false"
android:orientation="vertical"
android:overScrollMode="never"
android:scrollbars="none"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_scrobbling_title"
tools:itemCount="2"
tools:listitem="@layout/item_scrobbling_info"
tools:visibility="visible" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:showAnimationBehavior="inward"
app:trackCornerRadius="0dp"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_bookmarks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_bookmarks,textView_bookmarks_title,button_bookmarks_more"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_scrobbling"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_scrobbling,textView_scrobbling_title,button_scrobbling_more"
tools:visibility="visible" />
<TextView
android:id="@+id/textView_related_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:text="@string/related_manga"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
app:layout_constraintEnd_toStartOf="@id/button_related_more"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recyclerView_scrobbling" />
<Button
android:id="@+id/button_related_more"
style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/show_all"
android:visibility="gone"
app:layout_constraintBaseline_toBaselineOf="@id/textView_related_title"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_related"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_related_title"
tools:listitem="@layout/item_manga_grid" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_related"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="recyclerView_related,textView_related_title,button_related_more"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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">
<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
android:id="@+id/headerBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="@string/chapters" />
<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/headerBar"
android:orientation="vertical"
android:scrollIndicators="top"
app:bubbleSize="small"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_chapter" />
</RelativeLayout>

View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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">
<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
android:id="@+id/headerBar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="240dp">
<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="@dimen/grid_spacing"
android:scrollIndicators="top"
app:bubbleSize="small"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:listitem="@layout/item_page_thumb"
tools:targetApi="m" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:showAnimationBehavior="inward"
app:trackCornerRadius="0dp"
app:trackThickness="2dp"
tools:visibility="visible" />
</FrameLayout>
</LinearLayout>

View File

@@ -3,13 +3,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_favourite"
android:icon="@drawable/ic_heart_outline"
android:orderInCategory="10"
android:title="@string/add_to_favourites"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_share"
android:icon="?actionModeShareDrawable"