Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42d933ba83 | ||
|
|
4df644e21f | ||
|
|
e4ba738c00 | ||
|
|
b7f09243aa | ||
|
|
50d4c41855 | ||
|
|
67adc8b681 | ||
|
|
34fb4af9fe | ||
|
|
05241f73d9 | ||
|
|
d666e4b967 | ||
|
|
b4bf607d3a | ||
|
|
a417d5aaa9 | ||
|
|
4b6b2c3e12 | ||
|
|
51300e30bd | ||
|
|
399ac07fb3 | ||
|
|
eeba161235 | ||
|
|
088a388812 | ||
|
|
943bba3ee8 | ||
|
|
18c3229200 | ||
|
|
9b6f511ac6 | ||
|
|
ad3b5dde91 | ||
|
|
74ca19a931 | ||
|
|
2684a7384e | ||
|
|
2c561824ef |
@@ -16,8 +16,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 573
|
versionCode = 575
|
||||||
versionName = '6.0'
|
versionName = '6.0.2'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "org.koitharu.kotatsu.HiltTestRunner"
|
testInstrumentationRunner "org.koitharu.kotatsu.HiltTestRunner"
|
||||||
ksp {
|
ksp {
|
||||||
|
|||||||
@@ -148,13 +148,21 @@
|
|||||||
android:name="org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity"
|
android:name="org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity"
|
||||||
android:label="@string/manage_categories" />
|
android:label="@string/manage_categories" />
|
||||||
<activity
|
<activity
|
||||||
android:name="org.koitharu.kotatsu.widget.shelf.ShelfConfigActivity"
|
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetConfigActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/manga_shelf">
|
android:label="@string/manga_shelf">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="org.koitharu.kotatsu.widget.recent.RecentWidgetConfigActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/recent_manga">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity"
|
android:name="org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity"
|
||||||
android:label="@string/search" />
|
android:label="@string/search" />
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
|||||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
@@ -40,8 +39,4 @@ fun bookmarkListAD(
|
|||||||
enqueueWith(coil)
|
enqueueWith(coil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewThumb.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
|||||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
@@ -42,8 +41,4 @@ fun bookmarkLargeAD(
|
|||||||
}
|
}
|
||||||
binding.progressView.percent = item.percent
|
binding.progressView.percent = item.percent
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewThumb.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
package org.koitharu.kotatsu.browser.cloudflare
|
package org.koitharu.kotatsu.browser.cloudflare
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationChannelCompat
|
import androidx.core.app.NotificationChannelCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.PendingIntentCompat
|
import androidx.core.app.PendingIntentCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import coil.EventListener
|
||||||
import coil.request.ErrorResult
|
import coil.request.ErrorResult
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||||
|
|
||||||
class CaptchaNotifier(
|
class CaptchaNotifier(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
) : ImageRequest.Listener {
|
) : EventListener {
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
fun notify(exception: CloudFlareProtectedException) {
|
fun notify(exception: CloudFlareProtectedException) {
|
||||||
val manager = NotificationManagerCompat.from(context)
|
if (!context.checkNotificationPermission()) {
|
||||||
if (!manager.areNotificationsEnabled()) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
val manager = NotificationManagerCompat.from(context)
|
||||||
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||||
.setName(context.getString(R.string.captcha_required))
|
.setName(context.getString(R.string.captcha_required))
|
||||||
.setShowBadge(true)
|
.setShowBadge(true)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import coil.decode.SvgDecoder
|
|||||||
import coil.disk.DiskCache
|
import coil.disk.DiskCache
|
||||||
import coil.util.DebugLogger
|
import coil.util.DebugLogger
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Lazy
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -26,6 +25,7 @@ import kotlinx.coroutines.flow.SharedFlow
|
|||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
|
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||||
import org.koitharu.kotatsu.core.cache.ContentCache
|
import org.koitharu.kotatsu.core.cache.ContentCache
|
||||||
import org.koitharu.kotatsu.core.cache.MemoryContentCache
|
import org.koitharu.kotatsu.core.cache.MemoryContentCache
|
||||||
import org.koitharu.kotatsu.core.cache.StubContentCache
|
import org.koitharu.kotatsu.core.cache.StubContentCache
|
||||||
@@ -47,7 +47,7 @@ import org.koitharu.kotatsu.local.data.CacheDir
|
|||||||
import org.koitharu.kotatsu.local.data.CbzFetcher
|
import org.koitharu.kotatsu.local.data.CbzFetcher
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||||
import org.koitharu.kotatsu.main.domain.CoverRestorer
|
import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor
|
||||||
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
|
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
|
||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.reader.ui.thumbnails.MangaPageFetcher
|
import org.koitharu.kotatsu.reader.ui.thumbnails.MangaPageFetcher
|
||||||
@@ -91,7 +91,7 @@ interface AppModule {
|
|||||||
mangaRepositoryFactory: MangaRepository.Factory,
|
mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
imageProxyInterceptor: ImageProxyInterceptor,
|
imageProxyInterceptor: ImageProxyInterceptor,
|
||||||
pageFetcherFactory: MangaPageFetcher.Factory,
|
pageFetcherFactory: MangaPageFetcher.Factory,
|
||||||
coverRestorerProvider: Lazy<CoverRestorer>,
|
coverRestoreInterceptor: CoverRestoreInterceptor,
|
||||||
): ImageLoader {
|
): ImageLoader {
|
||||||
val diskCacheFactory = {
|
val diskCacheFactory = {
|
||||||
val rootDir = context.externalCacheDir ?: context.cacheDir
|
val rootDir = context.externalCacheDir ?: context.cacheDir
|
||||||
@@ -108,7 +108,7 @@ interface AppModule {
|
|||||||
.diskCache(diskCacheFactory)
|
.diskCache(diskCacheFactory)
|
||||||
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
|
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
|
||||||
.allowRgb565(context.isLowRamDevice())
|
.allowRgb565(context.isLowRamDevice())
|
||||||
.eventListenerFactory { coverRestorerProvider.get() }
|
.eventListener(CaptchaNotifier(context))
|
||||||
.components(
|
.components(
|
||||||
ComponentRegistry.Builder()
|
ComponentRegistry.Builder()
|
||||||
.add(SvgDecoder.Factory())
|
.add(SvgDecoder.Factory())
|
||||||
@@ -116,6 +116,7 @@ interface AppModule {
|
|||||||
.add(FaviconFetcher.Factory(context, okHttpClient, mangaRepositoryFactory))
|
.add(FaviconFetcher.Factory(context, okHttpClient, mangaRepositoryFactory))
|
||||||
.add(pageFetcherFactory)
|
.add(pageFetcherFactory)
|
||||||
.add(imageProxyInterceptor)
|
.add(imageProxyInterceptor)
|
||||||
|
.add(coverRestoreInterceptor)
|
||||||
.build(),
|
.build(),
|
||||||
).build()
|
).build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,25 +20,8 @@ interface ContentCache {
|
|||||||
|
|
||||||
fun putRelatedManga(source: MangaSource, url: String, related: SafeDeferred<List<Manga>>)
|
fun putRelatedManga(source: MangaSource, url: String, related: SafeDeferred<List<Manga>>)
|
||||||
|
|
||||||
class Key(
|
data class Key(
|
||||||
val source: MangaSource,
|
val source: MangaSource,
|
||||||
val url: String,
|
val url: String,
|
||||||
) {
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Key
|
|
||||||
|
|
||||||
if (source != other.source) return false
|
|
||||||
return url == other.url
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = source.hashCode()
|
|
||||||
result = 31 * result + url.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import androidx.room.Embedded
|
|||||||
import androidx.room.Junction
|
import androidx.room.Junction
|
||||||
import androidx.room.Relation
|
import androidx.room.Relation
|
||||||
|
|
||||||
class MangaWithTags(
|
data class MangaWithTags(
|
||||||
@Embedded val manga: MangaEntity,
|
@Embedded val manga: MangaEntity,
|
||||||
@Relation(
|
@Relation(
|
||||||
parentColumn = "manga_id",
|
parentColumn = "manga_id",
|
||||||
@@ -12,21 +12,4 @@ class MangaWithTags(
|
|||||||
associateBy = Junction(MangaTagsEntity::class)
|
associateBy = Junction(MangaTagsEntity::class)
|
||||||
)
|
)
|
||||||
val tags: List<TagEntity>,
|
val tags: List<TagEntity>,
|
||||||
) {
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as MangaWithTags
|
|
||||||
|
|
||||||
if (manga != other.manga) return false
|
|
||||||
return tags == other.tags
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = manga.hashCode()
|
|
||||||
result = 31 * result + tags.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,4 +6,8 @@ import java.util.Date
|
|||||||
class TooManyRequestExceptions(
|
class TooManyRequestExceptions(
|
||||||
val url: String,
|
val url: String,
|
||||||
val retryAt: Date?,
|
val retryAt: Date?,
|
||||||
) : IOException()
|
) : IOException() {
|
||||||
|
|
||||||
|
val retryAfter: Long
|
||||||
|
get() = if (retryAt == null) 0 else (retryAt.time - System.currentTimeMillis()).coerceAtLeast(0)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.core.github
|
|||||||
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class VersionId(
|
data class VersionId(
|
||||||
val major: Int,
|
val major: Int,
|
||||||
val minor: Int,
|
val minor: Int,
|
||||||
val build: Int,
|
val build: Int,
|
||||||
@@ -30,28 +30,6 @@ class VersionId(
|
|||||||
return variantNumber.compareTo(other.variantNumber)
|
return variantNumber.compareTo(other.variantNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as VersionId
|
|
||||||
|
|
||||||
if (major != other.major) return false
|
|
||||||
if (minor != other.minor) return false
|
|
||||||
if (build != other.build) return false
|
|
||||||
if (variantType != other.variantType) return false
|
|
||||||
return variantNumber == other.variantNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = major
|
|
||||||
result = 31 * result + minor
|
|
||||||
result = 31 * result + build
|
|
||||||
result = 31 * result + variantType.hashCode()
|
|
||||||
result = 31 * result + variantNumber
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun variantWeight(variantType: String) = when (variantType.lowercase(Locale.ROOT)) {
|
private fun variantWeight(variantType: String) = when (variantType.lowercase(Locale.ROOT)) {
|
||||||
"a", "alpha" -> 1
|
"a", "alpha" -> 1
|
||||||
"b", "beta" -> 2
|
"b", "beta" -> 2
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ fun Manga.getPreferredBranch(history: MangaHistory?): String? {
|
|||||||
if (groups.size == 1) {
|
if (groups.size == 1) {
|
||||||
return groups.keys.first()
|
return groups.keys.first()
|
||||||
}
|
}
|
||||||
val candidates = HashMap<String?, List<MangaChapter>>(groups.size)
|
|
||||||
for (locale in LocaleListCompat.getAdjustedDefault()) {
|
for (locale in LocaleListCompat.getAdjustedDefault()) {
|
||||||
val displayLanguage = locale.getDisplayLanguage(locale)
|
val displayLanguage = locale.getDisplayLanguage(locale)
|
||||||
val displayName = locale.getDisplayName(locale)
|
val displayName = locale.getDisplayName(locale)
|
||||||
|
val candidates = HashMap<String?, List<MangaChapter>>(3)
|
||||||
for (branch in groups.keys) {
|
for (branch in groups.keys) {
|
||||||
if (branch != null && (
|
if (branch != null && (
|
||||||
branch.contains(displayLanguage, ignoreCase = true) ||
|
branch.contains(displayLanguage, ignoreCase = true) ||
|
||||||
@@ -61,8 +61,11 @@ fun Manga.getPreferredBranch(history: MangaHistory?): String? {
|
|||||||
candidates[branch] = groups[branch] ?: continue
|
candidates[branch] = groups[branch] ?: continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (candidates.isNotEmpty()) {
|
||||||
|
return candidates.maxBy { it.value.size }.key
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return candidates.ifEmpty { groups }.maxByOrNull { it.value.size }?.key
|
return groups.maxByOrNull { it.value.size }?.key
|
||||||
}
|
}
|
||||||
|
|
||||||
val Manga.isLocal: Boolean
|
val Manga.isLocal: Boolean
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import java.io.ObjectInputStream
|
|||||||
import java.io.ObjectOutputStream
|
import java.io.ObjectOutputStream
|
||||||
|
|
||||||
|
|
||||||
class CookieWrapper(
|
data class CookieWrapper(
|
||||||
val cookie: Cookie,
|
val cookie: Cookie,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -66,17 +66,4 @@ class CookieWrapper(
|
|||||||
fun key(): String {
|
fun key(): String {
|
||||||
return (if (cookie.secure) "https" else "http") + "://" + cookie.domain + cookie.path + "|" + cookie.name
|
return (if (cookie.secure) "https" else "http") + "://" + cookie.domain + cookie.path + "|" + cookie.name
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as CookieWrapper
|
|
||||||
|
|
||||||
return cookie == other.cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return cookie.hashCode()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import coil.network.HttpException
|
|||||||
import coil.request.Options
|
import coil.request.Options
|
||||||
import coil.size.Size
|
import coil.size.Size
|
||||||
import coil.size.pxOrElse
|
import coil.size.pxOrElse
|
||||||
|
import kotlinx.coroutines.ensureActive
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@@ -25,11 +26,13 @@ import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
|||||||
import org.koitharu.kotatsu.core.model.MangaSource
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||||
import org.koitharu.kotatsu.local.data.CacheDir
|
import org.koitharu.kotatsu.local.data.CacheDir
|
||||||
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
|
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.await
|
import org.koitharu.kotatsu.parsers.util.await
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
private const val FALLBACK_SIZE = 9999 // largest icon
|
private const val FALLBACK_SIZE = 9999 // largest icon
|
||||||
|
|
||||||
@@ -55,13 +58,16 @@ class FaviconFetcher(
|
|||||||
options.size.height.pxOrElse { FALLBACK_SIZE },
|
options.size.height.pxOrElse { FALLBACK_SIZE },
|
||||||
)
|
)
|
||||||
var favicons = repo.getFavicons()
|
var favicons = repo.getFavicons()
|
||||||
|
var lastError: Exception? = null
|
||||||
while (favicons.isNotEmpty()) {
|
while (favicons.isNotEmpty()) {
|
||||||
val icon = favicons.find(sizePx) ?: throwNSEE()
|
coroutineContext.ensureActive()
|
||||||
|
val icon = favicons.find(sizePx) ?: throwNSEE(lastError)
|
||||||
val response = try {
|
val response = try {
|
||||||
loadIcon(icon.url, mangaSource)
|
loadIcon(icon.url, mangaSource)
|
||||||
} catch (e: CloudFlareProtectedException) {
|
} catch (e: CloudFlareProtectedException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: HttpException) {
|
} catch (e: HttpException) {
|
||||||
|
lastError = e
|
||||||
favicons -= icon
|
favicons -= icon
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -75,7 +81,7 @@ class FaviconFetcher(
|
|||||||
dataSource = response.toDataSource(),
|
dataSource = response.toDataSource(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
throwNSEE()
|
throwNSEE(lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadIcon(url: String, source: MangaSource): Response {
|
private suspend fun loadIcon(url: String, source: MangaSource): Response {
|
||||||
@@ -105,14 +111,14 @@ class FaviconFetcher(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeToDiskCache(body: ResponseBody): DiskCache.Snapshot? {
|
private suspend fun writeToDiskCache(body: ResponseBody): DiskCache.Snapshot? {
|
||||||
if (!options.diskCachePolicy.writeEnabled || body.contentLength() == 0L) {
|
if (!options.diskCachePolicy.writeEnabled || body.contentLength() == 0L) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val editor = diskCache.value?.openEditor(diskCacheKey) ?: return null
|
val editor = diskCache.value?.openEditor(diskCacheKey) ?: return null
|
||||||
try {
|
try {
|
||||||
fileSystem.write(editor.data) {
|
fileSystem.write(editor.data) {
|
||||||
body.source().readAll(this)
|
writeAllCancellable(body.source())
|
||||||
}
|
}
|
||||||
return editor.commitAndOpenSnapshot()
|
return editor.commitAndOpenSnapshot()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
@@ -154,7 +160,13 @@ class FaviconFetcher(
|
|||||||
append(height.toString())
|
append(height.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun throwNSEE(): Nothing = throw NoSuchElementException("No favicons found")
|
private fun throwNSEE(lastError: Exception?): Nothing {
|
||||||
|
if (lastError != null) {
|
||||||
|
throw lastError
|
||||||
|
} else {
|
||||||
|
throw NoSuchElementException("No favicons found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(
|
class Factory(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|||||||
@@ -1,15 +1,38 @@
|
|||||||
package org.koitharu.kotatsu.core.prefs
|
package org.koitharu.kotatsu.core.prefs
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetProvider
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
|
||||||
private const val CATEGORY_ID = "cat_id"
|
private const val CATEGORY_ID = "cat_id"
|
||||||
|
private const val BACKGROUND = "bg"
|
||||||
|
|
||||||
class AppWidgetConfig(context: Context, val widgetId: Int) {
|
class AppWidgetConfig(
|
||||||
|
context: Context,
|
||||||
|
cls: Class<out AppWidgetProvider>,
|
||||||
|
val widgetId: Int,
|
||||||
|
) {
|
||||||
|
|
||||||
private val prefs = context.getSharedPreferences("appwidget_$widgetId", Context.MODE_PRIVATE)
|
private val prefs = context.getSharedPreferences("appwidget_${cls.simpleName}_$widgetId", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
var categoryId: Long
|
var categoryId: Long
|
||||||
get() = prefs.getLong(CATEGORY_ID, 0L)
|
get() = prefs.getLong(CATEGORY_ID, 0L)
|
||||||
set(value) = prefs.edit { putLong(CATEGORY_ID, value) }
|
set(value) = prefs.edit { putLong(CATEGORY_ID, value) }
|
||||||
|
|
||||||
|
var hasBackground: Boolean
|
||||||
|
get() = prefs.getBoolean(BACKGROUND, Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||||
|
set(value) = prefs.edit { putBoolean(BACKGROUND, value) }
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
prefs.edit { clear() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyFrom(other: AppWidgetConfig) {
|
||||||
|
prefs.edit {
|
||||||
|
clear()
|
||||||
|
putLong(CATEGORY_ID, other.categoryId)
|
||||||
|
putBoolean(BACKGROUND, other.hasBackground)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package org.koitharu.kotatsu.core.prefs
|
|||||||
enum class ReaderMode(val id: Int) {
|
enum class ReaderMode(val id: Int) {
|
||||||
|
|
||||||
STANDARD(1),
|
STANDARD(1),
|
||||||
WEBTOON(2),
|
REVERSED(3),
|
||||||
REVERSED(3);
|
WEBTOON(2);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.koitharu.kotatsu.core.ui
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetProvider
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||||
|
|
||||||
|
abstract class BaseAppWidgetProvider : AppWidgetProvider() {
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onUpdate(
|
||||||
|
context: Context,
|
||||||
|
appWidgetManager: AppWidgetManager,
|
||||||
|
appWidgetIds: IntArray
|
||||||
|
) {
|
||||||
|
appWidgetIds.forEach { id ->
|
||||||
|
val config = AppWidgetConfig(context, javaClass, id)
|
||||||
|
val views = onUpdateWidget(context, config)
|
||||||
|
appWidgetManager.updateAppWidget(id, views)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||||
|
super.onDeleted(context, appWidgetIds)
|
||||||
|
for (id in appWidgetIds) {
|
||||||
|
AppWidgetConfig(context, javaClass, id).clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRestored(context: Context, oldWidgetIds: IntArray, newWidgetIds: IntArray) {
|
||||||
|
super.onRestored(context, oldWidgetIds, newWidgetIds)
|
||||||
|
if (oldWidgetIds.size != newWidgetIds.size) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (i in oldWidgetIds.indices) {
|
||||||
|
val oldId = oldWidgetIds[i]
|
||||||
|
val newId = newWidgetIds[i]
|
||||||
|
val oldConfig = AppWidgetConfig(context, javaClass, oldId)
|
||||||
|
val newConfig = AppWidgetConfig(context, javaClass, newId)
|
||||||
|
newConfig.copyFrom(oldConfig)
|
||||||
|
oldConfig.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun onUpdateWidget(
|
||||||
|
context: Context,
|
||||||
|
config: AppWidgetConfig,
|
||||||
|
): RemoteViews
|
||||||
|
}
|
||||||
@@ -106,12 +106,7 @@ class TrimTransformation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
return this === other || (other is TrimTransformation && other.tolerance == tolerance)
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as TrimTransformation
|
|
||||||
|
|
||||||
return tolerance == other.tolerance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
|
|||||||
@@ -20,38 +20,19 @@ sealed class DateTimeAgo {
|
|||||||
override fun equals(other: Any?): Boolean = other === JustNow
|
override fun equals(other: Any?): Boolean = other === JustNow
|
||||||
}
|
}
|
||||||
|
|
||||||
class MinutesAgo(val minutes: Int) : DateTimeAgo() {
|
data class MinutesAgo(val minutes: Int) : DateTimeAgo() {
|
||||||
|
|
||||||
override fun format(resources: Resources): String {
|
override fun format(resources: Resources): String {
|
||||||
return resources.getQuantityString(R.plurals.minutes_ago, minutes, minutes)
|
return resources.getQuantityString(R.plurals.minutes_ago, minutes, minutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
other as MinutesAgo
|
|
||||||
return minutes == other.minutes
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = minutes
|
|
||||||
|
|
||||||
override fun toString() = "minutes_ago_$minutes"
|
override fun toString() = "minutes_ago_$minutes"
|
||||||
}
|
}
|
||||||
|
|
||||||
class HoursAgo(val hours: Int) : DateTimeAgo() {
|
data class HoursAgo(val hours: Int) : DateTimeAgo() {
|
||||||
override fun format(resources: Resources): String {
|
override fun format(resources: Resources): String {
|
||||||
return resources.getQuantityString(R.plurals.hours_ago, hours, hours)
|
return resources.getQuantityString(R.plurals.hours_ago, hours, hours)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
other as HoursAgo
|
|
||||||
return hours == other.hours
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = hours
|
|
||||||
|
|
||||||
override fun toString() = "hours_ago_$hours"
|
override fun toString() = "hours_ago_$hours"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,26 +56,15 @@ sealed class DateTimeAgo {
|
|||||||
override fun equals(other: Any?): Boolean = other === Yesterday
|
override fun equals(other: Any?): Boolean = other === Yesterday
|
||||||
}
|
}
|
||||||
|
|
||||||
class DaysAgo(val days: Int) : DateTimeAgo() {
|
data class DaysAgo(val days: Int) : DateTimeAgo() {
|
||||||
|
|
||||||
override fun format(resources: Resources): String {
|
override fun format(resources: Resources): String {
|
||||||
return resources.getQuantityString(R.plurals.days_ago, days, days)
|
return resources.getQuantityString(R.plurals.days_ago, days, days)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
other as DaysAgo
|
|
||||||
return days == other.days
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = days
|
|
||||||
|
|
||||||
override fun toString() = "days_ago_$days"
|
override fun toString() = "days_ago_$days"
|
||||||
}
|
}
|
||||||
|
|
||||||
class MonthsAgo(val months: Int) : DateTimeAgo() {
|
data class MonthsAgo(val months: Int) : DateTimeAgo() {
|
||||||
|
|
||||||
override fun format(resources: Resources): String {
|
override fun format(resources: Resources): String {
|
||||||
return if (months == 0) {
|
return if (months == 0) {
|
||||||
resources.getString(R.string.this_month)
|
resources.getString(R.string.this_month)
|
||||||
@@ -102,19 +72,6 @@ sealed class DateTimeAgo {
|
|||||||
resources.getQuantityString(R.plurals.months_ago, months, months)
|
resources.getQuantityString(R.plurals.months_ago, months, months)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as MonthsAgo
|
|
||||||
|
|
||||||
return months == other.months
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return months
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Absolute(private val date: Date) : DateTimeAgo() {
|
class Absolute(private val date: Date) : DateTimeAgo() {
|
||||||
|
|||||||
@@ -139,39 +139,14 @@ class ChipsView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChipModel(
|
data class ChipModel(
|
||||||
@ColorRes val tint: Int,
|
@ColorRes val tint: Int,
|
||||||
val title: CharSequence,
|
val title: CharSequence,
|
||||||
@DrawableRes val icon: Int,
|
@DrawableRes val icon: Int,
|
||||||
val isCheckable: Boolean,
|
val isCheckable: Boolean,
|
||||||
val isChecked: Boolean,
|
val isChecked: Boolean,
|
||||||
val data: Any? = null,
|
val data: Any? = null,
|
||||||
) {
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ChipModel
|
|
||||||
|
|
||||||
if (tint != other.tint) return false
|
|
||||||
if (title != other.title) return false
|
|
||||||
if (icon != other.icon) return false
|
|
||||||
if (isCheckable != other.isCheckable) return false
|
|
||||||
if (isChecked != other.isChecked) return false
|
|
||||||
return data == other.data
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = tint.hashCode()
|
|
||||||
result = 31 * result + title.hashCode()
|
|
||||||
result = 31 * result + icon.hashCode()
|
|
||||||
result = 31 * result + isCheckable.hashCode()
|
|
||||||
result = 31 * result + isChecked.hashCode()
|
|
||||||
result = 31 * result + (data?.hashCode() ?: 0)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun interface OnChipClickListener {
|
fun interface OnChipClickListener {
|
||||||
|
|
||||||
|
|||||||
@@ -118,27 +118,10 @@ class SegmentedBarView @JvmOverloads constructor(
|
|||||||
segmentsSizes.add(w)
|
segmentsSizes.add(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Segment(
|
data class Segment(
|
||||||
@FloatRange(from = 0.0, to = 1.0) val percent: Float,
|
@FloatRange(from = 0.0, to = 1.0) val percent: Float,
|
||||||
@ColorInt val color: Int,
|
@ColorInt val color: Int,
|
||||||
) {
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Segment
|
|
||||||
|
|
||||||
if (percent != other.percent) return false
|
|
||||||
return color == other.color
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = percent.hashCode()
|
|
||||||
result = 31 * result + color
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OutlineProvider : ViewOutlineProvider() {
|
private class OutlineProvider : ViewOutlineProvider() {
|
||||||
override fun getOutline(view: View, outline: Outline) {
|
override fun getOutline(view: View, outline: Outline) {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.koitharu.kotatsu.core.util
|
||||||
|
|
||||||
|
import androidx.collection.ArrayMap
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
|
||||||
|
class CompositeMutex2<T : Any> : Set<T> {
|
||||||
|
|
||||||
|
private val delegates = ArrayMap<T, Mutex>()
|
||||||
|
|
||||||
|
override val size: Int
|
||||||
|
get() = delegates.size
|
||||||
|
|
||||||
|
override fun contains(element: T): Boolean {
|
||||||
|
return delegates.containsKey(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun containsAll(elements: Collection<T>): Boolean {
|
||||||
|
return elements.all { x -> delegates.containsKey(x) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isEmpty(): Boolean {
|
||||||
|
return delegates.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<T> {
|
||||||
|
return delegates.keys.iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun lock(element: T) {
|
||||||
|
val mutex = synchronized(delegates) {
|
||||||
|
delegates.getOrPut(element) {
|
||||||
|
Mutex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unlock(element: T) {
|
||||||
|
synchronized(delegates) {
|
||||||
|
delegates.remove(element)?.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import coil.request.SuccessResult
|
|||||||
import coil.util.CoilUtils
|
import coil.util.CoilUtils
|
||||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
|
||||||
import org.koitharu.kotatsu.core.ui.image.RegionBitmapDecoder
|
import org.koitharu.kotatsu.core.ui.image.RegionBitmapDecoder
|
||||||
import org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener
|
import org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
@@ -29,7 +28,6 @@ fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): Image
|
|||||||
.data(data)
|
.data(data)
|
||||||
.lifecycle(lifecycleOwner)
|
.lifecycle(lifecycleOwner)
|
||||||
.crossfade(context)
|
.crossfade(context)
|
||||||
.addListener(CaptchaNotifier(context.applicationContext))
|
|
||||||
.target(this)
|
.target(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import android.text.format.DateUtils
|
|||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
|
|
||||||
class ChapterListItem(
|
data class ChapterListItem(
|
||||||
val chapter: MangaChapter,
|
val chapter: MangaChapter,
|
||||||
val flags: Int,
|
val flags: Int,
|
||||||
private val uploadDateMs: Long,
|
private val uploadDateMs: Long,
|
||||||
@@ -66,24 +66,6 @@ class ChapterListItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ChapterListItem
|
|
||||||
|
|
||||||
if (chapter != other.chapter) return false
|
|
||||||
if (flags != other.flags) return false
|
|
||||||
return uploadDateMs == other.uploadDateMs
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = chapter.hashCode()
|
|
||||||
result = 31 * result + flags
|
|
||||||
result = 31 * result + uploadDateMs.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val FLAG_UNREAD = 2
|
const val FLAG_UNREAD = 2
|
||||||
|
|||||||
@@ -3,35 +3,14 @@ package org.koitharu.kotatsu.details.ui.model
|
|||||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
|
||||||
class HistoryInfo(
|
data class HistoryInfo(
|
||||||
val totalChapters: Int,
|
val totalChapters: Int,
|
||||||
val currentChapter: Int,
|
val currentChapter: Int,
|
||||||
val history: MangaHistory?,
|
val history: MangaHistory?,
|
||||||
val isIncognitoMode: Boolean,
|
val isIncognitoMode: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val isValid: Boolean
|
val isValid: Boolean
|
||||||
get() = totalChapters >= 0
|
get() = totalChapters >= 0
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as HistoryInfo
|
|
||||||
|
|
||||||
if (totalChapters != other.totalChapters) return false
|
|
||||||
if (currentChapter != other.currentChapter) return false
|
|
||||||
if (history != other.history) return false
|
|
||||||
return isIncognitoMode == other.isIncognitoMode
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = totalChapters
|
|
||||||
result = 31 * result + currentChapter
|
|
||||||
result = 31 * result + (history?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + isIncognitoMode.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun HistoryInfo(
|
fun HistoryInfo(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ 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
|
||||||
|
|
||||||
class MangaBranch(
|
data class MangaBranch(
|
||||||
val name: String?,
|
val name: String?,
|
||||||
val count: Int,
|
val count: Int,
|
||||||
val isSelected: Boolean,
|
val isSelected: Boolean,
|
||||||
@@ -21,24 +21,6 @@ class MangaBranch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as MangaBranch
|
|
||||||
|
|
||||||
if (name != other.name) return false
|
|
||||||
if (count != other.count) return false
|
|
||||||
return isSelected == other.isSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = name.hashCode()
|
|
||||||
result = 31 * result + count
|
|
||||||
result = 31 * result + isSelected.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$name: $count"
|
return "$name: $count"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleOwner
|
|||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.databinding.ItemScrobblingInfoBinding
|
import org.koitharu.kotatsu.databinding.ItemScrobblingInfoBinding
|
||||||
@@ -37,8 +36,4 @@ fun scrobblingInfoAD(
|
|||||||
context.resources.getStringArray(R.array.scrobbling_statuses).getOrNull(it.ordinal)
|
context.resources.getStringArray(R.array.scrobbling_statuses).getOrNull(it.ordinal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import coil.ImageLoader
|
|||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
@@ -135,8 +134,4 @@ fun downloadItemAD(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
class DownloadItemModel(
|
data class DownloadItemModel(
|
||||||
val id: UUID,
|
val id: UUID,
|
||||||
val workState: WorkInfo.State,
|
val workState: WorkInfo.State,
|
||||||
val isIndeterminate: Boolean,
|
val isIndeterminate: Boolean,
|
||||||
@@ -64,38 +64,4 @@ class DownloadItemModel(
|
|||||||
else -> super.getChangePayload(previousState)
|
else -> super.getChangePayload(previousState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as DownloadItemModel
|
|
||||||
|
|
||||||
if (id != other.id) return false
|
|
||||||
if (workState != other.workState) return false
|
|
||||||
if (isIndeterminate != other.isIndeterminate) return false
|
|
||||||
if (isPaused != other.isPaused) return false
|
|
||||||
if (manga != other.manga) return false
|
|
||||||
if (error != other.error) return false
|
|
||||||
if (max != other.max) return false
|
|
||||||
if (totalChapters != other.totalChapters) return false
|
|
||||||
if (progress != other.progress) return false
|
|
||||||
if (eta != other.eta) return false
|
|
||||||
return timestamp == other.timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = id.hashCode()
|
|
||||||
result = 31 * result + workState.hashCode()
|
|
||||||
result = 31 * result + isIndeterminate.hashCode()
|
|
||||||
result = 31 * result + isPaused.hashCode()
|
|
||||||
result = 31 * result + manga.hashCode()
|
|
||||||
result = 31 * result + (error?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + max
|
|
||||||
result = 31 * result + totalChapters
|
|
||||||
result = 31 * result + progress
|
|
||||||
result = 31 * result + eta.hashCode()
|
|
||||||
result = 31 * result + timestamp.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import okio.IOException
|
|||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.TooManyRequestExceptions
|
||||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||||
@@ -277,7 +278,12 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
publishState(currentState.copy(isPaused = false, error = null))
|
publishState(currentState.copy(isPaused = false, error = null))
|
||||||
} else {
|
} else {
|
||||||
countDown--
|
countDown--
|
||||||
delay(DOWNLOAD_ERROR_DELAY)
|
val retryDelay = if (e is TooManyRequestExceptions) {
|
||||||
|
e.retryAfter + DOWNLOAD_ERROR_DELAY
|
||||||
|
} else {
|
||||||
|
DOWNLOAD_ERROR_DELAY
|
||||||
|
}
|
||||||
|
delay(retryDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
|||||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
@@ -82,10 +81,6 @@ fun exploreRecommendationItemAD(
|
|||||||
enqueueWith(coil)
|
enqueueWith(coil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exploreSourceListItemAD(
|
fun exploreSourceListItemAD(
|
||||||
@@ -113,10 +108,6 @@ fun exploreSourceListItemAD(
|
|||||||
enqueueWith(coil)
|
enqueueWith(coil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewIcon.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exploreSourceGridItemAD(
|
fun exploreSourceGridItemAD(
|
||||||
@@ -144,8 +135,4 @@ fun exploreSourceGridItemAD(
|
|||||||
enqueueWith(coil)
|
enqueueWith(coil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewIcon.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,11 @@ package org.koitharu.kotatsu.explore.ui.model
|
|||||||
|
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
|
||||||
class ExploreButtons(
|
data class ExploreButtons(
|
||||||
val isRandomLoading: Boolean,
|
val isRandomLoading: Boolean,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|
||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is ExploreButtons
|
return other is ExploreButtons
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ExploreButtons
|
|
||||||
|
|
||||||
return isRandomLoading == other.isRandomLoading
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return isRandomLoading.hashCode()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.explore.ui.model
|
|||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
|
||||||
class MangaSourceItem(
|
data class MangaSourceItem(
|
||||||
val source: MangaSource,
|
val source: MangaSource,
|
||||||
val isGrid: Boolean,
|
val isGrid: Boolean,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
@@ -11,20 +11,4 @@ class MangaSourceItem(
|
|||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is MangaSourceItem && other.source == source
|
return other is MangaSourceItem && other.source == source
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as MangaSourceItem
|
|
||||||
|
|
||||||
if (source != other.source) return false
|
|
||||||
return isGrid == other.isGrid
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = source.hashCode()
|
|
||||||
result = 31 * result + isGrid.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
data class RecommendationsItem(
|
data class RecommendationsItem(
|
||||||
val manga: Manga
|
val manga: Manga
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|
||||||
val summary: String = manga.tags.joinToString { it.title }
|
val summary: String = manga.tags.joinToString { it.title }
|
||||||
|
|
||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
|
|||||||
@@ -3,31 +3,10 @@ package org.koitharu.kotatsu.favourites.domain.model
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.find
|
import org.koitharu.kotatsu.parsers.util.find
|
||||||
|
|
||||||
class Cover(
|
data class Cover(
|
||||||
val url: String,
|
val url: String,
|
||||||
val source: String,
|
val source: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val mangaSource: MangaSource?
|
val mangaSource: MangaSource?
|
||||||
get() = if (source.isEmpty()) null else MangaSource.entries.find(source)
|
get() = if (source.isEmpty()) null else MangaSource.entries.find(source)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Cover
|
|
||||||
|
|
||||||
if (url != other.url) return false
|
|
||||||
return source == other.source
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = url.hashCode()
|
|
||||||
result = 31 * result + source.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "Cover(url='$url', source=$source)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
|||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
|
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
|
||||||
|
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
|
||||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoriesAdapter
|
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoriesAdapter
|
||||||
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
|
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
||||||
@@ -77,6 +78,14 @@ class FavouriteCategoriesActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClick(item: FavouriteCategory, view: View) {
|
override fun onItemClick(item: FavouriteCategory, view: View) {
|
||||||
|
if (selectionController.onItemClick(item.id)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val intent = FavouritesActivity.newIntent(view.context, item)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEditClick(item: FavouriteCategory, view: View) {
|
||||||
if (selectionController.onItemClick(item.id)) {
|
if (selectionController.onItemClick(item.id)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -112,8 +121,8 @@ class FavouriteCategoriesActivity :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onCategoriesChanged(categories: List<ListModel>) {
|
private suspend fun onCategoriesChanged(categories: List<ListModel>) {
|
||||||
adapter.items = categories
|
adapter.emit(categories)
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +137,14 @@ class FavouriteCategoriesActivity :
|
|||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
target: RecyclerView.ViewHolder,
|
target: RecyclerView.ViewHolder,
|
||||||
): Boolean = viewHolder.itemViewType == target.itemViewType
|
): Boolean {
|
||||||
|
if (viewHolder.itemViewType != target.itemViewType) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val fromPos = viewHolder.bindingAdapterPosition
|
||||||
|
val toPos = target.bindingAdapterPosition
|
||||||
|
return fromPos != toPos && fromPos != RecyclerView.NO_POSITION && toPos != RecyclerView.NO_POSITION
|
||||||
|
}
|
||||||
|
|
||||||
override fun canDropOver(
|
override fun canDropOver(
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
@@ -153,7 +169,8 @@ class FavouriteCategoriesActivity :
|
|||||||
|
|
||||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||||
super.onSelectedChanged(viewHolder, actionState)
|
super.onSelectedChanged(viewHolder, actionState)
|
||||||
viewBinding.recyclerView.isNestedScrollingEnabled = actionState == ItemTouchHelper.ACTION_STATE_IDLE
|
viewBinding.recyclerView.isNestedScrollingEnabled =
|
||||||
|
actionState == ItemTouchHelper.ACTION_STATE_IDLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.favourites.ui.categories
|
package org.koitharu.kotatsu.favourites.ui.categories
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
@@ -7,4 +8,6 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
|||||||
interface FavouriteCategoriesListListener : OnListItemClickListener<FavouriteCategory> {
|
interface FavouriteCategoriesListListener : OnListItemClickListener<FavouriteCategory> {
|
||||||
|
|
||||||
fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean
|
fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean
|
||||||
|
|
||||||
|
fun onEditClick(item: FavouriteCategory, view: View)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
|||||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||||
import java.util.Collections
|
import org.koitharu.kotatsu.parsers.util.move
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -65,12 +65,11 @@ class FavouritesCategoriesViewModel @Inject constructor(
|
|||||||
val prevJob = reorderJob
|
val prevJob = reorderJob
|
||||||
reorderJob = launchJob(Dispatchers.Default) {
|
reorderJob = launchJob(Dispatchers.Default) {
|
||||||
prevJob?.join()
|
prevJob?.join()
|
||||||
val items = categories.requireValue()
|
val snapshot = categories.requireValue().toMutableList()
|
||||||
val ids = items.mapNotNullTo(ArrayList(items.size)) {
|
snapshot.move(oldPos, newPos)
|
||||||
|
val ids = snapshot.mapNotNullTo(ArrayList(snapshot.size)) {
|
||||||
(it as? CategoryListModel)?.category?.id
|
(it as? CategoryListModel)?.category?.id
|
||||||
}
|
}
|
||||||
Collections.swap(ids, oldPos, newPos)
|
|
||||||
ids.remove(0L)
|
|
||||||
repository.reorderCategories(ids)
|
repository.reorderCategories(ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ class CategoriesAdapter(
|
|||||||
) : BaseListAdapter<ListModel>() {
|
) : BaseListAdapter<ListModel>() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addDelegate(ListItemType.CATEGORY_LARGE ,categoryAD(coil, lifecycleOwner, onItemClickListener))
|
addDelegate(ListItemType.CATEGORY_LARGE, categoryAD(coil, lifecycleOwner, onItemClickListener))
|
||||||
addDelegate(ListItemType.STATE_EMPTY ,emptyStateListAD(coil, lifecycleOwner, listListener))
|
addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listListener))
|
||||||
addDelegate(ListItemType.STATE_LOADING ,loadingStateAD())
|
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import androidx.lifecycle.LifecycleOwner
|
|||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
|
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
@@ -35,8 +34,13 @@ fun categoryAD(
|
|||||||
{ inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) },
|
{ inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) },
|
||||||
) {
|
) {
|
||||||
val eventListener = object : OnClickListener, OnLongClickListener, OnTouchListener {
|
val eventListener = object : OnClickListener, OnLongClickListener, OnTouchListener {
|
||||||
override fun onClick(v: View) = clickListener.onItemClick(item.category, itemView)
|
override fun onClick(v: View) = if (v.id == R.id.imageView_edit) {
|
||||||
override fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, itemView)
|
clickListener.onEditClick(item.category, v)
|
||||||
|
} else {
|
||||||
|
clickListener.onItemClick(item.category, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, v)
|
||||||
override fun onTouch(v: View?, event: MotionEvent): Boolean = event.actionMasked == MotionEvent.ACTION_DOWN &&
|
override fun onTouch(v: View?, event: MotionEvent): Boolean = event.actionMasked == MotionEvent.ACTION_DOWN &&
|
||||||
clickListener.onDragHandleTouch(this@adapterDelegateViewBinding)
|
clickListener.onDragHandleTouch(this@adapterDelegateViewBinding)
|
||||||
}
|
}
|
||||||
@@ -58,6 +62,7 @@ fun categoryAD(
|
|||||||
val crossFadeDuration = context.getAnimationDuration(R.integer.config_defaultAnimTime).toInt()
|
val crossFadeDuration = context.getAnimationDuration(R.integer.config_defaultAnimTime).toInt()
|
||||||
itemView.setOnClickListener(eventListener)
|
itemView.setOnClickListener(eventListener)
|
||||||
itemView.setOnLongClickListener(eventListener)
|
itemView.setOnLongClickListener(eventListener)
|
||||||
|
binding.imageViewEdit.setOnClickListener(eventListener)
|
||||||
binding.imageViewHandle.setOnTouchListener(eventListener)
|
binding.imageViewHandle.setOnTouchListener(eventListener)
|
||||||
|
|
||||||
bind { payloads ->
|
bind { payloads ->
|
||||||
@@ -89,10 +94,4 @@ fun categoryAD(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
coverViews.forEach {
|
|
||||||
it.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,4 +46,8 @@ class CategoryListModel(
|
|||||||
result = 31 * result + category.isVisibleInLibrary.hashCode()
|
result = 31 * result + category.isVisibleInLibrary.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "CategoryListModel(categoryId=${category.id})"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.favourites.ui.container
|
|||||||
|
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
|
||||||
class FavouriteTabModel(
|
data class FavouriteTabModel(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val title: String,
|
val title: String,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
@@ -10,20 +10,4 @@ class FavouriteTabModel(
|
|||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is FavouriteTabModel && other.id == id
|
return other is FavouriteTabModel && other.id == id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as FavouriteTabModel
|
|
||||||
|
|
||||||
if (id != other.id) return false
|
|
||||||
return title == other.title
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = id.hashCode()
|
|
||||||
result = 31 * result + title.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,30 +263,11 @@ class FilterCoordinator @Inject constructor(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TagsWrapper(
|
private data class TagsWrapper(
|
||||||
val tags: Set<MangaTag>,
|
val tags: Set<MangaTag>,
|
||||||
val isLoading: Boolean,
|
val isLoading: Boolean,
|
||||||
val isError: Boolean,
|
val isError: Boolean,
|
||||||
) {
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as TagsWrapper
|
|
||||||
|
|
||||||
if (tags != other.tags) return false
|
|
||||||
if (isLoading != other.isLoading) return false
|
|
||||||
return isError == other.isError
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = tags.hashCode()
|
|
||||||
result = 31 * result + isLoading.hashCode()
|
|
||||||
result = 31 * result + isError.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TagTitleComparator(lc: String?) : Comparator<MangaTag> {
|
private class TagTitleComparator(lc: String?) : Comparator<MangaTag> {
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
|
|||||||
|
|
||||||
sealed interface FilterItem : ListModel {
|
sealed interface FilterItem : ListModel {
|
||||||
|
|
||||||
class Sort(
|
data class Sort(
|
||||||
val order: SortOrder,
|
val order: SortOrder,
|
||||||
val isSelected: Boolean,
|
val isSelected: Boolean,
|
||||||
) : FilterItem {
|
) : FilterItem {
|
||||||
@@ -24,25 +24,9 @@ sealed interface FilterItem : ListModel {
|
|||||||
super.getChangePayload(previousState)
|
super.getChangePayload(previousState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Sort
|
|
||||||
|
|
||||||
if (order != other.order) return false
|
|
||||||
return isSelected == other.isSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = order.hashCode()
|
|
||||||
result = 31 * result + isSelected.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tag(
|
data class Tag(
|
||||||
val tag: MangaTag,
|
val tag: MangaTag,
|
||||||
val isChecked: Boolean,
|
val isChecked: Boolean,
|
||||||
) : FilterItem {
|
) : FilterItem {
|
||||||
@@ -58,43 +42,14 @@ sealed interface FilterItem : ListModel {
|
|||||||
super.getChangePayload(previousState)
|
super.getChangePayload(previousState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Tag
|
|
||||||
|
|
||||||
if (tag != other.tag) return false
|
|
||||||
return isChecked == other.isChecked
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = tag.hashCode()
|
|
||||||
result = 31 * result + isChecked.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Error(
|
data class Error(
|
||||||
@StringRes val textResId: Int,
|
@StringRes val textResId: Int,
|
||||||
) : FilterItem {
|
) : FilterItem {
|
||||||
|
|
||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is Error && textResId == other.textResId
|
return other is Error && textResId == other.textResId
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Error
|
|
||||||
|
|
||||||
return textResId == other.textResId
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return textResId
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,7 @@ package org.koitharu.kotatsu.filter.ui.model
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
|
|
||||||
class FilterState(
|
data class FilterState(
|
||||||
val sortOrder: SortOrder?,
|
val sortOrder: SortOrder?,
|
||||||
val tags: Set<MangaTag>,
|
val tags: Set<MangaTag>,
|
||||||
) {
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as FilterState
|
|
||||||
|
|
||||||
if (sortOrder != other.sortOrder) return false
|
|
||||||
return tags == other.tags
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = sortOrder?.hashCode() ?: 0
|
|
||||||
result = 31 * result + tags.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.list.ui.adapter
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
||||||
@@ -27,8 +26,4 @@ fun emptyHintAD(
|
|||||||
binding.textSecondary.setTextAndVisible(item.textSecondary)
|
binding.textSecondary.setTextAndVisible(item.textSecondary)
|
||||||
binding.buttonRetry.setTextAndVisible(item.actionStringRes)
|
binding.buttonRetry.setTextAndVisible(item.actionStringRes)
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.icon.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.list.ui.adapter
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
||||||
@@ -31,8 +30,4 @@ fun emptyStateListAD(
|
|||||||
binding.buttonRetry.setTextAndVisible(item.actionStringRes)
|
binding.buttonRetry.setTextAndVisible(item.actionStringRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.icon.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,10 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
|
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
|
||||||
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
|
||||||
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 org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
||||||
@@ -54,11 +52,4 @@ fun mangaGridItemAD(
|
|||||||
}
|
}
|
||||||
badge = itemView.bindBadge(badge, item.counter)
|
badge = itemView.bindBadge(badge, item.counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
itemView.clearBadge(badge)
|
|
||||||
binding.progressView.percent = PROGRESS_NONE
|
|
||||||
badge = null
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,11 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||||
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
|
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
|
||||||
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
|
||||||
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 org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
|
import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
|
||||||
@@ -73,11 +71,4 @@ fun mangaListDetailedItemAD(
|
|||||||
binding.ratingBar.rating = binding.ratingBar.numStars * item.manga.rating
|
binding.ratingBar.rating = binding.ratingBar.numStars * item.manga.rating
|
||||||
badge = itemView.bindBadge(badge, item.counter)
|
badge = itemView.bindBadge(badge, item.counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
itemView.clearBadge(badge)
|
|
||||||
binding.progressView.percent = PROGRESS_NONE
|
|
||||||
badge = null
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
@@ -48,10 +47,4 @@ fun mangaListItemAD(
|
|||||||
}
|
}
|
||||||
badge = itemView.bindBadge(badge, item.counter)
|
badge = itemView.bindBadge(badge, item.counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
itemView.clearBadge(badge)
|
|
||||||
badge = null
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ class LocalStorageManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun computeStorageSize() = withContext(Dispatchers.IO) {
|
suspend fun computeStorageSize() = withContext(Dispatchers.IO) {
|
||||||
getAvailableStorageDirs().sumOf { it.computeSize() }
|
getConfiguredStorageDirs().sumOf { it.computeSize() }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun computeAvailableSize() = runInterruptible(Dispatchers.IO) {
|
suspend fun computeAvailableSize() = runInterruptible(Dispatchers.IO) {
|
||||||
getAvailableStorageDirs().mapToSet { it.freeSpace }.sum()
|
getConfiguredStorageDirs().mapToSet { it.freeSpace }.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun clearCache(cache: CacheDir) = runInterruptible(Dispatchers.IO) {
|
suspend fun clearCache(cache: CacheDir) = runInterruptible(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class LocalMangaDirInput(root: File) : LocalMangaInput(root) {
|
|||||||
largeCoverUrl = null,
|
largeCoverUrl = null,
|
||||||
description = null,
|
description = null,
|
||||||
)
|
)
|
||||||
LocalManga(root, manga)
|
LocalManga(manga, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getMangaInfo(): Manga? = runInterruptible(Dispatchers.IO) {
|
override suspend fun getMangaInfo(): Manga? = runInterruptible(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class LocalMangaZipInput(root: File) : LocalMangaInput(root) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return LocalManga(root, manga)
|
return LocalManga(manga, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getMangaInfo(): Manga? = runInterruptible(Dispatchers.IO) {
|
override suspend fun getMangaInfo(): Manga? = runInterruptible(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class LocalManga(
|
data class LocalManga(
|
||||||
val file: File,
|
|
||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
|
val file: File = manga.url.toUri().toFile(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(manga: Manga) : this(manga.url.toUri().toFile(), manga)
|
|
||||||
|
|
||||||
var createdAt: Long = -1L
|
var createdAt: Long = -1L
|
||||||
private set
|
private set
|
||||||
get() {
|
get() {
|
||||||
@@ -31,22 +29,6 @@ class LocalManga(
|
|||||||
return manga.tags.containsAll(tags)
|
return manga.tags.containsAll(tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as LocalManga
|
|
||||||
|
|
||||||
if (manga != other.manga) return false
|
|
||||||
return file == other.file
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = manga.hashCode()
|
|
||||||
result = 31 * result + file.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "LocalManga(${file.path}: ${manga.title})"
|
return "LocalManga(${file.path}: ${manga.title})"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,65 @@
|
|||||||
package org.koitharu.kotatsu.main.domain
|
package org.koitharu.kotatsu.main.domain
|
||||||
|
|
||||||
import androidx.collection.ArraySet
|
import androidx.collection.ArraySet
|
||||||
import androidx.lifecycle.coroutineScope
|
import coil.intercept.Interceptor
|
||||||
import coil.EventListener
|
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.network.HttpException
|
import coil.network.HttpException
|
||||||
import coil.request.ErrorResult
|
import coil.request.ErrorResult
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageResult
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.jsoup.HttpStatusException
|
import org.jsoup.HttpStatusException
|
||||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
|
import java.util.Collections
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
|
||||||
|
|
||||||
class CoverRestorer @Inject constructor(
|
class CoverRestoreInterceptor @Inject constructor(
|
||||||
private val dataRepository: MangaDataRepository,
|
private val dataRepository: MangaDataRepository,
|
||||||
private val bookmarksRepository: BookmarksRepository,
|
private val bookmarksRepository: BookmarksRepository,
|
||||||
private val repositoryFactory: MangaRepository.Factory,
|
private val repositoryFactory: MangaRepository.Factory,
|
||||||
private val coilProvider: Provider<ImageLoader>,
|
) : Interceptor {
|
||||||
) : EventListener {
|
|
||||||
|
|
||||||
private val blacklist = ArraySet<String>()
|
private val blacklist = Collections.synchronizedSet(ArraySet<String>())
|
||||||
|
|
||||||
override fun onError(request: ImageRequest, result: ErrorResult) {
|
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||||
super.onError(request, result)
|
val request = chain.request
|
||||||
if (!result.throwable.shouldRestore()) {
|
val result = chain.proceed(request)
|
||||||
return
|
if (result is ErrorResult && result.throwable.shouldRestore()) {
|
||||||
}
|
request.tags.tag<Manga>()?.let {
|
||||||
request.tags.tag<Manga>()?.let {
|
if (restoreManga(it)) {
|
||||||
restoreManga(it, request)
|
return chain.proceed(request.newBuilder().build())
|
||||||
}
|
} else {
|
||||||
request.tags.tag<Bookmark>()?.let {
|
return result
|
||||||
restoreBookmark(it, request)
|
}
|
||||||
}
|
}
|
||||||
}
|
request.tags.tag<Bookmark>()?.let {
|
||||||
|
if (restoreBookmark(it)) {
|
||||||
private fun restoreManga(manga: Manga, request: ImageRequest) {
|
return chain.proceed(request.newBuilder().build())
|
||||||
val key = manga.publicUrl
|
} else {
|
||||||
if (key in blacklist) {
|
return result
|
||||||
return
|
}
|
||||||
}
|
|
||||||
request.lifecycle.coroutineScope.launch {
|
|
||||||
val restored = runCatchingCancellable {
|
|
||||||
restoreMangaImpl(manga)
|
|
||||||
}.getOrDefault(false)
|
|
||||||
if (restored) {
|
|
||||||
request.newBuilder().enqueueWith(coilProvider.get())
|
|
||||||
} else {
|
|
||||||
blacklist.add(key)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun restoreManga(manga: Manga): Boolean {
|
||||||
|
val key = manga.publicUrl
|
||||||
|
if (!blacklist.add(key)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val restored = runCatchingCancellable {
|
||||||
|
restoreMangaImpl(manga)
|
||||||
|
}.getOrDefault(false)
|
||||||
|
if (restored) {
|
||||||
|
blacklist.remove(key)
|
||||||
|
}
|
||||||
|
return restored
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun restoreMangaImpl(manga: Manga): Boolean {
|
private suspend fun restoreMangaImpl(manga: Manga): Boolean {
|
||||||
@@ -75,21 +76,18 @@ class CoverRestorer @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreBookmark(bookmark: Bookmark, request: ImageRequest) {
|
private suspend fun restoreBookmark(bookmark: Bookmark): Boolean {
|
||||||
val key = bookmark.imageUrl
|
val key = bookmark.imageUrl
|
||||||
if (key in blacklist) {
|
if (!blacklist.add(key)) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
request.lifecycle.coroutineScope.launch {
|
val restored = runCatchingCancellable {
|
||||||
val restored = runCatchingCancellable {
|
restoreBookmarkImpl(bookmark)
|
||||||
restoreBookmarkImpl(bookmark)
|
}.getOrDefault(false)
|
||||||
}.getOrDefault(false)
|
if (restored) {
|
||||||
if (restored) {
|
blacklist.remove(key)
|
||||||
request.newBuilder().enqueueWith(coilProvider.get())
|
|
||||||
} else {
|
|
||||||
blacklist.add(key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return restored
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun restoreBookmarkImpl(bookmark: Bookmark): Boolean {
|
private suspend fun restoreBookmarkImpl(bookmark: Bookmark): Boolean {
|
||||||
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.reader.domain
|
|||||||
import android.graphics.ColorMatrix
|
import android.graphics.ColorMatrix
|
||||||
import android.graphics.ColorMatrixColorFilter
|
import android.graphics.ColorMatrixColorFilter
|
||||||
|
|
||||||
class ReaderColorFilter(
|
data class ReaderColorFilter(
|
||||||
val brightness: Float,
|
val brightness: Float,
|
||||||
val contrast: Float,
|
val contrast: Float,
|
||||||
val isInverted: Boolean,
|
val isInverted: Boolean,
|
||||||
@@ -51,22 +51,4 @@ class ReaderColorFilter(
|
|||||||
)
|
)
|
||||||
set(matrix)
|
set(matrix)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ReaderColorFilter
|
|
||||||
|
|
||||||
if (brightness != other.brightness) return false
|
|
||||||
if (contrast != other.contrast) return false
|
|
||||||
return isInverted == other.isInverted
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = brightness.hashCode()
|
|
||||||
result = 31 * result + contrast.hashCode()
|
|
||||||
result = 31 * result + isInverted.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class WebtoonFrameLayout @JvmOverloads constructor(
|
|||||||
@AttrRes defStyleAttr: Int = 0,
|
@AttrRes defStyleAttr: Int = 0,
|
||||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
private val target by lazy(LazyThreadSafetyMode.NONE) {
|
val target by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
findViewById<WebtoonImageView>(R.id.ssiv)
|
findViewById<WebtoonImageView>(R.id.ssiv)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,4 +24,4 @@ class WebtoonFrameLayout @JvmOverloads constructor(
|
|||||||
target.scrollBy(dy)
|
target.scrollBy(dy)
|
||||||
return target.getScroll() - oldScroll
|
return target.getScroll() - oldScroll
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,10 +84,22 @@ class WebtoonImageView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
width = width.coerceAtLeast(suggestedMinimumWidth)
|
width = width.coerceAtLeast(suggestedMinimumWidth)
|
||||||
height = height.coerceIn(suggestedMinimumHeight, parentHeight())
|
height = height.coerceAtLeast(suggestedMinimumHeight).coerceAtMost(parentHeight())
|
||||||
setMeasuredDimension(width, height)
|
setMeasuredDimension(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh)
|
||||||
|
if (oldh == h || oldw == 0 || oldh == 0 || scrollRange == SCROLL_UNKNOWN) return
|
||||||
|
|
||||||
|
computeScrollRange()
|
||||||
|
val container = parents.firstNotNullOfOrNull { it as? WebtoonFrameLayout } ?: return
|
||||||
|
val parentHeight = parentHeight()
|
||||||
|
if (scrollPos != 0 && container.bottom < parentHeight) {
|
||||||
|
scrollTo(scrollRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun scrollToInternal(pos: Int) {
|
private fun scrollToInternal(pos: Int) {
|
||||||
scrollPos = pos
|
scrollPos = pos
|
||||||
ct.set(sWidth / 2f, (height / 2f + pos.toFloat()) / minScale)
|
ct.set(sWidth / 2f, (height / 2f + pos.toFloat()) / minScale)
|
||||||
|
|||||||
@@ -2,16 +2,30 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
import androidx.core.view.ViewCompat.TYPE_TOUCH
|
import androidx.core.view.ViewCompat.TYPE_TOUCH
|
||||||
|
import androidx.core.view.forEach
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
|
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
import java.util.WeakHashMap
|
||||||
|
|
||||||
class WebtoonRecyclerView @JvmOverloads constructor(
|
class WebtoonRecyclerView @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
) : RecyclerView(context, attrs, defStyleAttr) {
|
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
private var onPageScrollListeners: MutableList<OnPageScrollListener>? = null
|
private var onPageScrollListeners: MutableList<OnPageScrollListener>? = null
|
||||||
|
private val detachedViews = WeakHashMap<View, Unit>()
|
||||||
|
|
||||||
|
override fun onChildDetachedFromWindow(child: View) {
|
||||||
|
super.onChildDetachedFromWindow(child)
|
||||||
|
detachedViews[child] = Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChildAttachedToWindow(child: View) {
|
||||||
|
super.onChildAttachedToWindow(child)
|
||||||
|
detachedViews.remove(child)
|
||||||
|
}
|
||||||
|
|
||||||
override fun startNestedScroll(axes: Int) = startNestedScroll(axes, TYPE_TOUCH)
|
override fun startNestedScroll(axes: Int) = startNestedScroll(axes, TYPE_TOUCH)
|
||||||
|
|
||||||
@@ -98,6 +112,15 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
|||||||
listeners.forEach { it.dispatchScroll(this, dy, centerPosition) }
|
listeners.forEach { it.dispatchScroll(this, dy, centerPosition) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun relayoutChildren() {
|
||||||
|
forEach { child ->
|
||||||
|
(child as WebtoonFrameLayout).target.requestLayout()
|
||||||
|
}
|
||||||
|
detachedViews.keys.forEach { child ->
|
||||||
|
(child as WebtoonFrameLayout).target.requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract class OnPageScrollListener {
|
abstract class OnPageScrollListener {
|
||||||
|
|
||||||
private var lastPosition = NO_POSITION
|
private var lastPosition = NO_POSITION
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Matrix
|
import android.graphics.Matrix
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
@@ -15,7 +15,7 @@ import android.widget.OverScroller
|
|||||||
import androidx.core.view.GestureDetectorCompat
|
import androidx.core.view.GestureDetectorCompat
|
||||||
|
|
||||||
private const val MAX_SCALE = 2.5f
|
private const val MAX_SCALE = 2.5f
|
||||||
private const val MIN_SCALE = 1f // under-scaling disabled due to buggy nested scroll
|
private const val MIN_SCALE = 0.5f
|
||||||
|
|
||||||
class WebtoonScalingFrame @JvmOverloads constructor(
|
class WebtoonScalingFrame @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
@@ -23,7 +23,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
|||||||
defStyles: Int = 0,
|
defStyles: Int = 0,
|
||||||
) : FrameLayout(context, attrs, defStyles), ScaleGestureDetector.OnScaleGestureListener {
|
) : FrameLayout(context, attrs, defStyles), ScaleGestureDetector.OnScaleGestureListener {
|
||||||
|
|
||||||
private val targetChild by lazy(LazyThreadSafetyMode.NONE) { getChildAt(0) }
|
private val targetChild by lazy(LazyThreadSafetyMode.NONE) { getChildAt(0) as WebtoonRecyclerView }
|
||||||
|
|
||||||
private val scaleDetector = ScaleGestureDetector(context, this)
|
private val scaleDetector = ScaleGestureDetector(context, this)
|
||||||
private val gestureDetector = GestureDetectorCompat(context, GestureListener())
|
private val gestureDetector = GestureDetectorCompat(context, GestureListener())
|
||||||
@@ -40,7 +40,6 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
|||||||
private var halfHeight = 0f
|
private var halfHeight = 0f
|
||||||
private val translateBounds = RectF()
|
private val translateBounds = RectF()
|
||||||
private val targetHitRect = Rect()
|
private val targetHitRect = Rect()
|
||||||
private var pendingScroll = 0
|
|
||||||
|
|
||||||
var isZoomEnable = true
|
var isZoomEnable = true
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -97,12 +96,11 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
|||||||
if (newHeight != targetChild.height) {
|
if (newHeight != targetChild.height) {
|
||||||
targetChild.layoutParams.height = newHeight
|
targetChild.layoutParams.height = newHeight
|
||||||
targetChild.requestLayout()
|
targetChild.requestLayout()
|
||||||
|
targetChild.relayoutChildren()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scale < 1) {
|
if (scale < 1) {
|
||||||
targetChild.getHitRect(targetHitRect)
|
targetChild.getHitRect(targetHitRect)
|
||||||
targetChild.scrollBy(0, pendingScroll)
|
|
||||||
pendingScroll = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +122,6 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
|||||||
else -> 0f
|
else -> 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingScroll = dy.toInt()
|
|
||||||
transformMatrix.postTranslate(dx, dy)
|
transformMatrix.postTranslate(dx, dy)
|
||||||
syncMatrixValues()
|
syncMatrixValues()
|
||||||
}
|
}
|
||||||
@@ -159,9 +156,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
|||||||
|
|
||||||
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = true
|
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = true
|
||||||
|
|
||||||
override fun onScaleEnd(p0: ScaleGestureDetector) {
|
override fun onScaleEnd(p0: ScaleGestureDetector) = Unit
|
||||||
pendingScroll = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private inner class GestureListener : GestureDetector.SimpleOnGestureListener(), Runnable {
|
private inner class GestureListener : GestureDetector.SimpleOnGestureListener(), Runnable {
|
||||||
@@ -175,7 +170,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
|||||||
|
|
||||||
override fun onDoubleTap(e: MotionEvent): Boolean {
|
override fun onDoubleTap(e: MotionEvent): Boolean {
|
||||||
val newScale = if (scale != 1f) 1f else MAX_SCALE * 0.8f
|
val newScale = if (scale != 1f) 1f else MAX_SCALE * 0.8f
|
||||||
ObjectAnimator.ofFloat(scale, newScale).run {
|
ValueAnimator.ofFloat(scale, newScale).run {
|
||||||
interpolator = AccelerateDecelerateInterpolator()
|
interpolator = AccelerateDecelerateInterpolator()
|
||||||
duration = 300
|
duration = 300
|
||||||
addUpdateListener {
|
addUpdateListener {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
|
|||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||||
|
|
||||||
class PageThumbnail(
|
data class PageThumbnail(
|
||||||
val isCurrent: Boolean,
|
val isCurrent: Boolean,
|
||||||
val repository: MangaRepository,
|
val repository: MangaRepository,
|
||||||
val page: ReaderPage,
|
val page: ReaderPage,
|
||||||
@@ -16,23 +16,4 @@ class PageThumbnail(
|
|||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is PageThumbnail && page == other.page
|
return other is PageThumbnail && page == other.page
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as PageThumbnail
|
|
||||||
|
|
||||||
if (isCurrent != other.isCurrent) return false
|
|
||||||
if (repository != other.repository) return false
|
|
||||||
return page == other.page
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = isCurrent.hashCode()
|
|
||||||
result = 31 * result + repository.hashCode()
|
|
||||||
result = 31 * result + page.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.setTextColorAttr
|
import org.koitharu.kotatsu.core.util.ext.setTextColorAttr
|
||||||
@@ -56,8 +55,4 @@ fun pageThumbnailAD(
|
|||||||
text = (item.number).toString()
|
text = (item.number).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewThumb.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,40 +2,17 @@ package org.koitharu.kotatsu.scrobbling.common.domain.model
|
|||||||
|
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
|
||||||
class ScrobblerManga(
|
data class ScrobblerManga(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
val altName: String?,
|
val altName: String?,
|
||||||
val cover: String,
|
val cover: String,
|
||||||
val url: String,
|
val url: String,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|
||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is ScrobblerManga && other.id == id
|
return other is ScrobblerManga && other.id == id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ScrobblerManga
|
|
||||||
|
|
||||||
if (id != other.id) return false
|
|
||||||
if (name != other.name) return false
|
|
||||||
if (altName != other.altName) return false
|
|
||||||
if (cover != other.cover) return false
|
|
||||||
return url == other.url
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = id.hashCode()
|
|
||||||
result = 31 * result + name.hashCode()
|
|
||||||
result = 31 * result + altName.hashCode()
|
|
||||||
result = 31 * result + cover.hashCode()
|
|
||||||
result = 31 * result + url.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "ScrobblerManga #$id \"$name\" $url"
|
return "ScrobblerManga #$id \"$name\" $url"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,8 @@
|
|||||||
package org.koitharu.kotatsu.scrobbling.common.domain.model
|
package org.koitharu.kotatsu.scrobbling.common.domain.model
|
||||||
|
|
||||||
class ScrobblerUser(
|
data class ScrobblerUser(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val avatar: String,
|
val avatar: String,
|
||||||
val service: ScrobblerService,
|
val service: ScrobblerService,
|
||||||
) {
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ScrobblerUser
|
|
||||||
|
|
||||||
if (id != other.id) return false
|
|
||||||
if (nickname != other.nickname) return false
|
|
||||||
if (avatar != other.avatar) return false
|
|
||||||
return service == other.service
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = id.hashCode()
|
|
||||||
result = 31 * result + nickname.hashCode()
|
|
||||||
result = 31 * result + avatar.hashCode()
|
|
||||||
result = 31 * result + service.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.scrobbling.common.domain.model
|
|||||||
|
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
|
||||||
class ScrobblingInfo(
|
data class ScrobblingInfo(
|
||||||
val scrobbler: ScrobblerService,
|
val scrobbler: ScrobblerService,
|
||||||
val mangaId: Long,
|
val mangaId: Long,
|
||||||
val targetId: Long,
|
val targetId: Long,
|
||||||
@@ -19,38 +19,4 @@ class ScrobblingInfo(
|
|||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is ScrobblingInfo && other.scrobbler == scrobbler
|
return other is ScrobblingInfo && other.scrobbler == scrobbler
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ScrobblingInfo
|
|
||||||
|
|
||||||
if (scrobbler != other.scrobbler) return false
|
|
||||||
if (mangaId != other.mangaId) return false
|
|
||||||
if (targetId != other.targetId) return false
|
|
||||||
if (status != other.status) return false
|
|
||||||
if (chapter != other.chapter) return false
|
|
||||||
if (comment != other.comment) return false
|
|
||||||
if (rating != other.rating) return false
|
|
||||||
if (title != other.title) return false
|
|
||||||
if (coverUrl != other.coverUrl) return false
|
|
||||||
if (description != other.description) return false
|
|
||||||
return externalUrl == other.externalUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = scrobbler.hashCode()
|
|
||||||
result = 31 * result + mangaId.hashCode()
|
|
||||||
result = 31 * result + targetId.hashCode()
|
|
||||||
result = 31 * result + (status?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + chapter
|
|
||||||
result = 31 * result + (comment?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + rating.hashCode()
|
|
||||||
result = 31 * result + title.hashCode()
|
|
||||||
result = 31 * result + coverUrl.hashCode()
|
|
||||||
result = 31 * result + (description?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + externalUrl.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.databinding.ItemScrobblingMangaBinding
|
import org.koitharu.kotatsu.databinding.ItemScrobblingMangaBinding
|
||||||
@@ -34,8 +33,4 @@ fun scrobblingMangaAD(
|
|||||||
binding.textViewTitle.text = item.title
|
binding.textViewTitle.text = item.title
|
||||||
binding.ratingBar.rating = item.rating * binding.ratingBar.numStars
|
binding.ratingBar.rating = item.rating * binding.ratingBar.numStars
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import coil.ImageLoader
|
|||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||||
@@ -35,8 +34,4 @@ fun scrobblingMangaAD(
|
|||||||
enqueueWith(coil)
|
enqueueWith(coil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
|
||||||
class ScrobblerHint(
|
data class ScrobblerHint(
|
||||||
@DrawableRes val icon: Int,
|
@DrawableRes val icon: Int,
|
||||||
@StringRes val textPrimary: Int,
|
@StringRes val textPrimary: Int,
|
||||||
@StringRes val textSecondary: Int,
|
@StringRes val textSecondary: Int,
|
||||||
@@ -15,26 +15,4 @@ class ScrobblerHint(
|
|||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is ScrobblerHint && other.textPrimary == textPrimary
|
return other is ScrobblerHint && other.textPrimary == textPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ScrobblerHint
|
|
||||||
|
|
||||||
if (icon != other.icon) return false
|
|
||||||
if (textPrimary != other.textPrimary) return false
|
|
||||||
if (textSecondary != other.textSecondary) return false
|
|
||||||
if (error != other.error) return false
|
|
||||||
return actionStringRes == other.actionStringRes
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = icon
|
|
||||||
result = 31 * result + textPrimary
|
|
||||||
result = 31 * result + textSecondary
|
|
||||||
result = 31 * result + (error?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + actionStringRes
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
|
|||||||
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
|
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
|
||||||
class MultiSearchListModel(
|
data class MultiSearchListModel(
|
||||||
val source: MangaSource,
|
val source: MangaSource,
|
||||||
val hasMore: Boolean,
|
val hasMore: Boolean,
|
||||||
val list: List<MangaItemModel>,
|
val list: List<MangaItemModel>,
|
||||||
@@ -23,24 +23,4 @@ class MultiSearchListModel(
|
|||||||
super.getChangePayload(previousState)
|
super.getChangePayload(previousState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as MultiSearchListModel
|
|
||||||
|
|
||||||
if (source != other.source) return false
|
|
||||||
if (hasMore != other.hasMore) return false
|
|
||||||
if (list != other.list) return false
|
|
||||||
return error == other.error
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = source.hashCode()
|
|
||||||
result = 31 * result + hasMore.hashCode()
|
|
||||||
result = 31 * result + list.hashCode()
|
|
||||||
result = 31 * result + (error?.hashCode() ?: 0)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
|
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
|
||||||
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
@@ -51,8 +50,4 @@ fun searchSuggestionSourceAD(
|
|||||||
enqueueWith(coil)
|
enqueueWith(coil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
|
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
|
||||||
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
@@ -66,10 +65,6 @@ private fun searchSuggestionMangaGridAD(
|
|||||||
}
|
}
|
||||||
binding.textViewTitle.text = item.title
|
binding.textViewTitle.text = item.title
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SuggestionMangaDiffCallback : DiffUtil.ItemCallback<Manga>() {
|
private class SuggestionMangaDiffCallback : DiffUtil.ItemCallback<Manga>() {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import org.koitharu.kotatsu.core.parser.favicon.faviconUri
|
|||||||
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnTipCloseListener
|
import org.koitharu.kotatsu.core.ui.list.OnTipCloseListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.crossfade
|
import org.koitharu.kotatsu.core.util.ext.crossfade
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
@@ -94,10 +93,6 @@ fun sourceConfigItemCheckableDelegate(
|
|||||||
enqueueWith(coil)
|
enqueueWith(coil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewIcon.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sourceConfigItemDelegate2(
|
fun sourceConfigItemDelegate2(
|
||||||
@@ -143,10 +138,6 @@ fun sourceConfigItemDelegate2(
|
|||||||
enqueueWith(coil)
|
enqueueWith(coil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewIcon.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sourceConfigTipDelegate(
|
fun sourceConfigTipDelegate(
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ 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.io.File
|
import java.io.File
|
||||||
|
|
||||||
class DirectoryModel(
|
data class DirectoryModel(
|
||||||
val title: String?,
|
val title: String?,
|
||||||
@StringRes val titleRes: Int,
|
@StringRes val titleRes: Int,
|
||||||
val file: File?,
|
val file: File?,
|
||||||
val isChecked: Boolean,
|
val isChecked: Boolean,
|
||||||
val isAvailable: Boolean,
|
val isAvailable: Boolean,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|
||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is DirectoryModel && other.file == file && other.title == title && other.titleRes == titleRes
|
return other is DirectoryModel && other.file == file && other.title == title && other.titleRes == titleRes
|
||||||
}
|
}
|
||||||
@@ -24,26 +23,4 @@ class DirectoryModel(
|
|||||||
super.getChangePayload(previousState)
|
super.getChangePayload(previousState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as DirectoryModel
|
|
||||||
|
|
||||||
if (title != other.title) return false
|
|
||||||
if (titleRes != other.titleRes) return false
|
|
||||||
if (file != other.file) return false
|
|
||||||
if (isChecked != other.isChecked) return false
|
|
||||||
return isAvailable == other.isAvailable
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = title?.hashCode() ?: 0
|
|
||||||
result = 31 * result + titleRes
|
|
||||||
result = 31 * result + (file?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + isChecked.hashCode()
|
|
||||||
result = 31 * result + isAvailable.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,13 @@
|
|||||||
package org.koitharu.kotatsu.settings.userdata
|
package org.koitharu.kotatsu.settings.userdata
|
||||||
|
|
||||||
class StorageUsage(
|
data class StorageUsage(
|
||||||
val savedManga: Item,
|
val savedManga: Item,
|
||||||
val pagesCache: Item,
|
val pagesCache: Item,
|
||||||
val otherCache: Item,
|
val otherCache: Item,
|
||||||
val available: Item,
|
val available: Item,
|
||||||
) {
|
) {
|
||||||
|
data class Item(
|
||||||
class Item(
|
|
||||||
val bytes: Long,
|
val bytes: Long,
|
||||||
val percent: Float,
|
val percent: Float,
|
||||||
) {
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Item
|
|
||||||
|
|
||||||
if (bytes != other.bytes) return false
|
|
||||||
return percent == other.percent
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = bytes.hashCode()
|
|
||||||
result = 31 * result + percent.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as StorageUsage
|
|
||||||
|
|
||||||
if (savedManga != other.savedManga) return false
|
|
||||||
if (pagesCache != other.pagesCache) return false
|
|
||||||
if (otherCache != other.otherCache) return false
|
|
||||||
return available == other.available
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = savedManga.hashCode()
|
|
||||||
result = 31 * result + pagesCache.hashCode()
|
|
||||||
result = 31 * result + otherCache.hashCode()
|
|
||||||
result = 31 * result + available.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import android.view.ViewTreeObserver
|
|||||||
import android.widget.HorizontalScrollView
|
import android.widget.HorizontalScrollView
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updatePaddingRelative
|
||||||
import androidx.customview.view.AbsSavedState
|
import androidx.customview.view.AbsSavedState
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceViewHolder
|
import androidx.preference.PreferenceViewHolder
|
||||||
@@ -52,7 +53,11 @@ class ThemeChooserPreference @JvmOverloads constructor(
|
|||||||
binding.linear.removeAllViews()
|
binding.linear.removeAllViews()
|
||||||
for (theme in entries) {
|
for (theme in entries) {
|
||||||
val context = ContextThemeWrapper(context, theme.styleResId)
|
val context = ContextThemeWrapper(context, theme.styleResId)
|
||||||
val item = ItemColorSchemeBinding.inflate(LayoutInflater.from(context), binding.linear, false)
|
val item =
|
||||||
|
ItemColorSchemeBinding.inflate(LayoutInflater.from(context), binding.linear, false)
|
||||||
|
if (binding.linear.childCount == 0) {
|
||||||
|
item.root.updatePaddingRelative(start = 0)
|
||||||
|
}
|
||||||
val isSelected = theme == currentValue
|
val isSelected = theme == currentValue
|
||||||
item.card.isChecked = isSelected
|
item.card.isChecked = isSelected
|
||||||
item.card.strokeWidth = if (isSelected) context.resources.getDimensionPixelSize(
|
item.card.strokeWidth = if (isSelected) context.resources.getDimensionPixelSize(
|
||||||
@@ -76,7 +81,8 @@ class ThemeChooserPreference @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
binding.scrollView.viewTreeObserver.run {
|
binding.scrollView.viewTreeObserver.run {
|
||||||
scrollPersistListener?.let { removeOnScrollChangedListener(it) }
|
scrollPersistListener?.let { removeOnScrollChangedListener(it) }
|
||||||
scrollPersistListener = ScrollPersistListener(WeakReference(binding.scrollView), lastScrollPosition)
|
scrollPersistListener =
|
||||||
|
ScrollPersistListener(WeakReference(binding.scrollView), lastScrollPosition)
|
||||||
addOnScrollChangedListener(scrollPersistListener)
|
addOnScrollChangedListener(scrollPersistListener)
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
@@ -133,7 +139,7 @@ class ThemeChooserPreference @JvmOverloads constructor(
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
superState: Parcelable,
|
superState: Parcelable,
|
||||||
scrollPosition: Int
|
scrollPosition: Int,
|
||||||
) : super(superState) {
|
) : super(superState) {
|
||||||
this.scrollPosition = scrollPosition
|
this.scrollPosition = scrollPosition
|
||||||
}
|
}
|
||||||
@@ -151,7 +157,8 @@ class ThemeChooserPreference @JvmOverloads constructor(
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@JvmField
|
@JvmField
|
||||||
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
|
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
|
||||||
override fun createFromParcel(`in`: Parcel) = SavedState(`in`, SavedState::class.java.classLoader)
|
override fun createFromParcel(`in`: Parcel) =
|
||||||
|
SavedState(`in`, SavedState::class.java.classLoader)
|
||||||
|
|
||||||
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
|
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,8 @@
|
|||||||
package org.koitharu.kotatsu.sync.domain
|
package org.koitharu.kotatsu.sync.domain
|
||||||
|
|
||||||
class SyncAuthResult(
|
data class SyncAuthResult(
|
||||||
val host: String,
|
val host: String,
|
||||||
val email: String,
|
val email: String,
|
||||||
val password: String,
|
val password: String,
|
||||||
val token: String,
|
val token: String,
|
||||||
) {
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as SyncAuthResult
|
|
||||||
|
|
||||||
if (host != other.host) return false
|
|
||||||
if (email != other.email) return false
|
|
||||||
if (password != other.password) return false
|
|
||||||
return token == other.token
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = host.hashCode()
|
|
||||||
result = 31 * result + email.hashCode()
|
|
||||||
result = 31 * result + password.hashCode()
|
|
||||||
result = 31 * result + token.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,31 +3,12 @@ package org.koitharu.kotatsu.tracker.domain.model
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
|
||||||
class MangaTracking(
|
data class MangaTracking(
|
||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
val lastChapterId: Long,
|
val lastChapterId: Long,
|
||||||
val lastCheck: Date?,
|
val lastCheck: Date?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun isEmpty(): Boolean {
|
fun isEmpty(): Boolean {
|
||||||
return lastChapterId == 0L
|
return lastChapterId == 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as MangaTracking
|
|
||||||
|
|
||||||
if (manga != other.manga) return false
|
|
||||||
if (lastChapterId != other.lastChapterId) return false
|
|
||||||
return lastCheck == other.lastCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = manga.hashCode()
|
|
||||||
result = 31 * result + lastChapterId.hashCode()
|
|
||||||
result = 31 * result + (lastCheck?.hashCode() ?: 0)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import coil.ImageLoader
|
|||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.isBold
|
import org.koitharu.kotatsu.core.util.ext.isBold
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
@@ -44,8 +43,4 @@ fun feedItemAD(
|
|||||||
item.count,
|
item.count,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
|
||||||
binding.imageViewCover.disposeImageRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.tracker.ui.feed.model
|
|||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
|
||||||
class FeedItem(
|
data class FeedItem(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val imageUrl: String,
|
val imageUrl: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
@@ -11,32 +11,7 @@ class FeedItem(
|
|||||||
val count: Int,
|
val count: Int,
|
||||||
val isNew: Boolean,
|
val isNew: Boolean,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|
||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is FeedItem && other.id == id
|
return other is FeedItem && other.id == id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as FeedItem
|
|
||||||
|
|
||||||
if (id != other.id) return false
|
|
||||||
if (imageUrl != other.imageUrl) return false
|
|
||||||
if (title != other.title) return false
|
|
||||||
if (manga != other.manga) return false
|
|
||||||
if (count != other.count) return false
|
|
||||||
return isNew == other.isNew
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = id.hashCode()
|
|
||||||
result = 31 * result + imageUrl.hashCode()
|
|
||||||
result = 31 * result + title.hashCode()
|
|
||||||
result = 31 * result + manga.hashCode()
|
|
||||||
result = 31 * result + count
|
|
||||||
result = 31 * result + isNew.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,7 @@ package org.koitharu.kotatsu.tracker.work
|
|||||||
|
|
||||||
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
||||||
|
|
||||||
class TrackingItem(
|
data class TrackingItem(
|
||||||
val tracking: MangaTracking,
|
val tracking: MangaTracking,
|
||||||
val channelId: String?,
|
val channelId: String?,
|
||||||
) {
|
)
|
||||||
|
|
||||||
operator fun component1() = tracking
|
|
||||||
|
|
||||||
operator fun component2() = channelId
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as TrackingItem
|
|
||||||
|
|
||||||
if (tracking != other.tracking) return false
|
|
||||||
return channelId == other.channelId
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = tracking.hashCode()
|
|
||||||
result = 31 * result + channelId.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class RecentListFactory(
|
|||||||
.data(item.coverUrl)
|
.data(item.coverUrl)
|
||||||
.size(coverSize)
|
.size(coverSize)
|
||||||
.tag(item.source)
|
.tag(item.source)
|
||||||
|
.tag(item)
|
||||||
.transformations(transformation)
|
.transformations(transformation)
|
||||||
.build(),
|
.build(),
|
||||||
).getDrawableOrThrow().toBitmap()
|
).getDrawableOrThrow().toBitmap()
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package org.koitharu.kotatsu.widget.recent
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.graphics.Insets
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
|
import org.koitharu.kotatsu.databinding.ActivityAppwidgetRecentBinding
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class RecentWidgetConfigActivity :
|
||||||
|
BaseActivity<ActivityAppwidgetRecentBinding>(),
|
||||||
|
View.OnClickListener {
|
||||||
|
|
||||||
|
private lateinit var config: AppWidgetConfig
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(ActivityAppwidgetRecentBinding.inflate(layoutInflater))
|
||||||
|
supportActionBar?.run {
|
||||||
|
setDisplayHomeAsUpEnabled(true)
|
||||||
|
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
|
||||||
|
}
|
||||||
|
viewBinding.buttonDone.setOnClickListener(this)
|
||||||
|
val appWidgetId = intent?.getIntExtra(
|
||||||
|
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||||
|
AppWidgetManager.INVALID_APPWIDGET_ID,
|
||||||
|
) ?: AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
|
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
|
||||||
|
finishAfterTransition()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config = AppWidgetConfig(this, RecentWidgetProvider::class.java, appWidgetId)
|
||||||
|
viewBinding.switchBackground.isChecked = config.hasBackground
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
when (v.id) {
|
||||||
|
R.id.button_done -> {
|
||||||
|
config.hasBackground = viewBinding.switchBackground.isChecked
|
||||||
|
updateWidget()
|
||||||
|
setResult(
|
||||||
|
Activity.RESULT_OK,
|
||||||
|
Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId),
|
||||||
|
)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWindowInsetsChanged(insets: Insets) {
|
||||||
|
viewBinding.root.updatePadding(
|
||||||
|
left = insets.left,
|
||||||
|
right = insets.right,
|
||||||
|
bottom = insets.bottom,
|
||||||
|
top = insets.top,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateWidget() {
|
||||||
|
val intent = Intent(this, RecentWidgetProvider::class.java)
|
||||||
|
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||||
|
val ids = intArrayOf(config.widgetId)
|
||||||
|
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,43 +2,52 @@ package org.koitharu.kotatsu.widget.recent
|
|||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.appwidget.AppWidgetProvider
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.core.app.PendingIntentCompat
|
import androidx.core.app.PendingIntentCompat
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseAppWidgetProvider
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||||
|
|
||||||
class RecentWidgetProvider : AppWidgetProvider() {
|
class RecentWidgetProvider : BaseAppWidgetProvider() {
|
||||||
|
|
||||||
override fun onUpdate(
|
override fun onUpdate(
|
||||||
context: Context,
|
context: Context,
|
||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
appWidgetIds: IntArray
|
appWidgetIds: IntArray
|
||||||
) {
|
) {
|
||||||
appWidgetIds.forEach { id ->
|
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||||
val views = RemoteViews(context.packageName, R.layout.widget_recent)
|
|
||||||
val adapter = Intent(context, RecentWidgetService::class.java)
|
|
||||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
|
|
||||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
|
||||||
views.setRemoteAdapter(R.id.stackView, adapter)
|
|
||||||
val intent = Intent(context, ReaderActivity::class.java)
|
|
||||||
intent.action = ReaderActivity.ACTION_MANGA_READ
|
|
||||||
views.setPendingIntentTemplate(
|
|
||||||
R.id.stackView,
|
|
||||||
PendingIntentCompat.getActivity(
|
|
||||||
context,
|
|
||||||
0,
|
|
||||||
intent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
views.setEmptyView(R.id.stackView, R.id.textView_holder)
|
|
||||||
appWidgetManager.updateAppWidget(id, views)
|
|
||||||
}
|
|
||||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stackView)
|
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stackView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onUpdateWidget(context: Context, config: AppWidgetConfig): RemoteViews {
|
||||||
|
val views = RemoteViews(context.packageName, R.layout.widget_recent)
|
||||||
|
if (!config.hasBackground) {
|
||||||
|
views.setInt(R.id.widget_root, "setBackgroundColor", Color.TRANSPARENT)
|
||||||
|
} else {
|
||||||
|
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
||||||
|
}
|
||||||
|
val adapter = Intent(context, RecentWidgetService::class.java)
|
||||||
|
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
||||||
|
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||||
|
views.setRemoteAdapter(R.id.stackView, adapter)
|
||||||
|
val intent = Intent(context, ReaderActivity::class.java)
|
||||||
|
intent.action = ReaderActivity.ACTION_MANGA_READ
|
||||||
|
views.setPendingIntentTemplate(
|
||||||
|
R.id.stackView,
|
||||||
|
PendingIntentCompat.getActivity(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
views.setEmptyView(R.id.stackView, R.id.textView_holder)
|
||||||
|
return views
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||||
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||||
|
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
||||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
@@ -27,7 +28,7 @@ class ShelfListFactory(
|
|||||||
) : RemoteViewsService.RemoteViewsFactory {
|
) : RemoteViewsService.RemoteViewsFactory {
|
||||||
|
|
||||||
private val dataSet = ArrayList<Manga>()
|
private val dataSet = ArrayList<Manga>()
|
||||||
private val config = AppWidgetConfig(context, widgetId)
|
private val config = AppWidgetConfig(context, ShelfWidgetProvider::class.java, widgetId)
|
||||||
private val transformation = RoundedCornersTransformation(
|
private val transformation = RoundedCornersTransformation(
|
||||||
context.resources.getDimension(R.dimen.appwidget_corner_radius_inner),
|
context.resources.getDimension(R.dimen.appwidget_corner_radius_inner),
|
||||||
)
|
)
|
||||||
@@ -66,7 +67,8 @@ class ShelfListFactory(
|
|||||||
.data(item.coverUrl)
|
.data(item.coverUrl)
|
||||||
.size(coverSize)
|
.size(coverSize)
|
||||||
.tag(item.source)
|
.tag(item.source)
|
||||||
.transformations(transformation)
|
.tag(item)
|
||||||
|
.transformations(transformation, TrimTransformation())
|
||||||
.build(),
|
.build(),
|
||||||
).getDrawableOrThrow().toBitmap()
|
).getDrawableOrThrow().toBitmap()
|
||||||
}.onSuccess { cover ->
|
}.onSuccess { cover ->
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
@@ -19,14 +18,14 @@ import org.koitharu.kotatsu.core.ui.BaseActivity
|
|||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
|
import org.koitharu.kotatsu.databinding.ActivityAppwidgetShelfBinding
|
||||||
import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter
|
import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter
|
||||||
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
|
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ShelfConfigActivity :
|
class ShelfWidgetConfigActivity :
|
||||||
BaseActivity<ActivityCategoriesBinding>(),
|
BaseActivity<ActivityAppwidgetShelfBinding>(),
|
||||||
OnListItemClickListener<CategoryItem>,
|
OnListItemClickListener<CategoryItem>,
|
||||||
View.OnClickListener {
|
View.OnClickListener {
|
||||||
|
|
||||||
@@ -37,16 +36,14 @@ class ShelfConfigActivity :
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
|
setContentView(ActivityAppwidgetShelfBinding.inflate(layoutInflater))
|
||||||
supportActionBar?.run {
|
supportActionBar?.run {
|
||||||
setDisplayHomeAsUpEnabled(true)
|
setDisplayHomeAsUpEnabled(true)
|
||||||
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
|
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
|
||||||
}
|
}
|
||||||
adapter = CategorySelectAdapter(this)
|
adapter = CategorySelectAdapter(this)
|
||||||
viewBinding.recyclerView.adapter = adapter
|
viewBinding.recyclerView.adapter = adapter
|
||||||
viewBinding.buttonDone.isVisible = true
|
|
||||||
viewBinding.buttonDone.setOnClickListener(this)
|
viewBinding.buttonDone.setOnClickListener(this)
|
||||||
viewBinding.fabAdd.hide()
|
|
||||||
val appWidgetId = intent?.getIntExtra(
|
val appWidgetId = intent?.getIntExtra(
|
||||||
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||||
AppWidgetManager.INVALID_APPWIDGET_ID,
|
AppWidgetManager.INVALID_APPWIDGET_ID,
|
||||||
@@ -55,8 +52,9 @@ class ShelfConfigActivity :
|
|||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config = AppWidgetConfig(this, appWidgetId)
|
config = AppWidgetConfig(this, ShelfWidgetProvider::class.java, appWidgetId)
|
||||||
viewModel.checkedId = config.categoryId
|
viewModel.checkedId = config.categoryId
|
||||||
|
viewBinding.switchBackground.isChecked = config.hasBackground
|
||||||
|
|
||||||
viewModel.content.observe(this, this::onContentChanged)
|
viewModel.content.observe(this, this::onContentChanged)
|
||||||
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
|
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
|
||||||
@@ -66,6 +64,7 @@ class ShelfConfigActivity :
|
|||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.button_done -> {
|
R.id.button_done -> {
|
||||||
config.categoryId = viewModel.checkedId
|
config.categoryId = viewModel.checkedId
|
||||||
|
config.hasBackground = viewBinding.switchBackground.isChecked
|
||||||
updateWidget()
|
updateWidget()
|
||||||
setResult(
|
setResult(
|
||||||
Activity.RESULT_OK,
|
Activity.RESULT_OK,
|
||||||
@@ -81,11 +80,6 @@ class ShelfConfigActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) {
|
override fun onWindowInsetsChanged(insets: Insets) {
|
||||||
viewBinding.fabAdd.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
||||||
rightMargin = topMargin + insets.right
|
|
||||||
leftMargin = topMargin + insets.left
|
|
||||||
bottomMargin = topMargin + insets.bottom
|
|
||||||
}
|
|
||||||
viewBinding.recyclerView.updatePadding(
|
viewBinding.recyclerView.updatePadding(
|
||||||
left = insets.left,
|
left = insets.left,
|
||||||
right = insets.right,
|
right = insets.right,
|
||||||
@@ -2,43 +2,52 @@ package org.koitharu.kotatsu.widget.shelf
|
|||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.appwidget.AppWidgetProvider
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.core.app.PendingIntentCompat
|
import androidx.core.app.PendingIntentCompat
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseAppWidgetProvider
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||||
|
|
||||||
class ShelfWidgetProvider : AppWidgetProvider() {
|
class ShelfWidgetProvider : BaseAppWidgetProvider() {
|
||||||
|
|
||||||
override fun onUpdate(
|
override fun onUpdate(
|
||||||
context: Context,
|
context: Context,
|
||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
appWidgetIds: IntArray
|
appWidgetIds: IntArray
|
||||||
) {
|
) {
|
||||||
appWidgetIds.forEach { id ->
|
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||||
val views = RemoteViews(context.packageName, R.layout.widget_shelf)
|
|
||||||
val adapter = Intent(context, ShelfWidgetService::class.java)
|
|
||||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
|
|
||||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
|
||||||
views.setRemoteAdapter(R.id.gridView, adapter)
|
|
||||||
val intent = Intent(context, ReaderActivity::class.java)
|
|
||||||
intent.action = ReaderActivity.ACTION_MANGA_READ
|
|
||||||
views.setPendingIntentTemplate(
|
|
||||||
R.id.gridView,
|
|
||||||
PendingIntentCompat.getActivity(
|
|
||||||
context,
|
|
||||||
0,
|
|
||||||
intent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
views.setEmptyView(R.id.gridView, R.id.textView_holder)
|
|
||||||
appWidgetManager.updateAppWidget(id, views)
|
|
||||||
}
|
|
||||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.gridView)
|
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.gridView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onUpdateWidget(context: Context, config: AppWidgetConfig): RemoteViews {
|
||||||
|
val views = RemoteViews(context.packageName, R.layout.widget_shelf)
|
||||||
|
if (!config.hasBackground) {
|
||||||
|
views.setInt(R.id.widget_root, "setBackgroundColor", Color.TRANSPARENT)
|
||||||
|
} else {
|
||||||
|
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
||||||
|
}
|
||||||
|
val adapter = Intent(context, ShelfWidgetService::class.java)
|
||||||
|
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
||||||
|
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||||
|
views.setRemoteAdapter(R.id.gridView, adapter)
|
||||||
|
val intent = Intent(context, ReaderActivity::class.java)
|
||||||
|
intent.action = ReaderActivity.ACTION_MANGA_READ
|
||||||
|
views.setPendingIntentTemplate(
|
||||||
|
R.id.gridView,
|
||||||
|
PendingIntentCompat.getActivity(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
views.setEmptyView(R.id.gridView, R.id.textView_holder)
|
||||||
|
return views
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<corners android:radius="@dimen/appwidget_corner_radius_inner" />
|
<corners android:radius="@dimen/appwidget_corner_radius_inner" />
|
||||||
<solid android:color="?android:panelColorBackground" />
|
<solid android:color="?android:panelColorBackground" />
|
||||||
</shape>
|
</shape>
|
||||||
|
|||||||
7
app/src/main/res/drawable/bg_appwidget_root.xml
Normal file
7
app/src/main/res/drawable/bg_appwidget_root.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="@dimen/appwidget_corner_radius_background" />
|
||||||
|
<solid android:color="?android:colorBackground" />
|
||||||
|
</shape>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/checked"
|
android:id="@+id/checked"
|
||||||
android:drawable="@drawable/ic_heart_outline"
|
android:drawable="@drawable/ic_heart"
|
||||||
android:state_checked="true" />
|
android:state_checked="true" />
|
||||||
|
|
||||||
<transition
|
<transition
|
||||||
|
|||||||
39
app/src/main/res/layout/activity_appwidget_recent.xml
Normal file
39
app/src/main/res/layout/activity_appwidget_recent.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?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="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:layout_collapseMode="pin"
|
||||||
|
tools:title="@string/recent_manga">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_done"
|
||||||
|
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
|
||||||
|
android:text="@string/done" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/switch_background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:paddingStart="?listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||||
|
android:text="@string/background"
|
||||||
|
android:textAppearance="?textAppearanceBodyMedium"
|
||||||
|
app:layout_scrollFlags="scroll" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
70
app/src/main/res/layout/activity_appwidget_shelf.xml
Normal file
70
app/src/main/res/layout/activity_appwidget_shelf.xml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
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="match_parent">
|
||||||
|
|
||||||
|
<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="?attr/actionBarSize"
|
||||||
|
app:layout_collapseMode="pin"
|
||||||
|
tools:title="@string/manga_shelf">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_done"
|
||||||
|
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
|
||||||
|
android:text="@string/done" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/switch_background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:checked="true"
|
||||||
|
android:paddingStart="?listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||||
|
android:text="@string/background"
|
||||||
|
android:textAppearance="?textAppearanceBodyMedium"
|
||||||
|
app:layout_scrollFlags="scroll" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:paddingStart="?listPreferredItemPaddingStart"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="@string/favourites_categories"
|
||||||
|
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="@dimen/list_spacing_normal"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||||
|
tools:listitem="@layout/item_checkable_single" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||||
app:layout_constraintBottom_toTopOf="@id/textView_subtitle"
|
app:layout_constraintBottom_toTopOf="@id/textView_subtitle"
|
||||||
app:layout_constraintEnd_toStartOf="@id/imageView_handle"
|
app:layout_constraintEnd_toStartOf="@id/imageView_edit"
|
||||||
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
|
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintVertical_chainStyle="packed"
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
@@ -120,10 +120,23 @@
|
|||||||
app:layout_constraintTop_toTopOf="@id/textView_subtitle"
|
app:layout_constraintTop_toTopOf="@id/textView_subtitle"
|
||||||
app:srcCompat="@drawable/ic_eye" />
|
app:srcCompat="@drawable/ic_eye" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView_edit"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/edit"
|
||||||
|
android:padding="@dimen/margin_normal"
|
||||||
|
android:src="@drawable/ic_edit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/imageView_handle"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/imageView_handle"
|
android:id="@+id/imageView_handle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
android:contentDescription="@string/reorder"
|
android:contentDescription="@string/reorder"
|
||||||
android:padding="@dimen/margin_normal"
|
android:padding="@dimen/margin_normal"
|
||||||
android:src="@drawable/ic_reorder_handle"
|
android:src="@drawable/ic_reorder_handle"
|
||||||
|
|||||||
@@ -9,10 +9,11 @@
|
|||||||
android:id="@+id/ssiv"
|
android:id="@+id/ssiv"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="1dp"
|
||||||
app:panEnabled="false"
|
app:panEnabled="false"
|
||||||
app:quickScaleEnabled="false"
|
app:quickScaleEnabled="false"
|
||||||
app:zoomEnabled="false" />
|
app:zoomEnabled="false" />
|
||||||
|
|
||||||
<include layout="@layout/layout_page_info" />
|
<include layout="@layout/layout_page_info" />
|
||||||
|
|
||||||
</org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonFrameLayout>
|
</org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonFrameLayout>
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="4dp"
|
android:padding="4dp"
|
||||||
android:theme="@style/Theme.Kotatsu.AppWidgetContainer">
|
android:theme="@style/Theme.Kotatsu.AppWidgetContainer"
|
||||||
|
tools:layout_width="116dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/rootLayout"
|
android:id="@+id/rootLayout"
|
||||||
@@ -13,14 +14,15 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:background="@drawable/bg_appwidget_card"
|
android:background="@drawable/bg_appwidget_card"
|
||||||
|
android:clipToOutline="true"
|
||||||
android:foreground="?android:selectableItemBackground"
|
android:foreground="?android:selectableItemBackground"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="4dp"
|
android:outlineProvider="background"
|
||||||
tools:ignore="UnusedAttribute,UselessParent">
|
tools:ignore="UnusedAttribute,UselessParent">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/imageView_cover"
|
android:id="@+id/imageView_cover"
|
||||||
android:layout_width="@dimen/widget_cover_width"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/widget_cover_height"
|
android:layout_height="@dimen/widget_cover_height"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
@@ -33,8 +35,11 @@
|
|||||||
android:elegantTextHeight="false"
|
android:elegantTextHeight="false"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:lines="2"
|
android:lines="2"
|
||||||
android:padding="2dp"
|
android:paddingHorizontal="6dp"
|
||||||
|
android:paddingTop="2dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="12sp"
|
||||||
tools:text="@tools:sample/lorem" />
|
tools:text="@tools:sample/lorem" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -8,70 +8,53 @@
|
|||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
android:paddingVertical="8dp"
|
||||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
|
||||||
tools:ignore="PrivateResource">
|
tools:ignore="PrivateResource">
|
||||||
|
|
||||||
<include layout="@layout/image_frame" />
|
<TextView
|
||||||
|
android:id="@android:id/title"
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:ellipsize="marquee"
|
||||||
android:layout_marginBottom="8dp"
|
android:labelFor="@id/scrollView"
|
||||||
android:clipChildren="false"
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
|
tools:ignore="LabelFor"
|
||||||
|
tools:text="@string/color_theme" />
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:id="@+id/scrollView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="vertical">
|
android:descendantFocusability="blocksDescendants"
|
||||||
|
android:scrollbars="none">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/linear"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:baselineAligned="true"
|
android:orientation="horizontal"
|
||||||
android:baselineAlignedChildIndex="0"
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
android:orientation="horizontal">
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" />
|
||||||
|
|
||||||
<TextView
|
</HorizontalScrollView>
|
||||||
android:id="@android:id/title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:labelFor="@id/seekbar"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
|
||||||
tools:ignore="LabelFor" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@android:id/summary"
|
android:id="@android:id/summary"
|
||||||
style="@style/PreferenceSummaryTextStyle"
|
style="@style/PreferenceSummaryTextStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true"
|
android:layout_marginTop="2dp"
|
||||||
android:textAlignment="viewStart"
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
android:textColor="?android:attr/textColorSecondary" />
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="?android:attr/textColorSecondary" />
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<HorizontalScrollView
|
|
||||||
android:id="@+id/scrollView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:descendantFocusability="blocksDescendants"
|
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:scrollbars="none"
|
|
||||||
tools:ignore="UnusedAttribute">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/linear"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal" />
|
|
||||||
|
|
||||||
</HorizontalScrollView>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?android:attr/colorBackground"
|
android:clipToOutline="true"
|
||||||
|
android:outlineProvider="background"
|
||||||
android:padding="4dp"
|
android:padding="4dp"
|
||||||
android:theme="@style/Theme.Kotatsu.AppWidgetContainer">
|
android:theme="@style/Theme.Kotatsu.AppWidgetContainer"
|
||||||
|
tools:background="@drawable/bg_appwidget_root">
|
||||||
|
|
||||||
<StackView
|
<StackView
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/stackView"
|
android:id="@+id/stackView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@@ -23,4 +25,4 @@
|
|||||||
android:text="@string/history_is_empty"
|
android:text="@string/history_is_empty"
|
||||||
android:textColor="?android:textColorPrimary" />
|
android:textColor="?android:textColorPrimary" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/widget_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?android:attr/colorBackground"
|
android:clipToOutline="true"
|
||||||
android:padding="4dp"
|
android:outlineProvider="background"
|
||||||
android:theme="@style/Theme.Kotatsu.AppWidgetContainer">
|
android:theme="@style/Theme.Kotatsu.AppWidgetContainer"
|
||||||
|
tools:background="?android:attr/colorBackground">
|
||||||
|
|
||||||
<GridView
|
<GridView
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/gridView"
|
android:id="@+id/gridView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:columnWidth="92dp"
|
android:columnWidth="92dp"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:numColumns="auto_fit"
|
android:numColumns="auto_fit"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:scrollbarStyle="insideOverlay"
|
||||||
tools:listitem="@layout/item_shelf" />
|
tools:listitem="@layout/item_shelf" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -27,4 +32,4 @@
|
|||||||
android:text="@string/you_have_not_favourites_yet"
|
android:text="@string/you_have_not_favourites_yet"
|
||||||
android:textColor="?android:attr/textColorPrimary" />
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -469,4 +469,7 @@
|
|||||||
<string name="disable_nsfw">Desactivar NSFW</string>
|
<string name="disable_nsfw">Desactivar NSFW</string>
|
||||||
<string name="too_many_requests_message">Demasiadas solicitudes. Vuelve a intentarlo más tarde</string>
|
<string name="too_many_requests_message">Demasiadas solicitudes. Vuelve a intentarlo más tarde</string>
|
||||||
<string name="related_manga_summary">Muestra una lista de mangas relacionados. En algunos casos puede ser inexacta o faltar</string>
|
<string name="related_manga_summary">Muestra una lista de mangas relacionados. En algunos casos puede ser inexacta o faltar</string>
|
||||||
|
<string name="advanced">Avanzado</string>
|
||||||
|
<string name="default_section">Sección predeterminada</string>
|
||||||
|
<string name="manga_list">Lista de mangas</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -7,29 +7,46 @@
|
|||||||
<item name="colorPrimary">@color/m3_sys_color_dynamic_dark_primary</item>
|
<item name="colorPrimary">@color/m3_sys_color_dynamic_dark_primary</item>
|
||||||
<item name="colorPrimaryContainer">@color/m3_sys_color_dynamic_dark_primary_container</item>
|
<item name="colorPrimaryContainer">@color/m3_sys_color_dynamic_dark_primary_container</item>
|
||||||
<item name="colorOnPrimary">@color/m3_sys_color_dynamic_dark_on_primary</item>
|
<item name="colorOnPrimary">@color/m3_sys_color_dynamic_dark_on_primary</item>
|
||||||
<item name="colorOnPrimaryContainer">@color/m3_sys_color_dynamic_dark_on_primary_container</item>
|
<item name="colorOnPrimaryContainer">@color/m3_sys_color_dynamic_dark_on_primary_container
|
||||||
|
</item>
|
||||||
<item name="colorPrimaryInverse">@color/m3_sys_color_dynamic_dark_inverse_primary</item>
|
<item name="colorPrimaryInverse">@color/m3_sys_color_dynamic_dark_inverse_primary</item>
|
||||||
<item name="colorSecondary">@color/m3_sys_color_dynamic_dark_secondary</item>
|
<item name="colorSecondary">@color/m3_sys_color_dynamic_dark_secondary</item>
|
||||||
<item name="colorSecondaryContainer">@color/m3_sys_color_dynamic_dark_secondary_container</item>
|
<item name="colorSecondaryContainer">@color/m3_sys_color_dynamic_dark_secondary_container
|
||||||
|
</item>
|
||||||
<item name="colorOnSecondary">@color/m3_sys_color_dynamic_dark_on_secondary</item>
|
<item name="colorOnSecondary">@color/m3_sys_color_dynamic_dark_on_secondary</item>
|
||||||
<item name="colorOnSecondaryContainer">@color/m3_sys_color_dynamic_dark_on_secondary_container</item>
|
<item name="colorOnSecondaryContainer">
|
||||||
|
@color/m3_sys_color_dynamic_dark_on_secondary_container
|
||||||
|
</item>
|
||||||
<item name="colorTertiary">@color/m3_sys_color_dynamic_dark_tertiary</item>
|
<item name="colorTertiary">@color/m3_sys_color_dynamic_dark_tertiary</item>
|
||||||
<item name="colorTertiaryContainer">@color/m3_sys_color_dynamic_dark_tertiary_container</item>
|
<item name="colorTertiaryContainer">@color/m3_sys_color_dynamic_dark_tertiary_container
|
||||||
|
</item>
|
||||||
<item name="colorOnTertiary">@color/m3_sys_color_dynamic_dark_on_tertiary</item>
|
<item name="colorOnTertiary">@color/m3_sys_color_dynamic_dark_on_tertiary</item>
|
||||||
<item name="colorOnTertiaryContainer">@color/m3_sys_color_dynamic_dark_on_tertiary_container</item>
|
<item name="colorOnTertiaryContainer">
|
||||||
|
@color/m3_sys_color_dynamic_dark_on_tertiary_container
|
||||||
|
</item>
|
||||||
<item name="colorSurface">@color/m3_sys_color_dynamic_dark_surface</item>
|
<item name="colorSurface">@color/m3_sys_color_dynamic_dark_surface</item>
|
||||||
<item name="colorSurfaceDim">@color/m3_sys_color_dynamic_dark_surface_dim</item>
|
<item name="colorSurfaceDim">@color/m3_sys_color_dynamic_dark_surface_dim</item>
|
||||||
<item name="colorSurfaceBright">@color/m3_sys_color_dynamic_dark_surface_bright</item>
|
<item name="colorSurfaceBright">@color/m3_sys_color_dynamic_dark_surface_bright</item>
|
||||||
<item name="colorSurfaceContainerLowest">@color/m3_sys_color_dynamic_dark_surface_container_lowest</item>
|
<item name="colorSurfaceContainerLowest">
|
||||||
<item name="colorSurfaceContainerLow">@color/m3_sys_color_dynamic_dark_surface_container_low</item>
|
@color/m3_sys_color_dynamic_dark_surface_container_lowest
|
||||||
|
</item>
|
||||||
|
<item name="colorSurfaceContainerLow">
|
||||||
|
@color/m3_sys_color_dynamic_dark_surface_container_low
|
||||||
|
</item>
|
||||||
<item name="colorSurfaceContainer">@color/m3_sys_color_dynamic_dark_surface_container</item>
|
<item name="colorSurfaceContainer">@color/m3_sys_color_dynamic_dark_surface_container</item>
|
||||||
<item name="colorSurfaceContainerHigh">@color/m3_sys_color_dynamic_dark_surface_container_high</item>
|
<item name="colorSurfaceContainerHigh">
|
||||||
<item name="colorSurfaceContainerHighest">@color/m3_sys_color_dynamic_dark_surface_container_highest</item>
|
@color/m3_sys_color_dynamic_dark_surface_container_high
|
||||||
|
</item>
|
||||||
|
<item name="colorSurfaceContainerHighest">
|
||||||
|
@color/m3_sys_color_dynamic_dark_surface_container_highest
|
||||||
|
</item>
|
||||||
<item name="colorSurfaceVariant">@color/m3_sys_color_dynamic_dark_surface_variant</item>
|
<item name="colorSurfaceVariant">@color/m3_sys_color_dynamic_dark_surface_variant</item>
|
||||||
<item name="colorOnSurface">@color/m3_sys_color_dynamic_dark_on_surface</item>
|
<item name="colorOnSurface">@color/m3_sys_color_dynamic_dark_on_surface</item>
|
||||||
<item name="colorOnSurfaceVariant">@color/m3_sys_color_dynamic_dark_on_surface_variant</item>
|
<item name="colorOnSurfaceVariant">@color/m3_sys_color_dynamic_dark_on_surface_variant
|
||||||
|
</item>
|
||||||
<item name="colorSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_surface</item>
|
<item name="colorSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_surface</item>
|
||||||
<item name="colorOnSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_on_surface</item>
|
<item name="colorOnSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_on_surface
|
||||||
|
</item>
|
||||||
<item name="colorOnBackground">@color/m3_sys_color_dynamic_dark_on_background</item>
|
<item name="colorOnBackground">@color/m3_sys_color_dynamic_dark_on_background</item>
|
||||||
<item name="colorError">@color/m3_sys_color_dark_error</item>
|
<item name="colorError">@color/m3_sys_color_dark_error</item>
|
||||||
<item name="colorErrorContainer">@color/m3_sys_color_dark_error_container</item>
|
<item name="colorErrorContainer">@color/m3_sys_color_dark_error_container</item>
|
||||||
@@ -42,26 +59,42 @@
|
|||||||
<item name="m3ColorExploreButton">@android:color/system_neutral2_800</item>
|
<item name="m3ColorExploreButton">@android:color/system_neutral2_800</item>
|
||||||
<item name="m3ColorBottomMenuBackground">@android:color/system_neutral2_800</item>
|
<item name="m3ColorBottomMenuBackground">@android:color/system_neutral2_800</item>
|
||||||
<!-- Default Framework Text Colors. -->
|
<!-- Default Framework Text Colors. -->
|
||||||
<item name="android:textColorPrimary">@color/m3_dynamic_dark_default_color_primary_text</item>
|
<item name="android:textColorPrimary">@color/m3_dynamic_dark_default_color_primary_text
|
||||||
<item name="android:textColorPrimaryInverse">@color/m3_dynamic_default_color_primary_text</item>
|
</item>
|
||||||
<item name="android:textColorSecondary">@color/m3_dynamic_dark_default_color_secondary_text</item>
|
<item name="android:textColorPrimaryInverse">@color/m3_dynamic_default_color_primary_text
|
||||||
<item name="android:textColorSecondaryInverse">@color/m3_dynamic_default_color_secondary_text</item>
|
</item>
|
||||||
<item name="android:textColorTertiary">@color/m3_dynamic_dark_default_color_secondary_text</item>
|
<item name="android:textColorSecondary">
|
||||||
<item name="android:textColorTertiaryInverse">@color/m3_dynamic_default_color_secondary_text</item>
|
@color/m3_dynamic_dark_default_color_secondary_text
|
||||||
<item name="android:textColorPrimaryDisableOnly">@color/m3_dynamic_dark_primary_text_disable_only</item>
|
</item>
|
||||||
<item name="android:textColorPrimaryInverseDisableOnly">@color/m3_dynamic_primary_text_disable_only</item>
|
<item name="android:textColorSecondaryInverse">
|
||||||
|
@color/m3_dynamic_default_color_secondary_text
|
||||||
|
</item>
|
||||||
|
<item name="android:textColorTertiary">@color/m3_dynamic_dark_default_color_secondary_text
|
||||||
|
</item>
|
||||||
|
<item name="android:textColorTertiaryInverse">
|
||||||
|
@color/m3_dynamic_default_color_secondary_text
|
||||||
|
</item>
|
||||||
|
<item name="android:textColorPrimaryDisableOnly">
|
||||||
|
@color/m3_dynamic_dark_primary_text_disable_only
|
||||||
|
</item>
|
||||||
|
<item name="android:textColorPrimaryInverseDisableOnly">
|
||||||
|
@color/m3_dynamic_primary_text_disable_only
|
||||||
|
</item>
|
||||||
<item name="android:textColorHint">@color/m3_dynamic_dark_hint_foreground</item>
|
<item name="android:textColorHint">@color/m3_dynamic_dark_hint_foreground</item>
|
||||||
<item name="android:textColorHintInverse">@color/m3_dynamic_hint_foreground</item>
|
<item name="android:textColorHintInverse">@color/m3_dynamic_hint_foreground</item>
|
||||||
<item name="android:textColorHighlight">@color/m3_dynamic_dark_highlighted_text</item>
|
<item name="android:textColorHighlight">@color/m3_dynamic_dark_highlighted_text</item>
|
||||||
<item name="android:textColorHighlightInverse">@color/m3_dynamic_highlighted_text</item>
|
<item name="android:textColorHighlightInverse">@color/m3_dynamic_highlighted_text</item>
|
||||||
<item name="android:textColorAlertDialogListItem">@color/m3_dynamic_dark_default_color_primary_text</item>
|
<item name="android:textColorAlertDialogListItem">
|
||||||
|
@color/m3_dynamic_dark_default_color_primary_text
|
||||||
|
</item>
|
||||||
<!-- Fixes -->
|
<!-- Fixes -->
|
||||||
<item name="bottomNavigationStyle">@style/Widget.Kotatsu.BottomNavigationView</item>
|
<item name="bottomNavigationStyle">@style/Widget.Kotatsu.BottomNavigationView</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Kotatsu.AppWidgetContainer" parent="@android:style/Theme.DeviceDefault.DayNight">
|
<style name="Theme.Kotatsu.AppWidgetContainer" parent="@android:style/Theme.DeviceDefault.DayNight">
|
||||||
<item name="android:colorBackground">@color/m3_sys_color_dynamic_dark_secondary_container</item>
|
<item name="android:colorBackground">@color/m3_ref_palette_dynamic_secondary20</item>
|
||||||
<item name="android:panelColorBackground">@color/m3_sys_color_dynamic_dark_inverse_primary</item>
|
<item name="android:panelColorBackground">@color/m3_ref_palette_dynamic_secondary40</item>
|
||||||
|
<item name="colorTertiary">@color/m3_ref_palette_dynamic_secondary40</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,33 +1,39 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<plurals name="new_chapters">
|
<plurals name="new_chapters">
|
||||||
<item quantity="one">%1$d nowy rozdział</item>
|
<item quantity="one">%1$d nowy rozdział</item>
|
||||||
<item quantity="few">%1$d nowe rozdziały</item>
|
<item quantity="few">%1$d nowe rozdziały</item>
|
||||||
<item quantity="many">%1$d nowych rozdziałów</item>
|
<item quantity="many">%1$d nowych rozdziałów</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="minutes_ago">
|
<plurals name="minutes_ago">
|
||||||
<item quantity="one">%1$d minutę temu</item>
|
<item quantity="one">%1$d minutę temu</item>
|
||||||
<item quantity="few">%1$d minuty temu</item>
|
<item quantity="few">%1$d minuty temu</item>
|
||||||
<item quantity="many">%1$d minut temu</item>
|
<item quantity="many">%1$d minut temu</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="hours_ago">
|
<plurals name="hours_ago">
|
||||||
<item quantity="one">%1$d godzinę temu</item>
|
<item quantity="one">%1$d godzinę temu</item>
|
||||||
<item quantity="few">%1$d godziny temu</item>
|
<item quantity="few">%1$d godziny temu</item>
|
||||||
<item quantity="many">%1$d godzin temu</item>
|
<item quantity="many">%1$d godzin temu</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="days_ago">
|
<plurals name="days_ago">
|
||||||
<item quantity="one">%1$d dzień temu</item>
|
<item quantity="one">%1$d dzień temu</item>
|
||||||
<item quantity="few">%1$d dni temu</item>
|
<item quantity="few">%1$d dni temu</item>
|
||||||
<item quantity="many">%1$d dni temu</item>
|
<item quantity="many">%1$d dni temu</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="items">
|
<plurals name="items">
|
||||||
<item quantity="one">%1$d przedmiot</item>
|
<item quantity="one">%1$d przedmiot</item>
|
||||||
<item quantity="few">%1$d przedmioty</item>
|
<item quantity="few">%1$d przedmioty</item>
|
||||||
<item quantity="many">%1$d przedmiotów</item>
|
<item quantity="many">%1$d przedmiotów</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="chapters">
|
<plurals name="chapters">
|
||||||
<item quantity="one">%1$d rozdział</item>
|
<item quantity="one">%1$d rozdział</item>
|
||||||
<item quantity="few">%1$d rozdziały</item>
|
<item quantity="few">%1$d rozdziały</item>
|
||||||
<item quantity="many">%1$d rozdziałów</item>
|
<item quantity="many">%1$d rozdziałów</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
</resources>
|
<plurals name="months_ago">
|
||||||
|
<item quantity="one">%1$d miesiąc temu</item>
|
||||||
|
<item quantity="few">%1$d miesiące temu</item>
|
||||||
|
<item quantity="many">%1$d miesięcy temu</item>
|
||||||
|
<item quantity="other">%1$d miesięcy temu</item>
|
||||||
|
</plurals>
|
||||||
|
</resources>
|
||||||
@@ -443,4 +443,17 @@
|
|||||||
<string name="pick_custom_directory">Wybierz katalog niestandardowy</string>
|
<string name="pick_custom_directory">Wybierz katalog niestandardowy</string>
|
||||||
<string name="no_access_to_file">Nie masz dostępu do tego pliku lub katalogu</string>
|
<string name="no_access_to_file">Nie masz dostępu do tego pliku lub katalogu</string>
|
||||||
<string name="local_manga_directories">Lokalne katalogi mangi</string>
|
<string name="local_manga_directories">Lokalne katalogi mangi</string>
|
||||||
|
<string name="advanced">Zaawansowane</string>
|
||||||
|
<string name="this_month">Ten miesiąc</string>
|
||||||
|
<string name="voice_search">Wyszukiwanie głosowe</string>
|
||||||
|
<string name="color_light">Jasny</string>
|
||||||
|
<string name="color_dark">Ciemny</string>
|
||||||
|
<string name="color_white">Biały</string>
|
||||||
|
<string name="color_black">Czarny</string>
|
||||||
|
<string name="background">Tło</string>
|
||||||
|
<string name="progress">Postęp</string>
|
||||||
|
<string name="order_added">Dodano</string>
|
||||||
|
<string name="description">Opis</string>
|
||||||
|
<string name="languages">Języki</string>
|
||||||
|
<string name="unknown">Nieznane</string>
|
||||||
</resources>
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user