New details activity and chapters sheet improvements

This commit is contained in:
Koitharu
2024-04-04 11:16:51 +03:00
parent 8174d236f6
commit 61ddee0bba
19 changed files with 180 additions and 38 deletions

View File

@@ -60,3 +60,25 @@ fun DialogFragment.showDistinct(fm: FragmentManager, tag: String) {
}
show(fm, tag)
}
tailrec fun Fragment.dismissParentDialog(): Boolean {
return when (val parent = parentFragment) {
null -> return false
is DialogFragment -> {
parent.dismiss()
true
}
else -> parent.dismissParentDialog()
}
}
@Suppress("UNCHECKED_CAST")
tailrec fun <T> Fragment.findParentCallback(cls: Class<T>): T? {
val parent = parentFragment
return when {
parent == null -> cls.castOrNull(activity)
cls.isInstance(parent) -> parent as T
else -> parent.findParentCallback(cls)
}
}

View File

@@ -19,4 +19,11 @@ data class ReadingTime(
resources.getQuantityString(R.plurals.minutes, minutes, minutes),
)
}
fun formatShort(resources: Resources): String? = when {
hours == 0 && minutes == 0 -> null
hours == 0 -> resources.getString(R.string.minutes_short, minutes)
minutes == 0 -> resources.getString(R.string.hours_short, hours)
else -> resources.getString(R.string.hours_minutes_short, hours, minutes)
}
}

View File

@@ -100,6 +100,7 @@ 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
@@ -142,6 +143,7 @@ class DetailsActivity2 :
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)
@@ -175,7 +177,7 @@ class DetailsActivity2 :
viewModel.localSize.observe(this, ::onLocalSizeChanged)
viewModel.relatedManga.observe(this, ::onRelatedMangaChanged)
// viewModel.chapters.observe(this, ::onChaptersChanged)
// viewModel.readingTime.observe(this, ::onReadingTimeChanged)
viewModel.readingTime.observe(this, ::onReadingTimeChanged)
viewModel.selectedBranch.observe(this) {
viewBinding.infoLayout.chipBranch.text = it.ifNullOrEmpty { getString(R.string.system_default) }
}
@@ -207,7 +209,7 @@ class DetailsActivity2 :
R.id.button_read -> openReader(isIncognitoMode = false)
R.id.chip_branch -> showBranchPopupMenu(v)
R.id.button_chapters -> {
ChaptersPagesSheet().showDistinct(supportFragmentManager, "ChaptersPagesSheet")
ChaptersPagesSheet.show(supportFragmentManager)
}
R.id.chip_author -> {
@@ -241,6 +243,15 @@ class DetailsActivity2 :
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(
@@ -386,7 +397,8 @@ class DetailsActivity2 :
}
private fun onReadingTimeChanged(time: ReadingTime?) {
// TODO
val chip = viewBinding.infoLayout.chipTime
chip.textAndVisible = time?.formatShort(chip.resources)
}
private fun onDescriptionChanged(description: CharSequence?) {

View File

@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.activityViewModels
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
@@ -15,6 +16,8 @@ import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
import org.koitharu.kotatsu.core.util.ext.menuView
import org.koitharu.kotatsu.core.util.ext.recyclerView
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetChaptersPagesBinding
import org.koitharu.kotatsu.details.ui.ChaptersMenuProvider2
import org.koitharu.kotatsu.details.ui.DetailsViewModel
@@ -34,13 +37,16 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
override fun onViewBindingCreated(binding: SheetChaptersPagesBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
val adapter = DetailsPagerAdapter2(this, settings)
disableFitToContents()
val args = arguments ?: Bundle.EMPTY
val adapter = DetailsPagerAdapter2(this, args.getBoolean(ARG_SHOW_PAGES, settings.isPagesTabEnabled))
binding.pager.recyclerView?.isNestedScrollingEnabled = false
binding.pager.offscreenPageLimit = adapter.itemCount
binding.pager.adapter = adapter
binding.pager.doOnPageChanged(::onPageChanged)
TabLayoutMediator(binding.tabs, binding.pager, adapter).attach()
binding.pager.setCurrentItem(settings.defaultDetailsTab, false)
binding.pager.setCurrentItem(args.getInt(ARG_TAB, settings.defaultDetailsTab), false)
binding.tabs.isVisible = adapter.itemCount > 1
val menuProvider = ChaptersMenuProvider2(viewModel, this)
@@ -80,4 +86,31 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
private fun onPageChanged(position: Int) {
viewBinding?.toolbar?.menuView?.isVisible = position == 0
}
companion object {
const val TAB_CHAPTERS = 0
const val TAB_PAGES = 1
const val TAB_BOOKMARKS = 2
private const val ARG_TAB = "tag"
private const val ARG_SHOW_PAGES = "pages"
private const val TAG = "ChaptersPagesSheet"
fun show(fm: FragmentManager) {
ChaptersPagesSheet().showDistinct(fm, TAG)
}
fun show(fm: FragmentManager, showPagesTab: Boolean) {
ChaptersPagesSheet().withArgs(1) {
putBoolean(ARG_SHOW_PAGES, showPagesTab)
}.showDistinct(fm, TAG)
}
fun show(fm: FragmentManager, showPagesTab: Boolean, defaultTab: Int) {
ChaptersPagesSheet().withArgs(2) {
putBoolean(ARG_SHOW_PAGES, showPagesTab)
putInt(ARG_TAB, defaultTab)
}.showDistinct(fm, TAG)
}
}
}

View File

@@ -12,12 +12,10 @@ import org.koitharu.kotatsu.details.ui.pager.pages.PagesFragment
class DetailsPagerAdapter2(
fragment: Fragment,
settings: AppSettings,
val isPagesTabEnabled: Boolean,
) : FragmentStateAdapter(fragment),
TabLayoutMediator.TabConfigurationStrategy {
val isPagesTabEnabled = settings.isPagesTabEnabled
override fun getItemCount(): Int = if (isPagesTabEnabled) 3 else 2
override fun createFragment(position: Int): Fragment = when (position) {

View File

@@ -15,6 +15,8 @@ import org.koitharu.kotatsu.bookmarks.ui.sheet.BookmarksAdapter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.dismissParentDialog
import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.FragmentMangaBookmarksBinding
import org.koitharu.kotatsu.details.ui.DetailsViewModel
@@ -22,6 +24,8 @@ import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
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.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
import javax.inject.Inject
@@ -96,9 +100,9 @@ class MangaBookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(),
override fun onWindowInsetsChanged(insets: Insets) = Unit
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))
val listener = findParentCallback(ReaderNavigationCallback::class.java)
if (listener != null && listener.onBookmarkSelected(item)) {
dismissParentDialog()
} else {
val intent = IntentBuilder(view.context)
.manga(activityViewModel.manga.value ?: return)

View File

@@ -22,7 +22,9 @@ import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.dismissParentDialog
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
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
@@ -38,6 +40,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback
import org.koitharu.kotatsu.reader.ui.ReaderState
import kotlin.math.roundToInt
@@ -122,12 +125,17 @@ class ChaptersFragment :
if (selectionController?.onItemClick(item.chapter.id) == true) {
return
}
startActivity(
IntentBuilder(view.context)
.manga(viewModel.manga.value ?: return)
.state(ReaderState(item.chapter.id, 0, 0))
.build(),
)
val listener = findParentCallback(ReaderNavigationCallback::class.java)
if (listener != null && listener.onChapterSelected(item.chapter)) {
dismissParentDialog()
} else {
startActivity(
IntentBuilder(view.context)
.manga(viewModel.manga.value ?: return)
.state(ReaderState(item.chapter.id, 0, 0))
.build(),
)
}
}
override fun onItemLongClick(item: ChapterListItem, view: View): Boolean {

View File

@@ -23,6 +23,8 @@ import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.dismissParentDialog
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.core.util.ext.showOrHide
@@ -33,7 +35,9 @@ 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.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.OnPageSelectListener
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
import javax.inject.Inject
@@ -130,10 +134,17 @@ class PagesFragment :
override fun onWindowInsetsChanged(insets: Insets) = Unit
override fun onItemClick(item: PageThumbnail, view: View) {
val manga = detailsViewModel.manga.value ?: return
val state = ReaderState(item.page.chapterId, item.page.index, 0)
val intent = IntentBuilder(view.context).manga(manga).state(state).build()
startActivity(intent)
val listener = findParentCallback(ReaderNavigationCallback::class.java)
if (listener != null && listener.onPageSelected(item.page)) {
dismissParentDialog()
} else {
startActivity(
IntentBuilder(view.context)
.manga(detailsViewModel.manga.value ?: return)
.state(ReaderState(item.page.chapterId, item.page.index, 0))
.build(),
)
}
}
private suspend fun onThumbnailsChanged(list: List<ListModel>) {

View File

@@ -30,6 +30,7 @@ import java.time.Instant
import javax.inject.Inject
import kotlin.math.roundToInt
@Deprecated("Use ChaptersPagesSheet instead")
@AndroidEntryPoint
class ChaptersSheet : BaseAdaptiveSheet<SheetChaptersBinding>(),
OnListItemClickListener<ChapterListItem> {
@@ -105,13 +106,13 @@ class ChaptersSheet : BaseAdaptiveSheet<SheetChaptersBinding>(),
((parentFragment as? OnChapterChangeListener)
?: (activity as? OnChapterChangeListener))?.let {
dismiss()
it.onChapterChanged(item.chapter)
it.onChapterSelected(item.chapter)
}
}
fun interface OnChapterChangeListener {
fun onChapterChanged(chapter: MangaChapter)
fun onChapterSelected(chapter: MangaChapter): Boolean
}
companion object {

View File

@@ -74,6 +74,7 @@ class ReaderActivity :
ReaderConfigSheet.Callback,
ReaderControlDelegate.OnInteractionListener,
OnApplyWindowInsetsListener,
ReaderNavigationCallback,
IdlingDetector.Callback,
ActivityResultCallback<Uri?>,
ZoomControl.ZoomControlListener {
@@ -257,11 +258,12 @@ class ReaderActivity :
return controlDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event)
}
override fun onChapterChanged(chapter: MangaChapter) {
override fun onChapterSelected(chapter: MangaChapter): Boolean {
viewModel.switchChapter(chapter.id, 0)
return true
}
override fun onPageSelected(page: ReaderPage) {
override fun onPageSelected(page: ReaderPage): Boolean {
lifecycleScope.launch(Dispatchers.Default) {
val pages = viewModel.content.value.pages
val index = pages.indexOfFirst { it.chapterId == page.chapterId && it.id == page.id }
@@ -273,6 +275,7 @@ class ReaderActivity :
viewModel.switchChapter(page.chapterId, page.index)
}
}
return true
}
override fun onReaderModeChanged(mode: ReaderMode) {

View File

@@ -6,6 +6,7 @@ import android.view.MenuItem
import androidx.core.view.MenuProvider
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
@@ -42,13 +43,7 @@ class ReaderBottomMenuProvider(
}
R.id.action_pages_thumbs -> {
val state = viewModel.getCurrentState() ?: return false
PagesThumbnailsSheet.show(
activity.supportFragmentManager,
viewModel.manga?.toManga() ?: return false,
state.chapterId,
state.page,
)
ChaptersPagesSheet.show(activity.supportFragmentManager, true, ChaptersPagesSheet.TAB_PAGES)
true
}

View File

@@ -0,0 +1,16 @@
package org.koitharu.kotatsu.reader.ui
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
interface ReaderNavigationCallback {
fun onPageSelected(page: ReaderPage): Boolean
fun onChapterSelected(chapter: MangaChapter): Boolean
fun onBookmarkSelected(bookmark: Bookmark): Boolean = onPageSelected(
ReaderPage(bookmark.toMangaPage(), bookmark.page, bookmark.chapterId),
)
}

View File

@@ -8,6 +8,7 @@ import androidx.fragment.app.FragmentActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet
class ReaderTopMenuProvider(
private val activity: FragmentActivity,
@@ -25,7 +26,7 @@ class ReaderTopMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_chapters -> {
ChaptersSheet.show(activity.supportFragmentManager)
ChaptersPagesSheet.show(activity.supportFragmentManager, true, ChaptersPagesSheet.TAB_CHAPTERS)
true
}

View File

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

View File

@@ -38,6 +38,7 @@ 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>(),

View File

@@ -2,7 +2,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View File

@@ -101,25 +101,37 @@
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_marginStart="16dp"
android:layout_marginVertical="0.5dp"
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="16dp"
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_cover"
app:layout_constraintStart_toEndOf="@id/imageView_state"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle"
app:layout_constraintWidth_default="wrap"
tools:drawableStart="@drawable/ic_state_ongoing"
tools:text="@string/state_ongoing" />
<RatingBar

View File

@@ -63,4 +63,15 @@
tools:text="English"
tools:visibility="visible" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_time"
style="@style/Widget.Kotatsu.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:chipIcon="@drawable/ic_timelapse"
app:ensureMinTouchTargetSize="false"
tools:text="2 h 40 m"
tools:visibility="visible" />
</com.google.android.material.chip.ChipGroup>

View File

@@ -643,4 +643,10 @@
<string name="error_no_data_received">No data was received from server</string>
<string name="unsupported_backup_message">Please select a proper Kotatsu backup file</string>
<string name="list_ellipsize_pattern" translatable="false">(+%d)</string>
<!-- Short hours format pattern -->
<string name="hours_short">%d h</string>
<!-- Short minutes format pattern -->
<string name="minutes_short">%d m</string>
<!-- Short hours and minutes format pattern -->
<string name="hours_minutes_short">%1$d h %2$d m</string>
</resources>