Improve details activity
This commit is contained in:
@@ -20,7 +20,7 @@ abstract class BaseBrowserActivity : BaseActivity<ActivityBrowserBinding>(), Bro
|
|||||||
|
|
||||||
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
|
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
|
||||||
|
|
||||||
final override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
if (!setContentViewWebViewSafe { ActivityBrowserBinding.inflate(layoutInflater) }) {
|
if (!setContentViewWebViewSafe { ActivityBrowserBinding.inflate(layoutInflater) }) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.koitharu.kotatsu.core.image
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.View
|
||||||
|
import androidx.collection.ArrayMap
|
||||||
|
import coil3.memory.MemoryCache
|
||||||
|
import coil3.request.SuccessResult
|
||||||
|
import coil3.util.CoilUtils
|
||||||
|
import kotlinx.parcelize.Parceler
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class CoilMemoryCacheKey(
|
||||||
|
val data: MemoryCache.Key
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
companion object : Parceler<CoilMemoryCacheKey> {
|
||||||
|
override fun CoilMemoryCacheKey.write(parcel: Parcel, flags: Int) = with(data) {
|
||||||
|
parcel.writeString(key)
|
||||||
|
parcel.writeInt(extras.size)
|
||||||
|
for (entry in extras.entries) {
|
||||||
|
parcel.writeString(entry.key)
|
||||||
|
parcel.writeString(entry.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(parcel: Parcel): CoilMemoryCacheKey = CoilMemoryCacheKey(
|
||||||
|
MemoryCache.Key(
|
||||||
|
key = parcel.readString().orEmpty(),
|
||||||
|
extras = run {
|
||||||
|
val size = parcel.readInt()
|
||||||
|
val map = ArrayMap<String, String>(size)
|
||||||
|
repeat(size) {
|
||||||
|
map.put(parcel.readString(), parcel.readString())
|
||||||
|
}
|
||||||
|
map
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun from(view: View): CoilMemoryCacheKey? {
|
||||||
|
return (CoilUtils.result(view) as? SuccessResult)?.memoryCacheKey?.let {
|
||||||
|
CoilMemoryCacheKey(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import org.koitharu.kotatsu.core.parser.external.ExternalMangaSource
|
|||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayName
|
import org.koitharu.kotatsu.core.util.ext.getDisplayName
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
import org.koitharu.kotatsu.core.util.ext.toLocale
|
import org.koitharu.kotatsu.core.util.ext.toLocale
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.toLocaleOrNull
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
@@ -80,7 +81,7 @@ tailrec fun MangaSource.unwrap(): MangaSource = if (this is MangaSourceInfo) {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MangaSource.getLocale(): Locale? = (unwrap() as? MangaParserSource)?.locale?.toLocale()
|
fun MangaSource.getLocale(): Locale? = (unwrap() as? MangaParserSource)?.locale?.toLocaleOrNull()
|
||||||
|
|
||||||
fun MangaSource.getSummary(context: Context): String? = when (val source = unwrap()) {
|
fun MangaSource.getSummary(context: Context): String? = when (val source = unwrap()) {
|
||||||
is MangaParserSource -> {
|
is MangaParserSource -> {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity
|
|||||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||||
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
|
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
|
||||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||||
|
import org.koitharu.kotatsu.core.image.CoilMemoryCacheKey
|
||||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
import org.koitharu.kotatsu.core.model.MangaSourceInfo
|
import org.koitharu.kotatsu.core.model.MangaSourceInfo
|
||||||
import org.koitharu.kotatsu.core.model.appUrl
|
import org.koitharu.kotatsu.core.model.appUrl
|
||||||
@@ -180,11 +181,12 @@ class AppRouter private constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openImage(url: String, source: MangaSource?, anchor: View? = null) {
|
fun openImage(url: String, source: MangaSource?, anchor: View? = null, preview: CoilMemoryCacheKey? = null) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(contextOrNull(), ImageActivity::class.java)
|
Intent(contextOrNull(), ImageActivity::class.java)
|
||||||
.setData(url.toUri())
|
.setData(url.toUri())
|
||||||
.putExtra(KEY_SOURCE, source?.name),
|
.putExtra(KEY_SOURCE, source?.name)
|
||||||
|
.putExtra(KEY_PREVIEW, preview),
|
||||||
anchor?.let { scaleUpActivityOptionsOf(it) },
|
anchor?.let { scaleUpActivityOptionsOf(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -768,6 +770,7 @@ class AppRouter private constructor(
|
|||||||
const val KEY_MANGA = "manga"
|
const val KEY_MANGA = "manga"
|
||||||
const val KEY_MANGA_LIST = "manga_list"
|
const val KEY_MANGA_LIST = "manga_list"
|
||||||
const val KEY_PAGES = "pages"
|
const val KEY_PAGES = "pages"
|
||||||
|
const val KEY_PREVIEW = "preview"
|
||||||
const val KEY_QUERY = "query"
|
const val KEY_QUERY = "query"
|
||||||
const val KEY_READER_MODE = "reader_mode"
|
const val KEY_READER_MODE = "reader_mode"
|
||||||
const val KEY_SORT_ORDER = "sort_order"
|
const val KEY_SORT_ORDER = "sort_order"
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ inline fun <T> LocaleListCompat.mapToSet(block: (Locale) -> T): Set<T> {
|
|||||||
|
|
||||||
fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException()
|
fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException()
|
||||||
|
|
||||||
fun String.toLocale() = Locale(this)
|
fun String.toLocale(): Locale = Locale.forLanguageTag(this)
|
||||||
|
|
||||||
|
fun String.toLocaleOrNull() = if (isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
toLocale().takeUnless { it.displayName == this }
|
||||||
|
}
|
||||||
|
|
||||||
fun Locale?.getDisplayName(context: Context): String = when (this) {
|
fun Locale?.getDisplayName(context: Context): String = when (this) {
|
||||||
null -> context.getString(R.string.all_languages)
|
null -> context.getString(R.string.all_languages)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.details.data
|
package org.koitharu.kotatsu.details.data
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.core.model.getLocale
|
||||||
import org.koitharu.kotatsu.core.model.isLocal
|
import org.koitharu.kotatsu.core.model.isLocal
|
||||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
@@ -7,6 +8,7 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
|
|||||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||||
import org.koitharu.kotatsu.reader.data.filterChapters
|
import org.koitharu.kotatsu.reader.data.filterChapters
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
data class MangaDetails(
|
data class MangaDetails(
|
||||||
private val manga: Manga,
|
private val manga: Manga,
|
||||||
@@ -39,6 +41,13 @@ data class MangaDetails(
|
|||||||
|
|
||||||
fun toManga() = manga
|
fun toManga() = manga
|
||||||
|
|
||||||
|
fun getLocale(): Locale? {
|
||||||
|
findAppropriateLocale(chapters.keys.singleOrNull())?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
return manga.source.getLocale()
|
||||||
|
}
|
||||||
|
|
||||||
fun filterChapters(branch: String?) = MangaDetails(
|
fun filterChapters(branch: String?) = MangaDetails(
|
||||||
manga = manga.filterChapters(branch),
|
manga = manga.filterChapters(branch),
|
||||||
localManga = localManga?.run {
|
localManga = localManga?.run {
|
||||||
@@ -69,4 +78,16 @@ data class MangaDetails(
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findAppropriateLocale(name: String?): Locale? {
|
||||||
|
if (name.isNullOrEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return Locale.getAvailableLocales().find { lc ->
|
||||||
|
name.contains(lc.getDisplayName(lc), ignoreCase = true) ||
|
||||||
|
name.contains(lc.getDisplayName(Locale.ENGLISH), ignoreCase = true) ||
|
||||||
|
name.contains(lc.getDisplayLanguage(lc), ignoreCase = true) ||
|
||||||
|
name.contains(lc.getDisplayLanguage(Locale.ENGLISH), ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import kotlinx.coroutines.flow.filterNotNull
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||||
|
import org.koitharu.kotatsu.core.image.CoilMemoryCacheKey
|
||||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
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
|
||||||
@@ -100,10 +101,12 @@ import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
|
|||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.MangaListModel
|
import org.koitharu.kotatsu.list.ui.model.MangaListModel
|
||||||
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
|
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
|
||||||
|
import org.koitharu.kotatsu.parsers.model.ContentRating
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||||
|
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -179,16 +182,6 @@ class DetailsActivity :
|
|||||||
viewModel.isStatsAvailable.observe(this, menuInvalidator)
|
viewModel.isStatsAvailable.observe(this, menuInvalidator)
|
||||||
viewModel.remoteManga.observe(this, menuInvalidator)
|
viewModel.remoteManga.observe(this, menuInvalidator)
|
||||||
viewModel.tags.observe(this, ::onTagsChanged)
|
viewModel.tags.observe(this, ::onTagsChanged)
|
||||||
viewModel.branches.observe(this) {
|
|
||||||
val branch = it.singleOrNull()
|
|
||||||
infoBinding.textViewTranslation.textAndVisible = branch?.name
|
|
||||||
infoBinding.textViewTranslation.drawableStart = branch?.locale?.let {
|
|
||||||
LocaleUtils.getEmojiFlag(it)
|
|
||||||
}?.let {
|
|
||||||
TextDrawable.compound(infoBinding.textViewTranslation, it)
|
|
||||||
}
|
|
||||||
infoBinding.textViewTranslationLabel.isVisible = infoBinding.textViewTranslation.isVisible
|
|
||||||
}
|
|
||||||
viewModel.chapters.observe(this, PrefetchObserver(this))
|
viewModel.chapters.observe(this, PrefetchObserver(this))
|
||||||
viewModel.onDownloadStarted
|
viewModel.onDownloadStarted
|
||||||
.filterNot { appRouter.isChapterPagesSheetShown() }
|
.filterNot { appRouter.isChapterPagesSheetShown() }
|
||||||
@@ -202,7 +195,7 @@ class DetailsActivity :
|
|||||||
addMenuProvider(menuProvider)
|
addMenuProvider(menuProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isNsfwContent(): Flow<Boolean> = viewModel.manga.map { it?.isNsfw == true }
|
override fun isNsfwContent(): Flow<Boolean> = viewModel.manga.map { it?.contentRating == ContentRating.ADULT }
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
@@ -232,6 +225,7 @@ class DetailsActivity :
|
|||||||
router.openImage(
|
router.openImage(
|
||||||
url = viewModel.coverUrl.value ?: return,
|
url = viewModel.coverUrl.value ?: return,
|
||||||
source = manga.source,
|
source = manga.source,
|
||||||
|
preview = CoilMemoryCacheKey.from(viewBinding.imageViewCover),
|
||||||
anchor = v,
|
anchor = v,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -407,10 +401,20 @@ class DetailsActivity :
|
|||||||
with(viewBinding) {
|
with(viewBinding) {
|
||||||
textViewTitle.text = manga.title
|
textViewTitle.text = manga.title
|
||||||
textViewSubtitle.textAndVisible = manga.altTitles.joinToString("\n")
|
textViewSubtitle.textAndVisible = manga.altTitles.joinToString("\n")
|
||||||
textViewNsfw.isVisible = manga.isNsfw
|
textViewNsfw16.isVisible = manga.contentRating == ContentRating.SUGGESTIVE
|
||||||
|
textViewNsfw18.isVisible = manga.contentRating == ContentRating.ADULT
|
||||||
textViewDescription.text = details.description.ifNullOrEmpty { getString(R.string.no_description) }
|
textViewDescription.text = details.description.ifNullOrEmpty { getString(R.string.no_description) }
|
||||||
}
|
}
|
||||||
with(infoBinding) {
|
with(infoBinding) {
|
||||||
|
val translation = details.getLocale()
|
||||||
|
infoBinding.textViewTranslation.textAndVisible = translation?.getDisplayLanguage(translation)
|
||||||
|
?.toTitleCase(translation)
|
||||||
|
infoBinding.textViewTranslation.drawableStart = translation?.let {
|
||||||
|
LocaleUtils.getEmojiFlag(it)
|
||||||
|
}?.let {
|
||||||
|
TextDrawable.compound(infoBinding.textViewTranslation, it)
|
||||||
|
}
|
||||||
|
infoBinding.textViewTranslationLabel.isVisible = infoBinding.textViewTranslation.isVisible
|
||||||
textViewAuthor.textAndVisible = manga.author
|
textViewAuthor.textAndVisible = manga.author
|
||||||
textViewAuthorLabel.isVisible = textViewAuthor.isVisible
|
textViewAuthorLabel.isVisible = textViewAuthor.isVisible
|
||||||
if (manga.hasRating) {
|
if (manga.hasRating) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.details.ui.model
|
|||||||
|
|
||||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
data class MangaBranch(
|
data class MangaBranch(
|
||||||
val name: String?,
|
val name: String?,
|
||||||
@@ -11,8 +10,6 @@ data class MangaBranch(
|
|||||||
val isCurrent: Boolean,
|
val isCurrent: Boolean,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|
||||||
val locale: Locale? by lazy(::findAppropriateLocale)
|
|
||||||
|
|
||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is MangaBranch && other.name == name
|
return other is MangaBranch && other.name == name
|
||||||
}
|
}
|
||||||
@@ -28,16 +25,4 @@ data class MangaBranch(
|
|||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$name: $count"
|
return "$name: $count"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findAppropriateLocale(): Locale? {
|
|
||||||
if (name.isNullOrEmpty()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return Locale.getAvailableLocales().find { lc ->
|
|
||||||
name.contains(lc.getDisplayName(lc), ignoreCase = true) ||
|
|
||||||
name.contains(lc.getDisplayName(Locale.ENGLISH), ignoreCase = true) ||
|
|
||||||
name.contains(lc.getDisplayLanguage(lc), ignoreCase = true) ||
|
|
||||||
name.contains(lc.getDisplayLanguage(Locale.ENGLISH), ignoreCase = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class BookmarksViewModel @Inject constructor(
|
|||||||
if (b.isNullOrEmpty()) {
|
if (b.isNullOrEmpty()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result += ListHeader(chapter.name)
|
result += ListHeader(chapter)
|
||||||
result.addAll(b)
|
result.addAll(b)
|
||||||
}
|
}
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class PagesViewModel @Inject constructor(
|
|||||||
for (page in snapshot) {
|
for (page in snapshot) {
|
||||||
if (page.chapterId != previousChapterId) {
|
if (page.chapterId != previousChapterId) {
|
||||||
chaptersLoader.peekChapter(page.chapterId)?.let {
|
chaptersLoader.peekChapter(page.chapterId)?.let {
|
||||||
add(ListHeader(it.name))
|
add(ListHeader(it))
|
||||||
}
|
}
|
||||||
previousChapterId = page.chapterId
|
previousChapterId = page.chapterId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,21 +11,20 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
import coil3.Image
|
|
||||||
import coil3.ImageLoader
|
import coil3.ImageLoader
|
||||||
import coil3.asDrawable
|
|
||||||
import coil3.request.CachePolicy
|
import coil3.request.CachePolicy
|
||||||
import coil3.request.ErrorResult
|
import coil3.request.ErrorResult
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import coil3.request.SuccessResult
|
import coil3.request.SuccessResult
|
||||||
import coil3.request.lifecycle
|
import coil3.request.lifecycle
|
||||||
import coil3.target.ViewTarget
|
import coil3.target.GenericViewTarget
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
|
import org.koitharu.kotatsu.core.image.CoilMemoryCacheKey
|
||||||
import org.koitharu.kotatsu.core.model.MangaSource
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
@@ -36,6 +35,7 @@ import org.koitharu.kotatsu.core.util.ext.end
|
|||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayIcon
|
import org.koitharu.kotatsu.core.util.ext.getDisplayIcon
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
@@ -63,7 +63,6 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
|||||||
setContentView(ActivityImageBinding.inflate(layoutInflater))
|
setContentView(ActivityImageBinding.inflate(layoutInflater))
|
||||||
viewBinding.buttonBack.setOnClickListener(this)
|
viewBinding.buttonBack.setOnClickListener(this)
|
||||||
viewBinding.buttonMenu.setOnClickListener(this)
|
viewBinding.buttonMenu.setOnClickListener(this)
|
||||||
val imageUrl = requireNotNull(intent.data)
|
|
||||||
|
|
||||||
val menuProvider = ImageMenuProvider(
|
val menuProvider = ImageMenuProvider(
|
||||||
activity = this,
|
activity = this,
|
||||||
@@ -74,14 +73,14 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
|||||||
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
|
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
|
||||||
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.root, null))
|
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.root, null))
|
||||||
viewModel.onImageSaved.observeEvent(this, ::onImageSaved)
|
viewModel.onImageSaved.observeEvent(this, ::onImageSaved)
|
||||||
loadImage(imageUrl)
|
loadImage()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.button_back -> dispatchNavigateUp()
|
R.id.button_back -> dispatchNavigateUp()
|
||||||
R.id.button_menu -> menuMediator.onLongClick(v)
|
R.id.button_menu -> menuMediator.onLongClick(v)
|
||||||
else -> loadImage(intent.data)
|
else -> loadImage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,10 +121,11 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
|||||||
return insets.consumeAll(typeMask)
|
return insets.consumeAll(typeMask)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadImage(url: Uri?) {
|
private fun loadImage() {
|
||||||
ImageRequest.Builder(this)
|
ImageRequest.Builder(this)
|
||||||
.data(url)
|
.data(intent.data)
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
.memoryCacheKey(intent.getParcelableExtraCompat<CoilMemoryCacheKey>(AppRouter.KEY_PREVIEW)?.data)
|
||||||
|
.memoryCachePolicy(CachePolicy.READ_ONLY)
|
||||||
.lifecycle(this)
|
.lifecycle(this)
|
||||||
.listener(this)
|
.listener(this)
|
||||||
.mangaSourceExtra(MangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE)))
|
.mangaSourceExtra(MangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE)))
|
||||||
@@ -158,11 +158,13 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
|||||||
|
|
||||||
private class SsivTarget(
|
private class SsivTarget(
|
||||||
override val view: SubsamplingScaleImageView,
|
override val view: SubsamplingScaleImageView,
|
||||||
) : ViewTarget<SubsamplingScaleImageView> {
|
) : GenericViewTarget<SubsamplingScaleImageView>() {
|
||||||
|
|
||||||
override fun onError(error: Image?) = setDrawable(error?.asDrawable(view.resources))
|
override var drawable: Drawable? = null
|
||||||
|
set(value) {
|
||||||
override fun onSuccess(result: Image) = setDrawable(result.asDrawable(view.resources))
|
field = value
|
||||||
|
setImageDrawable(value)
|
||||||
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return (this === other) || (other is SsivTarget && view == other.view)
|
return (this === other) || (other is SsivTarget && view == other.view)
|
||||||
@@ -172,7 +174,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
|||||||
|
|
||||||
override fun toString() = "SsivTarget(view=$view)"
|
override fun toString() = "SsivTarget(view=$view)"
|
||||||
|
|
||||||
private fun setDrawable(drawable: Drawable?) {
|
private fun setImageDrawable(drawable: Drawable?) {
|
||||||
if (drawable != null) {
|
if (drawable != null) {
|
||||||
view.setImage(ImageSource.bitmap(drawable.toBitmap()))
|
view.setImage(ImageSource.bitmap(drawable.toBitmap()))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package org.koitharu.kotatsu.list.ui.model
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import org.koitharu.kotatsu.core.model.getLocalizedTitle
|
||||||
import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
|
import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
|
|
||||||
|
@ConsistentCopyVisibility
|
||||||
data class ListHeader private constructor(
|
data class ListHeader private constructor(
|
||||||
private val textRaw: Any,
|
private val textRaw: Any,
|
||||||
@StringRes val buttonTextRes: Int,
|
@StringRes val buttonTextRes: Int,
|
||||||
@@ -25,6 +28,13 @@ data class ListHeader private constructor(
|
|||||||
badge: String? = null,
|
badge: String? = null,
|
||||||
) : this(textRaw = textRes, buttonTextRes, payload, badge)
|
) : this(textRaw = textRes, buttonTextRes, payload, badge)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
chapter: MangaChapter,
|
||||||
|
@StringRes buttonTextRes: Int = 0,
|
||||||
|
payload: Any? = null,
|
||||||
|
badge: String? = null,
|
||||||
|
) : this(textRaw = chapter, buttonTextRes, payload, badge)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
dateTimeAgo: DateTimeAgo,
|
dateTimeAgo: DateTimeAgo,
|
||||||
@StringRes buttonTextRes: Int = 0,
|
@StringRes buttonTextRes: Int = 0,
|
||||||
@@ -36,6 +46,7 @@ data class ListHeader private constructor(
|
|||||||
is CharSequence -> textRaw
|
is CharSequence -> textRaw
|
||||||
is Int -> if (textRaw != 0) context.getString(textRaw) else null
|
is Int -> if (textRaw != 0) context.getString(textRaw) else null
|
||||||
is DateTimeAgo -> textRaw.format(context)
|
is DateTimeAgo -> textRaw.format(context)
|
||||||
|
is MangaChapter -> textRaw.getLocalizedTitle(context.resources)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE
|
|||||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||||
import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
|
import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
|
||||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.ContentRating
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||||
@@ -177,7 +178,7 @@ class ReaderViewModel @Inject constructor(
|
|||||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null),
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null),
|
||||||
)
|
)
|
||||||
|
|
||||||
val isMangaNsfw = manga.map { it?.isNsfw == true }
|
val isMangaNsfw = manga.map { it?.contentRating == ContentRating.ADULT }
|
||||||
|
|
||||||
val isBookmarkAdded = readingState.flatMapLatest { state ->
|
val isBookmarkAdded = readingState.flatMapLatest { state ->
|
||||||
val manga = mangaDetails.value?.toManga()
|
val manga = mangaDetails.value?.toManga()
|
||||||
|
|||||||
@@ -74,19 +74,27 @@
|
|||||||
tools:ignore="ContentDescription,UnusedAttribute" />
|
tools:ignore="ContentDescription,UnusedAttribute" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView_nsfw"
|
android:id="@+id/textView_nsfw_16"
|
||||||
|
style="@style/Widget.Kotatsu.TextView.Badge"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="@dimen/card_indicator_offset"
|
android:layout_margin="@dimen/card_indicator_offset"
|
||||||
android:background="@drawable/bg_chip"
|
android:backgroundTint="@color/nsfw_16"
|
||||||
android:backgroundTint="@color/warning"
|
android:text="@string/nsfw_16"
|
||||||
android:gravity="center"
|
android:visibility="gone"
|
||||||
android:paddingHorizontal="4dp"
|
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||||
android:paddingVertical="2dp"
|
app:layout_constraintEnd_toEndOf="@id/imageView_cover"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_nsfw_18"
|
||||||
|
style="@style/Widget.Kotatsu.TextView.Badge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/card_indicator_offset"
|
||||||
|
android:backgroundTint="@color/nsfw_18"
|
||||||
android:text="@string/nsfw"
|
android:text="@string/nsfw"
|
||||||
android:textAlignment="center"
|
android:visibility="gone"
|
||||||
android:textAppearance="?textAppearanceLabelMedium"
|
|
||||||
android:textColor="?colorOnError"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||||
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
|
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
|
||||||
|
|
||||||
|
|||||||
@@ -66,19 +66,27 @@
|
|||||||
tools:ignore="ContentDescription,UnusedAttribute" />
|
tools:ignore="ContentDescription,UnusedAttribute" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView_nsfw"
|
android:id="@+id/textView_nsfw_16"
|
||||||
|
style="@style/Widget.Kotatsu.TextView.Badge"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="@dimen/card_indicator_offset"
|
android:layout_margin="@dimen/card_indicator_offset"
|
||||||
android:background="@drawable/bg_chip"
|
android:backgroundTint="@color/nsfw_16"
|
||||||
android:backgroundTint="@color/warning"
|
android:text="@string/nsfw_16"
|
||||||
android:gravity="center"
|
android:visibility="gone"
|
||||||
android:paddingHorizontal="4dp"
|
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||||
android:paddingVertical="2dp"
|
app:layout_constraintEnd_toEndOf="@id/imageView_cover"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_nsfw_18"
|
||||||
|
style="@style/Widget.Kotatsu.TextView.Badge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/card_indicator_offset"
|
||||||
|
android:backgroundTint="@color/nsfw_18"
|
||||||
android:text="@string/nsfw"
|
android:text="@string/nsfw"
|
||||||
android:textAlignment="center"
|
android:visibility="gone"
|
||||||
android:textAppearance="?textAppearanceLabelMedium"
|
|
||||||
android:textColor="?colorOnError"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||||
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
|
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
<color name="common_green">#81C784</color>
|
<color name="common_green">#81C784</color>
|
||||||
<color name="common_red">#E57373</color>
|
<color name="common_red">#E57373</color>
|
||||||
<color name="dim2">#C8000000</color>
|
<color name="dim2">#C8000000</color>
|
||||||
|
<color name="nsfw_18">#BF360C</color>
|
||||||
|
<color name="nsfw_16">#FF6F00</color>
|
||||||
|
|
||||||
<!-- Color schemes colors -->
|
<!-- Color schemes colors -->
|
||||||
<color name="background_miku">#191C1C</color>
|
<color name="background_miku">#191C1C</color>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
<color name="launcher_background">#FFFFFF</color>
|
<color name="launcher_background">#FFFFFF</color>
|
||||||
<color name="common_green">#388E3C</color>
|
<color name="common_green">#388E3C</color>
|
||||||
<color name="common_red">#D32F2F</color>
|
<color name="common_red">#D32F2F</color>
|
||||||
|
<color name="nsfw_18">#FF8A65</color>
|
||||||
|
<color name="nsfw_16">#FFD54F</color>
|
||||||
|
|
||||||
<!-- Color schemes colors -->
|
<!-- Color schemes colors -->
|
||||||
<color name="background_miku">#F7FAF8</color>
|
<color name="background_miku">#F7FAF8</color>
|
||||||
|
|||||||
@@ -215,6 +215,7 @@
|
|||||||
<string name="preload_pages">Preload pages</string>
|
<string name="preload_pages">Preload pages</string>
|
||||||
<string name="logged_in_as">Logged in as %s</string>
|
<string name="logged_in_as">Logged in as %s</string>
|
||||||
<string name="nsfw">18+</string>
|
<string name="nsfw">18+</string>
|
||||||
|
<string name="nsfw_16">16+</string>
|
||||||
<string name="various_languages">Various languages</string>
|
<string name="various_languages">Various languages</string>
|
||||||
<string name="search_chapters">Find chapter</string>
|
<string name="search_chapters">Find chapter</string>
|
||||||
<string name="chapters_empty">No chapters in this manga</string>
|
<string name="chapters_empty">No chapters in this manga</string>
|
||||||
|
|||||||
@@ -229,6 +229,18 @@
|
|||||||
<item name="android:textAppearance">?textAppearanceLabelMedium</item>
|
<item name="android:textAppearance">?textAppearanceLabelMedium</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Kotatsu.TextView.Badge" parent="Widget.MaterialComponents.TextView">
|
||||||
|
<item name="android:background">@drawable/bg_chip</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
|
<item name="android:textAlignment">center</item>
|
||||||
|
<item name="android:paddingStart">4dp</item>
|
||||||
|
<item name="android:paddingEnd">4dp</item>
|
||||||
|
<item name="android:paddingTop">2dp</item>
|
||||||
|
<item name="android:paddingBottom">2dp</item>
|
||||||
|
<item name="android:textAppearance">?textAppearanceLabelMedium</item>
|
||||||
|
<item name="android:textColor">?colorOnBackground</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="ThemeOverlay.Kotatsu.MainToolbar" parent="">
|
<style name="ThemeOverlay.Kotatsu.MainToolbar" parent="">
|
||||||
<item name="colorControlHighlight">@color/selector_overlay</item>
|
<item name="colorControlHighlight">@color/selector_overlay</item>
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user