@ -9,6 +9,7 @@ import android.text.style.ForegroundColorSpan
import android.text.style.ImageSpan
import android.text.style.ImageSpan
import android.text.style.RelativeSizeSpan
import android.text.style.RelativeSizeSpan
import android.transition.TransitionManager
import android.transition.TransitionManager
import android.view.Gravity
import android.view.Menu
import android.view.Menu
import android.view.MenuItem
import android.view.MenuItem
import android.view.View
import android.view.View
@ -37,6 +38,7 @@ import coil3.request.lifecycle
import coil3.request.placeholder
import coil3.request.placeholder
import coil3.request.target
import coil3.request.target
import coil3.request.transformations
import coil3.request.transformations
import coil3.size.Precision
import coil3.size.Scale
import coil3.size.Scale
import coil3.transform.RoundedCornersTransformation
import coil3.transform.RoundedCornersTransformation
import coil3.util.CoilUtils
import coil3.util.CoilUtils
@ -55,7 +57,6 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.model.UnknownMangaSource
import org.koitharu.kotatsu.core.model.UnknownMangaSource
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.iconResId
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.os.AppShortcutManager
@ -64,8 +65,9 @@ import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
import org.koitharu.kotatsu.core.ui.image.ChipIconTarget
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.ui.image.TextViewTarget
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.sheet.BottomSheetCollapseCallback
import org.koitharu.kotatsu.core.ui.sheet.BottomSheetCollapseCallback
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
@ -86,9 +88,9 @@ import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.core.util.ext.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.databinding.LayoutDetailsTableBinding
import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.details.data.ReadingTime
import org.koitharu.kotatsu.details.data.ReadingTime
import org.koitharu.kotatsu.details.service.MangaPrefetchService
import org.koitharu.kotatsu.details.service.MangaPrefetchService
@ -112,13 +114,12 @@ import org.koitharu.kotatsu.local.ui.info.LocalInfoDialog
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
import javax.inject.Inject
import javax.inject.Inject
import kotlin.math.roundToInt
import com.google.android.material.R as materialR
import com.google.android.material.R as materialR
@AndroidEntryPoint
@AndroidEntryPoint
@ -140,30 +141,24 @@ class DetailsActivity :
private val viewModel : DetailsViewModel by viewModels ( )
private val viewModel : DetailsViewModel by viewModels ( )
private lateinit var menuProvider : DetailsMenuProvider
private lateinit var menuProvider : DetailsMenuProvider
private lateinit var infoBinding : LayoutDetailsTableBinding
override fun onCreate ( savedInstanceState : Bundle ? ) {
override fun onCreate ( savedInstanceState : Bundle ? ) {
super . onCreate ( savedInstanceState )
super . onCreate ( savedInstanceState )
setContentView ( ActivityDetailsBinding . inflate ( layoutInflater ) )
setContentView ( ActivityDetailsBinding . inflate ( layoutInflater ) )
infoBinding = LayoutDetailsTableBinding . bind ( viewBinding . root )
supportActionBar ?. run {
supportActionBar ?. run {
setDisplayHomeAsUpEnabled ( true )
setDisplayHomeAsUpEnabled ( true )
setDisplayShowTitleEnabled ( false )
setDisplayShowTitleEnabled ( false )
}
}
viewBinding . buttonRead . setOnClickListener ( this )
viewBinding . chipFavorite . setOnClickListener ( this )
viewBinding . buttonRead . setOnLongClickListener ( this )
infoBinding . textViewLocal . setOnClickListener ( this )
viewBinding . buttonRead . setOnContextClickListenerCompat ( this )
infoBinding . textViewAuthor . setOnClickListener ( this )
viewBinding . buttonDownload ?. setOnClickListener ( this )
infoBinding . textViewSource . 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 . imageViewCover . setOnClickListener ( this )
viewBinding . buttonDescriptionMore . setOnClickListener ( this )
viewBinding . buttonDescriptionMore . setOnClickListener ( this )
viewBinding . buttonScrobblingMore . setOnClickListener ( this )
viewBinding . buttonScrobblingMore . setOnClickListener ( this )
viewBinding . buttonRelatedMore . setOnClickListener ( this )
viewBinding . buttonRelatedMore . setOnClickListener ( this )
viewBinding . infoLayout . chipSource . setOnClickListener ( this )
viewBinding . infoLayout . chipSize . setOnClickListener ( this )
viewBinding . textViewDescription . addOnLayoutChangeListener ( this )
viewBinding . textViewDescription . addOnLayoutChangeListener ( this )
viewBinding . swipeRefreshLayout . setOnRefreshListener ( this )
viewBinding . swipeRefreshLayout . setOnRefreshListener ( this )
viewBinding . textViewDescription . viewTreeObserver . addOnDrawListener ( this )
viewBinding . textViewDescription . viewTreeObserver . addOnDrawListener ( this )
@ -191,17 +186,17 @@ class DetailsActivity :
viewModel . localSize . observe ( this , :: onLocalSizeChanged )
viewModel . localSize . observe ( this , :: onLocalSizeChanged )
viewModel . relatedManga . observe ( this , :: onRelatedMangaChanged )
viewModel . relatedManga . observe ( this , :: onRelatedMangaChanged )
viewModel . readingTime . observe ( this , :: onReadingTimeChanged )
viewModel . readingTime . observe ( this , :: onReadingTimeChanged )
viewModel . selectedBranch . observe ( this ) {
// viewModel.selectedBranch.observe(this) {
viewBinding . infoLayout . chipBranch . text = it . ifNullOrEmpty { getString ( R . string . system _default ) }
// viewBinding.infoLayout?.chipBranch?.text = it.ifNullOrEmpty { getString(R.string.system_default) }
}
// }
viewModel . favouriteCategories . observe ( this , :: onFavoritesChanged )
viewModel . favouriteCategories . observe ( this , :: onFavoritesChanged )
val menuInvalidator = MenuInvalidator ( this )
val menuInvalidator = MenuInvalidator ( this )
viewModel . isStatsAvailable . observe ( this , menuInvalidator )
viewModel . isStatsAvailable . observe ( this , menuInvalidator )
viewModel . remoteManga . observe ( this , menuInvalidator )
viewModel . remoteManga . observe ( this , menuInvalidator )
viewModel . branches . observe ( this ) {
// viewModel.branches.observe(this) {
viewBinding . infoLayout . chipBranch . isVisible = it . size > 1 || ! it . firstOrNull ( ) ?. name . isNullOrEmpty ( )
// viewBinding.infoLayout?.chipBranch?.isVisible = it.size > 1 || !it.firstOrNull()?.name.isNullOrEmpty( )
viewBinding . infoLayout . chipBranch . isCloseIconVisible = it . size > 1
// viewBinding.infoLayout?.chipBranch?.isCloseIconVisible = it.size > 1
}
// }
viewModel . chapters . observe ( this , PrefetchObserver ( this ) )
viewModel . chapters . observe ( this , PrefetchObserver ( this ) )
viewModel . onDownloadStarted
viewModel . onDownloadStarted
. filterNot { ChaptersPagesSheet . isShown ( supportFragmentManager ) }
. filterNot { ChaptersPagesSheet . isShown ( supportFragmentManager ) }
@ -221,14 +216,9 @@ class DetailsActivity :
override fun onClick ( v : View ) {
override fun onClick ( v : View ) {
when ( v . id ) {
when ( v . id ) {
R . id . button _read -> openReader ( isIncognitoMode = false )
// R.id.chip_branch -> showBranchPopupMenu(v)
R . id . chip _branch -> showBranchPopupMenu ( v )
R . id . button _download -> {
val manga = viewModel . manga . value ?: return
DownloadDialogFragment . show ( supportFragmentManager , listOf ( manga ) )
}
R . id . chip _author -> {
R . id . textView _author -> {
val manga = viewModel . manga . value ?: return
val manga = viewModel . manga . value ?: return
startActivity (
startActivity (
MangaListActivity . newIntent (
MangaListActivity . newIntent (
@ -239,7 +229,7 @@ class DetailsActivity :
)
)
}
}
R . id . chip _source -> {
R . id . textView _source -> {
val manga = viewModel . manga . value ?: return
val manga = viewModel . manga . value ?: return
startActivity (
startActivity (
MangaListActivity . newIntent (
MangaListActivity . newIntent (
@ -250,7 +240,7 @@ class DetailsActivity :
)
)
}
}
R . id . chip_size -> {
R . id . textView_local -> {
val manga = viewModel . manga . value ?: return
val manga = viewModel . manga . value ?: return
LocalInfoDialog . show ( supportFragmentManager , manga )
LocalInfoDialog . show ( supportFragmentManager , manga )
}
}
@ -260,14 +250,14 @@ class DetailsActivity :
FavoriteSheet . show ( supportFragmentManager , manga )
FavoriteSheet . show ( supportFragmentManager , manga )
}
}
R . id . chip _time -> {
// R.id.chip_time -> {
if ( viewModel . isStatsAvailable . value ) {
// if (viewModel.isStatsAvailable.value) {
val manga = viewModel . manga . value ?: return
// val manga = viewModel.manga.value ?: return
MangaStatsSheet . show ( supportFragmentManager , manga )
// MangaStatsSheet.show(supportFragmentManager, manga)
} else {
// } else {
// TODO
// // TODO
}
// }
}
// }
R . id . imageView _cover -> {
R . id . imageView _cover -> {
val manga = viewModel . manga . value ?: return
val manga = viewModel . manga . value ?: return
@ -378,7 +368,7 @@ class DetailsActivity :
}
}
private fun onFavoritesChanged ( categories : Set < FavouriteCategory > ) {
private fun onFavoritesChanged ( categories : Set < FavouriteCategory > ) {
val chip = viewBinding . infoLayout. chipFavorite
val chip = viewBinding . chipFavorite
chip . setChipIconResource ( if ( categories . isEmpty ( ) ) R . drawable . ic _heart _outline else R . drawable . ic _heart )
chip . setChipIconResource ( if ( categories . isEmpty ( ) ) R . drawable . ic _heart _outline else R . drawable . ic _heart )
chip . text = if ( categories . isEmpty ( ) ) {
chip . text = if ( categories . isEmpty ( ) ) {
getString ( R . string . add _to _favourites )
getString ( R . string . add _to _favourites )
@ -388,17 +378,18 @@ class DetailsActivity :
}
}
private fun onReadingTimeChanged ( time : ReadingTime ? ) {
private fun onReadingTimeChanged ( time : ReadingTime ? ) {
val chip = viewBinding . infoLayout . chipTime
// TODO
chip . textAndVisible = time ?. formatShort ( chip . resources )
// chip.textAndVisible = time?.formatShort(chip.resources )
}
}
private fun onLocalSizeChanged ( size : Long ) {
private fun onLocalSizeChanged ( size : Long ) {
val chip = viewBinding . infoLayout . chipSize
if ( size == 0L ) {
if ( size == 0L ) {
chip . isVisible = false
infoBinding . textViewLocal . isVisible = false
infoBinding . textViewLocalLabel . isVisible = false
} else {
} else {
chip . text = FileSize . BYTES . format ( chip . context , size )
infoBinding . textViewLocal . text = FileSize . BYTES . format ( this , size )
chip . isVisible = true
infoBinding . textViewLocal . isVisible = true
infoBinding . textViewLocalLabel . isVisible = true
}
}
}
}
@ -442,63 +433,60 @@ class DetailsActivity :
}
}
private fun onMangaUpdated ( details : MangaDetails ) {
private fun onMangaUpdated ( details : MangaDetails ) {
with ( viewBinding ) {
val manga = details . toManga ( )
val manga = details . toManga ( )
// Main
loadCover ( manga )
loadCover ( manga )
with ( viewBinding ) {
textViewTitle . text = manga . title
textViewTitle . text = manga . title
textViewSubtitle . textAndVisible = manga . altTitle
textViewSubtitle . textAndVisible = manga . altTitle
infoLayout . chipAuthor . textAndVisible = manga . author ?. ellipsize ( AUTHOR _LABEL _LIMIT )
textViewNsfw . isVisible = manga . isNsfw
textViewDescription . text = details . description . ifNullOrEmpty { getString ( R . string . no _description ) }
}
with ( infoBinding ) {
textViewAuthor . textAndVisible = manga . author
textViewAuthorLabel . isVisible = textViewAuthor . isVisible
if ( manga . hasRating ) {
if ( manga . hasRating ) {
ratingBar . rating = manga . rating * ratingBar . numStars
ratingBarRating . rating = manga . rating * ratingBarRating . numStars
ratingBar . isVisible = true
ratingBarRating . isVisible = true
textViewRatingLabel . isVisible = true
} else {
} else {
ratingBar . isVisible = false
ratingBarRating . isVisible = false
textViewRatingLabel . isVisible = false
}
}
manga . state ?. let { state ->
manga . state ?. let { state ->
textViewState . textAndVisible = resources . getString ( state . titleResId )
textViewState . textAndVisible = resources . getString ( state . titleResId )
imageViewState . setImageResource ( state . iconResId )
textViewStateLabel . isVisible = textViewState . isVisible
imageViewState . isVisible = true
} ?: run {
} ?: run {
textViewState . isVisible = false
textViewState . isVisible = false
imageViewState . isVisible = false
textViewStateLabel . isVisible = false
}
}
if ( manga . source == LocalMangaSource || manga . source == UnknownMangaSource ) {
if ( manga . source == LocalMangaSource || manga . source == UnknownMangaSource ) {
infoLayout . chipSource . isVisible = false
textViewSource . isVisible = false
textViewSourceLabel . isVisible = false
} else {
} else {
infoLayout. chipSource . text = manga . source . getTitle ( this @DetailsActivity )
textViewSource. textAndVisible = manga . source . getTitle ( this @DetailsActivity )
infoLayout. chipSource . isVisible = true
textViewSourceLabel. isVisible = textViewSource . isVisible = = true
}
}
val faviconPlaceholderFactory = FaviconDrawable . Factory ( R . style . FaviconDrawable _Chip )
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 )
ImageRequest . Builder ( this @DetailsActivity )
. data ( manga . source . faviconUri ( ) )
. data ( manga . source . faviconUri ( ) )
. lifecycle ( this @DetailsActivity )
. lifecycle ( this @DetailsActivity )
. crossfade ( false )
. crossfade ( false )
. precision ( Precision . EXACT )
. size ( resources . getDimensionPixelSize ( materialR . dimen . m3 _chip _icon _size ) )
. size ( resources . getDimensionPixelSize ( materialR . dimen . m3 _chip _icon _size ) )
. target ( ChipIconTarget ( chip ) )
. target ( TextViewTarget ( textViewSource , Gravity . START ) )
. placeholder ( R . drawable . ic _web )
. placeholder ( faviconPlaceholderFactory )
. fallback ( R . drawable . ic _web )
. error ( faviconPlaceholderFactory )
. error ( R . drawable . ic _web )
. fallback ( faviconPlaceholderFactory )
. mangaSourceExtra ( manga . source )
. mangaSourceExtra ( manga . source )
. transformations ( RoundedCornersTransformation ( resources . getDimension ( R . dimen . chip _icon _corner ) ) )
. transformations ( RoundedCornersTransformation ( resources . getDimension ( R . dimen . chip _icon _corner ) ) )
. allowRgb565 ( true )
. allowRgb565 ( true )
. enqueueWith ( coil )
. enqueueWith ( coil )
}
}
bindTags ( manga )
title = manga . title
title = manga . title
invalidateOptionsMenu ( )
invalidateOptionsMenu ( )
}
}
}
private fun onMangaRemoved ( manga : Manga ) {
private fun onMangaRemoved ( manga : Manga ) {
Toast . makeText (
Toast . makeText (
@ -527,22 +515,28 @@ class DetailsActivity :
}
}
}
}
private fun onHistoryChanged ( info : HistoryInfo , isLoading : Boolean ) = with ( viewBinding ) {
private fun onHistoryChanged ( info : HistoryInfo , isLoading : Boolean ) = with ( infoBinding ) {
buttonRead . setTitle ( if ( info . canContinue ) R . string . _continue else R . string . read )
textViewChapters . textAndVisible = when {
buttonRead . subtitle = when {
isLoading -> null
isLoading -> getString ( R . string . loading _ )
info . isIncognitoMode -> getString ( R . string . incognito _mode )
info . isChapterMissing -> getString ( R . string . chapter _is _missing )
info . currentChapter >= 0 -> getString ( R . string . chapter _d _of _d , info . currentChapter + 1 , info . totalChapters )
info . currentChapter >= 0 -> getString ( R . string . chapter _d _of _d , info . currentChapter + 1 , info . totalChapters )
info . totalChapters == 0 -> getString ( R . string . no _chapters )
info . totalChapters == 0 -> getString ( R . string . no _chapters )
info . totalChapters == - 1 -> getString ( R . string . error _occurred )
info . totalChapters == - 1 -> getString ( R . string . error _occurred )
else -> resources . getQuantityString ( R . plurals . chapters , info . totalChapters , info . totalChapters )
else -> resources . getQuantityString ( R . plurals . chapters , info . totalChapters , info . totalChapters )
}
}
val isFirstCall = buttonRead . tag == null
textViewProgress . textAndVisible = if ( info . percent <= 0f ) {
buttonRead . tag = Unit
null
buttonRead . setProgress ( info . percent . coerceIn ( 0f , 1f ) , !is FirstCall )
} else {
buttonDownload ?. isEnabled = info . isValid && info . canDownload
getString ( R . string . percent _string _pattern , ( info . percent * 100f ) . toInt ( ) . toString ( ) )
buttonRead . isEnabled = info . isValid
}
progress . setProgressCompat (
( progress . max * info . percent . coerceIn ( 0f , 1f ) ) . roundToInt ( ) ,
true ,
)
textViewProgressLabel . isVisible = info . history != null
textViewProgress . isVisible = info . history != null
progress . isVisible = info . history != null
// buttonRead.setProgress(info.percent.coerceIn(0f, 1f), !isFirstCall)
// buttonDownload?.isEnabled = info.isValid && info.canDownload
}
}
private fun showBranchPopupMenu ( v : View ) {
private fun showBranchPopupMenu ( v : View ) {
@ -663,7 +657,6 @@ class DetailsActivity :
companion object {
companion object {
private const val FAV _LABEL _LIMIT = 16
private const val FAV _LABEL _LIMIT = 16
private const val AUTHOR _LABEL _LIMIT = 16
fun newIntent ( context : Context , manga : Manga ) : Intent {
fun newIntent ( context : Context , manga : Manga ) : Intent {
return Intent ( context , DetailsActivity :: class . java )
return Intent ( context , DetailsActivity :: class . java )