Merge branch 'devel' into feature/mal
This commit is contained in:
@@ -16,7 +16,7 @@ Download APK directly from GitHub:
|
|||||||
|
|
||||||
### Main Features
|
### Main Features
|
||||||
|
|
||||||
* Online manga catalogues
|
* Online [manga catalogues](https://github.com/KotatsuApp/kotatsu-parsers)
|
||||||
* Search manga by name and genres
|
* Search manga by name and genres
|
||||||
* Reading history and bookmarks
|
* Reading history and bookmarks
|
||||||
* Favourites organized by user-defined categories
|
* Favourites organized by user-defined categories
|
||||||
@@ -24,7 +24,7 @@ Download APK directly from GitHub:
|
|||||||
* Tablet-optimized Material You UI
|
* Tablet-optimized Material You UI
|
||||||
* Standard and Webtoon-optimized reader
|
* Standard and Webtoon-optimized reader
|
||||||
* Notifications about new chapters with updates feed
|
* Notifications about new chapters with updates feed
|
||||||
* Shikimori integration (manga tracking)
|
* Integration with manga tracking services: Shikimori, AniList, MAL (coming soon)
|
||||||
* Password/fingerprint protect access to the app
|
* Password/fingerprint protect access to the app
|
||||||
* History and favourites [synchronization](https://github.com/KotatsuApp/kotatsu-syncserver) across devices
|
* History and favourites [synchronization](https://github.com/KotatsuApp/kotatsu-syncserver) across devices
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 513
|
versionCode 514
|
||||||
versionName '4.3.2'
|
versionName '4.3.3'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation('com.github.KotatsuApp:kotatsu-parsers:c28e2a72d5') {
|
implementation('com.github.KotatsuApp:kotatsu-parsers:05d705ac03') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
package org.koitharu.kotatsu.core.parser
|
package org.koitharu.kotatsu.core.parser
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaParser
|
import org.koitharu.kotatsu.parsers.MangaParser
|
||||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
import org.koitharu.kotatsu.parsers.model.*
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
|
import java.util.EnumSet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This parser is just for parser development, it should not be used in releases
|
* This parser is just for parser development, it should not be used in releases
|
||||||
*/
|
*/
|
||||||
class DummyParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.DUMMY) {
|
class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.DUMMY) {
|
||||||
|
|
||||||
override val configKeyDomain: ConfigKey.Domain
|
override val configKeyDomain: ConfigKey.Domain
|
||||||
get() = ConfigKey.Domain("", null)
|
get() = ConfigKey.Domain("", null)
|
||||||
@@ -37,4 +42,4 @@ class DummyParser(override val context: MangaLoaderContext) : MangaParser(MangaS
|
|||||||
override suspend fun getTags(): Set<MangaTag> {
|
override suspend fun getTags(): Set<MangaTag> {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import org.koitharu.kotatsu.databinding.ItemBookmarkBinding
|
|||||||
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
||||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.utils.ext.referer
|
|
||||||
|
|
||||||
fun bookmarkListAD(
|
fun bookmarkListAD(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
@@ -26,8 +25,7 @@ fun bookmarkListAD(
|
|||||||
binding.root.setOnLongClickListener(listener)
|
binding.root.setOnLongClickListener(listener)
|
||||||
|
|
||||||
bind {
|
bind {
|
||||||
binding.imageViewThumb.newImageRequest(item.imageUrl)?.run {
|
binding.imageViewThumb.newImageRequest(item.imageUrl, item.manga.source)?.run {
|
||||||
referer(item.manga.publicUrl)
|
|
||||||
placeholder(R.drawable.ic_placeholder)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
error(R.drawable.ic_error_placeholder)
|
error(R.drawable.ic_error_placeholder)
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup
|
|||||||
import org.koitharu.kotatsu.databinding.ItemBookmarksGroupBinding
|
import org.koitharu.kotatsu.databinding.ItemBookmarksGroupBinding
|
||||||
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
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.clearItemDecorations
|
||||||
|
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
||||||
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
|
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||||
|
|
||||||
fun bookmarksGroupAD(
|
fun bookmarksGroupAD(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
@@ -45,8 +48,7 @@ fun bookmarksGroupAD(
|
|||||||
binding.recyclerView.addItemDecoration(spacingDecoration)
|
binding.recyclerView.addItemDecoration(spacingDecoration)
|
||||||
selectionController.attachToRecyclerView(item.manga, binding.recyclerView)
|
selectionController.attachToRecyclerView(item.manga, binding.recyclerView)
|
||||||
}
|
}
|
||||||
binding.imageViewCover.newImageRequest(item.manga.coverUrl)?.run {
|
binding.imageViewCover.newImageRequest(item.manga.coverUrl, item.manga.source)?.run {
|
||||||
referer(item.manga.publicUrl)
|
|
||||||
placeholder(R.drawable.ic_placeholder)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
error(R.drawable.ic_error_placeholder)
|
error(R.drawable.ic_error_placeholder)
|
||||||
|
|||||||
@@ -8,16 +8,14 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.core.network.UserAgentInterceptor
|
import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
|
||||||
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
|
class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
|
||||||
@@ -31,7 +29,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
|
|||||||
}
|
}
|
||||||
with(binding.webView.settings) {
|
with(binding.webView.settings) {
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
userAgentString = UserAgentInterceptor.userAgentChrome
|
userAgentString = CommonHeadersInterceptor.userAgentChrome
|
||||||
}
|
}
|
||||||
binding.webView.webViewClient = BrowserClient(this)
|
binding.webView.webViewClient = BrowserClient(this)
|
||||||
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
|
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
|
||||||
@@ -72,6 +70,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
|
|||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_browser -> {
|
R.id.action_browser -> {
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
intent.data = Uri.parse(binding.webView.url)
|
intent.data = Uri.parse(binding.webView.url)
|
||||||
@@ -81,6 +80,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import androidx.core.view.isInvisible
|
|||||||
import androidx.fragment.app.setFragmentResult
|
import androidx.fragment.app.setFragmentResult
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import okhttp3.Headers
|
||||||
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
|
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
|
||||||
import org.koitharu.kotatsu.core.network.UserAgentInterceptor
|
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||||
|
import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
|
||||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||||
import org.koitharu.kotatsu.databinding.FragmentCloudflareBinding
|
import org.koitharu.kotatsu.databinding.FragmentCloudflareBinding
|
||||||
import org.koitharu.kotatsu.utils.ext.stringArgument
|
import org.koitharu.kotatsu.utils.ext.stringArgument
|
||||||
@@ -42,7 +44,7 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
|||||||
cacheMode = WebSettings.LOAD_DEFAULT
|
cacheMode = WebSettings.LOAD_DEFAULT
|
||||||
domStorageEnabled = true
|
domStorageEnabled = true
|
||||||
databaseEnabled = true
|
databaseEnabled = true
|
||||||
userAgentString = UserAgentInterceptor.userAgentChrome
|
userAgentString = arguments?.getString(ARG_UA) ?: CommonHeadersInterceptor.userAgentChrome
|
||||||
}
|
}
|
||||||
binding.webView.webViewClient = CloudFlareClient(cookieJar, this, url.orEmpty())
|
binding.webView.webViewClient = CloudFlareClient(cookieJar, this, url.orEmpty())
|
||||||
CookieManager.getInstance().setAcceptThirdPartyCookies(binding.webView, true)
|
CookieManager.getInstance().setAcceptThirdPartyCookies(binding.webView, true)
|
||||||
@@ -92,9 +94,13 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
|||||||
const val TAG = "CloudFlareDialog"
|
const val TAG = "CloudFlareDialog"
|
||||||
const val EXTRA_RESULT = "result"
|
const val EXTRA_RESULT = "result"
|
||||||
private const val ARG_URL = "url"
|
private const val ARG_URL = "url"
|
||||||
|
private const val ARG_UA = "ua"
|
||||||
|
|
||||||
fun newInstance(url: String) = CloudFlareDialog().withArgs(1) {
|
fun newInstance(url: String, headers: Headers?) = CloudFlareDialog().withArgs(2) {
|
||||||
putString(ARG_URL, url)
|
putString(ARG_URL, url)
|
||||||
|
headers?.get(CommonHeaders.USER_AGENT)?.let {
|
||||||
|
putString(ARG_UA, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ interface AppModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
fun provideOkHttpClient(
|
fun provideOkHttpClient(
|
||||||
localStorageManager: LocalStorageManager,
|
localStorageManager: LocalStorageManager,
|
||||||
|
commonHeadersInterceptor: CommonHeadersInterceptor,
|
||||||
cookieJar: CookieJar,
|
cookieJar: CookieJar,
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
): OkHttpClient {
|
): OkHttpClient {
|
||||||
@@ -97,7 +98,7 @@ interface AppModule {
|
|||||||
dns(DoHManager(cache, settings))
|
dns(DoHManager(cache, settings))
|
||||||
cache(cache)
|
cache(cache)
|
||||||
addInterceptor(GZipInterceptor())
|
addInterceptor(GZipInterceptor())
|
||||||
addInterceptor(UserAgentInterceptor())
|
addInterceptor(commonHeadersInterceptor)
|
||||||
addInterceptor(CloudFlareInterceptor())
|
addInterceptor(CloudFlareInterceptor())
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
addInterceptor(CurlLoggingInterceptor())
|
addInterceptor(CurlLoggingInterceptor())
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package org.koitharu.kotatsu.core.exceptions
|
package org.koitharu.kotatsu.core.exceptions
|
||||||
|
|
||||||
|
import okhttp3.Headers
|
||||||
import okio.IOException
|
import okio.IOException
|
||||||
|
|
||||||
class CloudFlareProtectedException(
|
class CloudFlareProtectedException(
|
||||||
val url: String
|
val url: String,
|
||||||
) : IOException("Protected by CloudFlare")
|
val headers: Headers,
|
||||||
|
) : IOException("Protected by CloudFlare")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.collection.ArrayMap
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import okhttp3.Headers
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||||
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
|
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
|
||||||
@@ -43,7 +44,7 @@ class ExceptionResolver private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun resolve(e: Throwable): Boolean = when (e) {
|
suspend fun resolve(e: Throwable): Boolean = when (e) {
|
||||||
is CloudFlareProtectedException -> resolveCF(e.url)
|
is CloudFlareProtectedException -> resolveCF(e.url, e.headers)
|
||||||
is AuthRequiredException -> resolveAuthException(e.source)
|
is AuthRequiredException -> resolveAuthException(e.source)
|
||||||
is NotFoundException -> {
|
is NotFoundException -> {
|
||||||
openInBrowser(e.url)
|
openInBrowser(e.url)
|
||||||
@@ -53,8 +54,8 @@ class ExceptionResolver private constructor(
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun resolveCF(url: String): Boolean {
|
private suspend fun resolveCF(url: String, headers: Headers): Boolean {
|
||||||
val dialog = CloudFlareDialog.newInstance(url)
|
val dialog = CloudFlareDialog.newInstance(url, headers)
|
||||||
val fm = getFragmentManager()
|
val fm = getFragmentManager()
|
||||||
return suspendCancellableCoroutine { cont ->
|
return suspendCancellableCoroutine { cont ->
|
||||||
fm.clearFragmentResult(CloudFlareDialog.TAG)
|
fm.clearFragmentResult(CloudFlareDialog.TAG)
|
||||||
|
|||||||
@@ -13,13 +13,17 @@ private const val SERVER_CLOUDFLARE = "cloudflare"
|
|||||||
class CloudFlareInterceptor : Interceptor {
|
class CloudFlareInterceptor : Interceptor {
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val response = chain.proceed(chain.request())
|
val request = chain.request()
|
||||||
|
val response = chain.proceed(request)
|
||||||
if (response.code == HTTP_FORBIDDEN || response.code == HTTP_UNAVAILABLE) {
|
if (response.code == HTTP_FORBIDDEN || response.code == HTTP_UNAVAILABLE) {
|
||||||
if (response.header(HEADER_SERVER)?.startsWith(SERVER_CLOUDFLARE) == true) {
|
if (response.header(HEADER_SERVER)?.startsWith(SERVER_CLOUDFLARE) == true) {
|
||||||
response.closeQuietly()
|
response.closeQuietly()
|
||||||
throw CloudFlareProtectedException(response.request.url.toString())
|
throw CloudFlareProtectedException(
|
||||||
|
url = response.request.url.toString(),
|
||||||
|
headers = request.headers,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import dagger.Lazy
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.util.mergeWith
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class CommonHeadersInterceptor @Inject constructor(
|
||||||
|
private val mangaRepositoryFactoryLazy: Lazy<MangaRepository.Factory>,
|
||||||
|
) : Interceptor {
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
val source = request.tag(MangaSource::class.java)
|
||||||
|
val repository = if (source != null) {
|
||||||
|
mangaRepositoryFactoryLazy.get().create(source) as? RemoteMangaRepository
|
||||||
|
} else {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.w("Http", "Request without source tag: ${request.url}")
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val headersBuilder = request.headers.newBuilder()
|
||||||
|
repository?.headers?.let {
|
||||||
|
headersBuilder.mergeWith(it, replaceExisting = false)
|
||||||
|
}
|
||||||
|
if (headersBuilder[CommonHeaders.USER_AGENT] == null) {
|
||||||
|
headersBuilder[CommonHeaders.USER_AGENT] = userAgentFallback
|
||||||
|
}
|
||||||
|
if (headersBuilder[CommonHeaders.REFERER] == null && repository != null) {
|
||||||
|
headersBuilder[CommonHeaders.REFERER] = "https://${repository.domain}/"
|
||||||
|
}
|
||||||
|
val newRequest = request.newBuilder().headers(headersBuilder.build()).build()
|
||||||
|
return repository?.intercept(ProxyChain(chain, newRequest)) ?: chain.proceed(newRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProxyChain(
|
||||||
|
private val delegate: Interceptor.Chain,
|
||||||
|
private val request: Request,
|
||||||
|
) : Interceptor.Chain by delegate {
|
||||||
|
|
||||||
|
override fun request(): Request = request
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val userAgentFallback
|
||||||
|
get() = "Kotatsu/%s (Android %s; %s; %s %s; %s)".format(
|
||||||
|
BuildConfig.VERSION_NAME,
|
||||||
|
Build.VERSION.RELEASE,
|
||||||
|
Build.MODEL,
|
||||||
|
Build.BRAND,
|
||||||
|
Build.DEVICE,
|
||||||
|
Locale.getDefault().language,
|
||||||
|
)
|
||||||
|
|
||||||
|
val userAgentChrome
|
||||||
|
get() = (
|
||||||
|
"Mozilla/5.0 (Linux; Android %s; %s) AppleWebKit/537.36 (KHTML, like Gecko) " +
|
||||||
|
"Chrome/100.0.4896.127 Mobile Safari/537.36"
|
||||||
|
).format(
|
||||||
|
Build.VERSION.RELEASE,
|
||||||
|
Build.MODEL,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.network
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import java.util.*
|
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
|
||||||
|
|
||||||
class UserAgentInterceptor : Interceptor {
|
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
|
||||||
val request = chain.request()
|
|
||||||
return chain.proceed(
|
|
||||||
if (request.header(CommonHeaders.USER_AGENT) == null) {
|
|
||||||
request.newBuilder()
|
|
||||||
.addHeader(CommonHeaders.USER_AGENT, userAgentChrome)
|
|
||||||
.build()
|
|
||||||
} else request
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
val userAgent
|
|
||||||
get() = "Kotatsu/%s (Android %s; %s; %s %s; %s)".format(
|
|
||||||
BuildConfig.VERSION_NAME,
|
|
||||||
Build.VERSION.RELEASE,
|
|
||||||
Build.MODEL,
|
|
||||||
Build.BRAND,
|
|
||||||
Build.DEVICE,
|
|
||||||
Locale.getDefault().language
|
|
||||||
) // TODO Decide what to do with this afterwards
|
|
||||||
|
|
||||||
val userAgentChrome
|
|
||||||
get() = (
|
|
||||||
"Mozilla/5.0 (Linux; Android %s; %s) AppleWebKit/537.36 (KHTML, like Gecko) " +
|
|
||||||
"Chrome/100.0.4896.127 Mobile Safari/537.36"
|
|
||||||
).format(
|
|
||||||
Build.VERSION.RELEASE,
|
|
||||||
Build.MODEL,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -118,6 +118,7 @@ class ShortcutsUpdater @Inject constructor(
|
|||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
.data(manga.coverUrl)
|
.data(manga.coverUrl)
|
||||||
.size(iconSize.width, iconSize.height)
|
.size(iconSize.width, iconSize.height)
|
||||||
|
.tag(manga.source)
|
||||||
.precision(Precision.EXACT)
|
.precision(Precision.EXACT)
|
||||||
.scale(Scale.FILL)
|
.scale(Scale.FILL)
|
||||||
.build(),
|
.build(),
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.MainCoroutineDispatcher
|
import kotlinx.coroutines.MainCoroutineDispatcher
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
import org.koitharu.kotatsu.core.cache.ContentCache
|
import org.koitharu.kotatsu.core.cache.ContentCache
|
||||||
import org.koitharu.kotatsu.core.cache.SafeDeferred
|
import org.koitharu.kotatsu.core.cache.SafeDeferred
|
||||||
import org.koitharu.kotatsu.core.prefs.SourceSettings
|
import org.koitharu.kotatsu.core.prefs.SourceSettings
|
||||||
@@ -19,13 +22,14 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
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
|
||||||
|
import org.koitharu.kotatsu.parsers.util.domain
|
||||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||||
|
|
||||||
class RemoteMangaRepository(
|
class RemoteMangaRepository(
|
||||||
private val parser: MangaParser,
|
private val parser: MangaParser,
|
||||||
private val cache: ContentCache,
|
private val cache: ContentCache,
|
||||||
) : MangaRepository {
|
) : MangaRepository, Interceptor {
|
||||||
|
|
||||||
override val source: MangaSource
|
override val source: MangaSource
|
||||||
get() = parser.source
|
get() = parser.source
|
||||||
@@ -39,6 +43,20 @@ class RemoteMangaRepository(
|
|||||||
getConfig().defaultSortOrder = value
|
getConfig().defaultSortOrder = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val domain: String
|
||||||
|
get() = parser.domain
|
||||||
|
|
||||||
|
val headers: Headers?
|
||||||
|
get() = parser.headers
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
return if (parser is Interceptor) {
|
||||||
|
parser.intercept(chain)
|
||||||
|
} else {
|
||||||
|
chain.proceed(chain.request())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getList(offset: Int, query: String): List<Manga> {
|
override suspend fun getList(offset: Int, query: String): List<Manga> {
|
||||||
return parser.getList(offset, query)
|
return parser.getList(offset, query)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import okhttp3.Response
|
|||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import okhttp3.internal.closeQuietly
|
import okhttp3.internal.closeQuietly
|
||||||
import org.koitharu.kotatsu.core.model.MangaSource
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
|
||||||
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.local.data.CacheDir
|
import org.koitharu.kotatsu.local.data.CacheDir
|
||||||
@@ -53,7 +52,7 @@ class FaviconFetcher(
|
|||||||
options.size.height.pxOrElse { FALLBACK_SIZE },
|
options.size.height.pxOrElse { FALLBACK_SIZE },
|
||||||
)
|
)
|
||||||
val icon = checkNotNull(favicons.find(sizePx)) { "No favicons found" }
|
val icon = checkNotNull(favicons.find(sizePx)) { "No favicons found" }
|
||||||
val response = loadIcon(icon.url, favicons.referer)
|
val response = loadIcon(icon.url, mangaSource)
|
||||||
val responseBody = response.requireBody()
|
val responseBody = response.requireBody()
|
||||||
val source = writeToDiskCache(responseBody)?.toImageSource() ?: responseBody.toImageSource()
|
val source = writeToDiskCache(responseBody)?.toImageSource() ?: responseBody.toImageSource()
|
||||||
return SourceResult(
|
return SourceResult(
|
||||||
@@ -63,11 +62,11 @@ class FaviconFetcher(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadIcon(url: String, referer: String): Response {
|
private suspend fun loadIcon(url: String, source: MangaSource): Response {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.get()
|
.get()
|
||||||
.header(CommonHeaders.REFERER, referer)
|
.tag(MangaSource::class.java, source)
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
options.tags.asMap().forEach { request.tag(it.key as Class<Any>, it.value) }
|
options.tags.asMap().forEach { request.tag(it.key as Class<Any>, it.value) }
|
||||||
val response = okHttpClient.newCall(request.build()).await()
|
val response = okHttpClient.newCall(request.build()).await()
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import org.koitharu.kotatsu.utils.ext.observe
|
|||||||
import org.koitharu.kotatsu.utils.ext.putEnumValue
|
import org.koitharu.kotatsu.utils.ext.putEnumValue
|
||||||
import org.koitharu.kotatsu.utils.ext.toUriOrNull
|
import org.koitharu.kotatsu.utils.ext.toUriOrNull
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.DateFormat
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -263,12 +261,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
return policy.isNetworkAllowed(connectivityManager)
|
return policy.isNetworkAllowed(connectivityManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDateFormat(format: String = prefs.getString(KEY_DATE_FORMAT, "").orEmpty()): DateFormat =
|
|
||||||
when (format) {
|
|
||||||
"" -> DateFormat.getDateInstance(DateFormat.SHORT)
|
|
||||||
else -> SimpleDateFormat(format, Locale.getDefault())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSuggestionsTagsBlacklistRegex(): Regex? {
|
fun getSuggestionsTagsBlacklistRegex(): Regex? {
|
||||||
val string = prefs.getString(KEY_SUGGESTIONS_EXCLUDE_TAGS, null)?.trimEnd(' ', ',')
|
val string = prefs.getString(KEY_SUGGESTIONS_EXCLUDE_TAGS, null)?.trimEnd(' ', ',')
|
||||||
if (string.isNullOrEmpty()) {
|
if (string.isNullOrEmpty()) {
|
||||||
@@ -317,7 +309,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_THEME = "theme"
|
const val KEY_THEME = "theme"
|
||||||
const val KEY_COLOR_THEME = "color_theme"
|
const val KEY_COLOR_THEME = "color_theme"
|
||||||
const val KEY_THEME_AMOLED = "amoled_theme"
|
const val KEY_THEME_AMOLED = "amoled_theme"
|
||||||
const val KEY_DATE_FORMAT = "date_format"
|
|
||||||
const val KEY_SOURCES_ORDER = "sources_order_2"
|
const val KEY_SOURCES_ORDER = "sources_order_2"
|
||||||
const val KEY_SOURCES_HIDDEN = "sources_hidden"
|
const val KEY_SOURCES_HIDDEN = "sources_hidden"
|
||||||
const val KEY_TRAFFIC_WARNING = "traffic_warning"
|
const val KEY_TRAFFIC_WARNING = "traffic_warning"
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ enum class ColorScheme(
|
|||||||
|
|
||||||
DEFAULT(R.style.Theme_Kotatsu, R.string.system_default),
|
DEFAULT(R.style.Theme_Kotatsu, R.string.system_default),
|
||||||
MONET(R.style.Theme_Kotatsu_Monet, R.string.theme_name_dynamic),
|
MONET(R.style.Theme_Kotatsu_Monet, R.string.theme_name_dynamic),
|
||||||
MINT(R.style.Theme_Kotatsu_Mint, R.string.theme_name_mint),
|
MIKU(R.style.Theme_Kotatsu_Miku, R.string.theme_name_miku),
|
||||||
OCTOBER(R.style.Theme_Kotatsu_October, R.string.theme_name_october),
|
RENA(R.style.Theme_Kotatsu_Asuka, R.string.theme_name_asuka),
|
||||||
|
FROG(R.style.Theme_Kotatsu_Mion, R.string.theme_name_mion),
|
||||||
|
BLUEBERRY(R.style.Theme_Kotatsu_Rikka, R.string.theme_name_rikka),
|
||||||
|
NAME2(R.style.Theme_Kotatsu_Sakura, R.string.theme_name_sakura),
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import org.koitharu.kotatsu.utils.ext.drawableTop
|
|||||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.utils.ext.ifNullOrEmpty
|
import org.koitharu.kotatsu.utils.ext.ifNullOrEmpty
|
||||||
import org.koitharu.kotatsu.utils.ext.measureHeight
|
import org.koitharu.kotatsu.utils.ext.measureHeight
|
||||||
import org.koitharu.kotatsu.utils.ext.referer
|
|
||||||
import org.koitharu.kotatsu.utils.ext.resolveDp
|
import org.koitharu.kotatsu.utils.ext.resolveDp
|
||||||
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
|
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
|
||||||
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||||
@@ -254,7 +253,11 @@ class DetailsFragment :
|
|||||||
|
|
||||||
R.id.imageView_cover -> {
|
R.id.imageView_cover -> {
|
||||||
startActivity(
|
startActivity(
|
||||||
ImageActivity.newIntent(v.context, manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }),
|
ImageActivity.newIntent(
|
||||||
|
v.context,
|
||||||
|
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
|
||||||
|
manga.source,
|
||||||
|
),
|
||||||
scaleUpActivityOptionsOf(v).toBundle(),
|
scaleUpActivityOptionsOf(v).toBundle(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -337,8 +340,8 @@ class DetailsFragment :
|
|||||||
.target(binding.imageViewCover)
|
.target(binding.imageViewCover)
|
||||||
.size(CoverSizeResolver(binding.imageViewCover))
|
.size(CoverSizeResolver(binding.imageViewCover))
|
||||||
.data(imageUrl)
|
.data(imageUrl)
|
||||||
|
.tag(manga.source)
|
||||||
.crossfade(context)
|
.crossfade(context)
|
||||||
.referer(manga.publicUrl)
|
|
||||||
.lifecycle(viewLifecycleOwner)
|
.lifecycle(viewLifecycleOwner)
|
||||||
.placeholderMemoryCacheKey(manga.coverUrl)
|
.placeholderMemoryCacheKey(manga.coverUrl)
|
||||||
val previousDrawable = lastResult?.drawable
|
val previousDrawable = lastResult?.drawable
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ class DetailsViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
private val delegate = MangaDetailsDelegate(
|
private val delegate = MangaDetailsDelegate(
|
||||||
intent = intent,
|
intent = intent,
|
||||||
settings = settings,
|
|
||||||
mangaDataRepository = mangaDataRepository,
|
mangaDataRepository = mangaDataRepository,
|
||||||
historyRepository = historyRepository,
|
historyRepository = historyRepository,
|
||||||
localMangaRepository = localMangaRepository,
|
localMangaRepository = localMangaRepository,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import org.koitharu.kotatsu.base.domain.MangaIntent
|
|||||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||||
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||||
import org.koitharu.kotatsu.details.ui.model.toListItem
|
import org.koitharu.kotatsu.details.ui.model.toListItem
|
||||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||||
@@ -21,7 +20,6 @@ import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
|||||||
|
|
||||||
class MangaDetailsDelegate(
|
class MangaDetailsDelegate(
|
||||||
private val intent: MangaIntent,
|
private val intent: MangaIntent,
|
||||||
private val settings: AppSettings,
|
|
||||||
private val mangaDataRepository: MangaDataRepository,
|
private val mangaDataRepository: MangaDataRepository,
|
||||||
private val historyRepository: HistoryRepository,
|
private val historyRepository: HistoryRepository,
|
||||||
private val localMangaRepository: LocalMangaRepository,
|
private val localMangaRepository: LocalMangaRepository,
|
||||||
@@ -82,7 +80,6 @@ class MangaDetailsDelegate(
|
|||||||
branch: String?,
|
branch: String?,
|
||||||
): List<ChapterListItem> {
|
): List<ChapterListItem> {
|
||||||
val result = ArrayList<ChapterListItem>(chapters.size)
|
val result = ArrayList<ChapterListItem>(chapters.size)
|
||||||
val dateFormat = settings.getDateFormat()
|
|
||||||
val currentIndex = chapters.indexOfFirst { it.id == currentId }
|
val currentIndex = chapters.indexOfFirst { it.id == currentId }
|
||||||
val firstNewIndex = chapters.size - newCount
|
val firstNewIndex = chapters.size - newCount
|
||||||
val downloadedIds = downloadedChapters?.mapTo(HashSet(downloadedChapters.size)) { it.id }
|
val downloadedIds = downloadedChapters?.mapTo(HashSet(downloadedChapters.size)) { it.id }
|
||||||
@@ -97,7 +94,6 @@ class MangaDetailsDelegate(
|
|||||||
isNew = i >= firstNewIndex,
|
isNew = i >= firstNewIndex,
|
||||||
isMissing = false,
|
isMissing = false,
|
||||||
isDownloaded = downloadedIds?.contains(chapter.id) == true,
|
isDownloaded = downloadedIds?.contains(chapter.id) == true,
|
||||||
dateFormat = dateFormat,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (result.size < chapters.size / 2) {
|
if (result.size < chapters.size / 2) {
|
||||||
@@ -117,7 +113,6 @@ class MangaDetailsDelegate(
|
|||||||
val result = ArrayList<ChapterListItem>(sourceChapters.size)
|
val result = ArrayList<ChapterListItem>(sourceChapters.size)
|
||||||
val currentIndex = sourceChapters.indexOfFirst { it.id == currentId }
|
val currentIndex = sourceChapters.indexOfFirst { it.id == currentId }
|
||||||
val firstNewIndex = sourceChapters.size - newCount
|
val firstNewIndex = sourceChapters.size - newCount
|
||||||
val dateFormat = settings.getDateFormat()
|
|
||||||
for (i in sourceChapters.indices) {
|
for (i in sourceChapters.indices) {
|
||||||
val chapter = sourceChapters[i]
|
val chapter = sourceChapters[i]
|
||||||
val localChapter = chaptersMap.remove(chapter.id)
|
val localChapter = chaptersMap.remove(chapter.id)
|
||||||
@@ -130,14 +125,12 @@ class MangaDetailsDelegate(
|
|||||||
isNew = i >= firstNewIndex,
|
isNew = i >= firstNewIndex,
|
||||||
isMissing = false,
|
isMissing = false,
|
||||||
isDownloaded = false,
|
isDownloaded = false,
|
||||||
dateFormat = dateFormat,
|
|
||||||
) ?: chapter.toListItem(
|
) ?: chapter.toListItem(
|
||||||
isCurrent = i == currentIndex,
|
isCurrent = i == currentIndex,
|
||||||
isUnread = i > currentIndex,
|
isUnread = i > currentIndex,
|
||||||
isNew = i >= firstNewIndex,
|
isNew = i >= firstNewIndex,
|
||||||
isMissing = true,
|
isMissing = true,
|
||||||
isDownloaded = false,
|
isDownloaded = false,
|
||||||
dateFormat = dateFormat,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (chaptersMap.isNotEmpty()) { // some chapters on device but not online source
|
if (chaptersMap.isNotEmpty()) { // some chapters on device but not online source
|
||||||
@@ -150,7 +143,6 @@ class MangaDetailsDelegate(
|
|||||||
isNew = false,
|
isNew = false,
|
||||||
isMissing = false,
|
isMissing = false,
|
||||||
isDownloaded = false,
|
isDownloaded = false,
|
||||||
dateFormat = dateFormat,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
package org.koitharu.kotatsu.details.ui.model
|
package org.koitharu.kotatsu.details.ui.model
|
||||||
|
|
||||||
import java.text.DateFormat
|
import android.text.format.DateUtils
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
|
|
||||||
class ChapterListItem(
|
class ChapterListItem(
|
||||||
val chapter: MangaChapter,
|
val chapter: MangaChapter,
|
||||||
val flags: Int,
|
val flags: Int,
|
||||||
private val uploadDateMs: Long,
|
private val uploadDateMs: Long,
|
||||||
private val dateFormat: DateFormat,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var uploadDate: String? = null
|
var uploadDate: CharSequence? = null
|
||||||
private set
|
private set
|
||||||
get() {
|
get() {
|
||||||
if (field != null) return field
|
if (field != null) return field
|
||||||
if (uploadDateMs == 0L) return null
|
if (uploadDateMs == 0L) return null
|
||||||
field = dateFormat.format(uploadDateMs)
|
field = DateUtils.getRelativeTimeSpanString(
|
||||||
|
uploadDateMs,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
DateUtils.DAY_IN_MILLIS,
|
||||||
|
)
|
||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +47,6 @@ class ChapterListItem(
|
|||||||
if (chapter != other.chapter) return false
|
if (chapter != other.chapter) return false
|
||||||
if (flags != other.flags) return false
|
if (flags != other.flags) return false
|
||||||
if (uploadDateMs != other.uploadDateMs) return false
|
if (uploadDateMs != other.uploadDateMs) return false
|
||||||
if (dateFormat != other.dateFormat) return false
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -53,7 +55,6 @@ class ChapterListItem(
|
|||||||
var result = chapter.hashCode()
|
var result = chapter.hashCode()
|
||||||
result = 31 * result + flags
|
result = 31 * result + flags
|
||||||
result = 31 * result + uploadDateMs.hashCode()
|
result = 31 * result + uploadDateMs.hashCode()
|
||||||
result = 31 * result + dateFormat.hashCode()
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.koitharu.kotatsu.details.ui.model
|
package org.koitharu.kotatsu.details.ui.model
|
||||||
|
|
||||||
import java.text.DateFormat
|
|
||||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_CURRENT
|
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_CURRENT
|
||||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_DOWNLOADED
|
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_DOWNLOADED
|
||||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_MISSING
|
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_MISSING
|
||||||
@@ -14,7 +13,6 @@ fun MangaChapter.toListItem(
|
|||||||
isNew: Boolean,
|
isNew: Boolean,
|
||||||
isMissing: Boolean,
|
isMissing: Boolean,
|
||||||
isDownloaded: Boolean,
|
isDownloaded: Boolean,
|
||||||
dateFormat: DateFormat,
|
|
||||||
): ChapterListItem {
|
): ChapterListItem {
|
||||||
var flags = 0
|
var flags = 0
|
||||||
if (isCurrent) flags = flags or FLAG_CURRENT
|
if (isCurrent) flags = flags or FLAG_CURRENT
|
||||||
@@ -26,6 +24,5 @@ fun MangaChapter.toListItem(
|
|||||||
chapter = this,
|
chapter = this,
|
||||||
flags = flags,
|
flags = flags,
|
||||||
uploadDateMs = uploadDate,
|
uploadDateMs = uploadDate,
|
||||||
dateFormat = dateFormat,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ fun scrobblingInfoAD(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bind {
|
bind {
|
||||||
binding.imageViewCover.newImageRequest(item.coverUrl)?.run {
|
binding.imageViewCover.newImageRequest(item.coverUrl /* TODO */, null)?.run {
|
||||||
placeholder(R.drawable.ic_placeholder)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
error(R.drawable.ic_error_placeholder)
|
error(R.drawable.ic_error_placeholder)
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class ScrobblingInfoBottomSheet :
|
|||||||
R.id.imageView_cover -> {
|
R.id.imageView_cover -> {
|
||||||
val coverUrl = viewModel.scrobblingInfo.value?.getOrNull(scrobblerIndex)?.coverUrl ?: return
|
val coverUrl = viewModel.scrobblingInfo.value?.getOrNull(scrobblerIndex)?.coverUrl ?: return
|
||||||
val options = scaleUpActivityOptionsOf(v)
|
val options = scaleUpActivityOptionsOf(v)
|
||||||
startActivity(ImageActivity.newIntent(v.context, coverUrl), options.toBundle())
|
startActivity(ImageActivity.newIntent(v.context, coverUrl, null), options.toBundle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import org.koitharu.kotatsu.parsers.util.await
|
|||||||
import org.koitharu.kotatsu.utils.ext.copyToSuspending
|
import org.koitharu.kotatsu.utils.ext.copyToSuspending
|
||||||
import org.koitharu.kotatsu.utils.ext.deleteAwait
|
import org.koitharu.kotatsu.utils.ext.deleteAwait
|
||||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.utils.ext.referer
|
|
||||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.utils.progress.PausingProgressJob
|
import org.koitharu.kotatsu.utils.progress.PausingProgressJob
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -118,7 +117,7 @@ class DownloadManager @AssistedInject constructor(
|
|||||||
val data = if (manga.chapters.isNullOrEmpty()) repo.getDetails(manga) else manga
|
val data = if (manga.chapters.isNullOrEmpty()) repo.getDetails(manga) else manga
|
||||||
output = CbzMangaOutput.get(destination, data)
|
output = CbzMangaOutput.get(destination, data)
|
||||||
val coverUrl = data.largeCoverUrl ?: data.coverUrl
|
val coverUrl = data.largeCoverUrl ?: data.coverUrl
|
||||||
downloadFile(coverUrl, data.publicUrl, destination, tempFileName).let { file ->
|
downloadFile(coverUrl, data.publicUrl, destination, tempFileName, repo.source).let { file ->
|
||||||
output.addCover(file, MimeTypeMap.getFileExtensionFromUrl(coverUrl))
|
output.addCover(file, MimeTypeMap.getFileExtensionFromUrl(coverUrl))
|
||||||
}
|
}
|
||||||
val chapters = checkNotNull(
|
val chapters = checkNotNull(
|
||||||
@@ -139,7 +138,8 @@ class DownloadManager @AssistedInject constructor(
|
|||||||
for ((pageIndex, page) in pages.withIndex()) {
|
for ((pageIndex, page) in pages.withIndex()) {
|
||||||
runFailsafe(outState, pausingHandle) {
|
runFailsafe(outState, pausingHandle) {
|
||||||
val url = repo.getPageUrl(page)
|
val url = repo.getPageUrl(page)
|
||||||
val file = cache[url] ?: downloadFile(url, page.referer, destination, tempFileName)
|
val file = cache[url]
|
||||||
|
?: downloadFile(url, page.referer, destination, tempFileName, repo.source)
|
||||||
output.addPage(
|
output.addPage(
|
||||||
chapter = chapter,
|
chapter = chapter,
|
||||||
file = file,
|
file = file,
|
||||||
@@ -209,10 +209,17 @@ class DownloadManager @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun downloadFile(url: String, referer: String, destination: File, tempFileName: String): File {
|
private suspend fun downloadFile(
|
||||||
|
url: String,
|
||||||
|
referer: String,
|
||||||
|
destination: File,
|
||||||
|
tempFileName: String,
|
||||||
|
source: MangaSource,
|
||||||
|
): File {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header(CommonHeaders.REFERER, referer)
|
.header(CommonHeaders.REFERER, referer)
|
||||||
|
.tag(MangaSource::class.java, source)
|
||||||
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
|
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
|
||||||
.get()
|
.get()
|
||||||
.build()
|
.build()
|
||||||
@@ -242,7 +249,7 @@ class DownloadManager @AssistedInject constructor(
|
|||||||
imageLoader.execute(
|
imageLoader.execute(
|
||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
.data(manga.coverUrl)
|
.data(manga.coverUrl)
|
||||||
.referer(manga.publicUrl)
|
.tag(manga.source)
|
||||||
.size(coverWidth, coverHeight)
|
.size(coverWidth, coverHeight)
|
||||||
.scale(Scale.FILL)
|
.scale(Scale.FILL)
|
||||||
.build(),
|
.build(),
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import org.koitharu.kotatsu.databinding.ItemDownloadBinding
|
|||||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||||
import org.koitharu.kotatsu.parsers.util.format
|
import org.koitharu.kotatsu.parsers.util.format
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||||
|
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||||
|
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||||
|
|
||||||
fun downloadItemAD(
|
fun downloadItemAD(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
@@ -40,8 +43,7 @@ fun downloadItemAD(
|
|||||||
bind {
|
bind {
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = item.progressAsFlow().onFirst { state ->
|
job = item.progressAsFlow().onFirst { state ->
|
||||||
binding.imageViewCover.newImageRequest(state.manga.coverUrl)?.run {
|
binding.imageViewCover.newImageRequest(state.manga.coverUrl, state.manga.source)?.run {
|
||||||
referer(state.manga.publicUrl)
|
|
||||||
placeholder(state.cover)
|
placeholder(state.cover)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
error(R.drawable.ic_error_placeholder)
|
error(R.drawable.ic_error_placeholder)
|
||||||
@@ -60,6 +62,7 @@ fun downloadItemAD(
|
|||||||
binding.buttonCancel.isVisible = false
|
binding.buttonCancel.isVisible = false
|
||||||
binding.buttonResume.isVisible = false
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
is DownloadState.Done -> {
|
is DownloadState.Done -> {
|
||||||
binding.textViewStatus.setText(R.string.download_complete)
|
binding.textViewStatus.setText(R.string.download_complete)
|
||||||
binding.progressBar.isIndeterminate = false
|
binding.progressBar.isIndeterminate = false
|
||||||
@@ -69,6 +72,7 @@ fun downloadItemAD(
|
|||||||
binding.buttonCancel.isVisible = false
|
binding.buttonCancel.isVisible = false
|
||||||
binding.buttonResume.isVisible = false
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
is DownloadState.Error -> {
|
is DownloadState.Error -> {
|
||||||
binding.textViewStatus.setText(R.string.error_occurred)
|
binding.textViewStatus.setText(R.string.error_occurred)
|
||||||
binding.progressBar.isIndeterminate = false
|
binding.progressBar.isIndeterminate = false
|
||||||
@@ -79,6 +83,7 @@ fun downloadItemAD(
|
|||||||
binding.buttonCancel.isVisible = state.canRetry
|
binding.buttonCancel.isVisible = state.canRetry
|
||||||
binding.buttonResume.isVisible = state.canRetry
|
binding.buttonResume.isVisible = state.canRetry
|
||||||
}
|
}
|
||||||
|
|
||||||
is DownloadState.PostProcessing -> {
|
is DownloadState.PostProcessing -> {
|
||||||
binding.textViewStatus.setText(R.string.processing_)
|
binding.textViewStatus.setText(R.string.processing_)
|
||||||
binding.progressBar.isIndeterminate = true
|
binding.progressBar.isIndeterminate = true
|
||||||
@@ -88,6 +93,7 @@ fun downloadItemAD(
|
|||||||
binding.buttonCancel.isVisible = false
|
binding.buttonCancel.isVisible = false
|
||||||
binding.buttonResume.isVisible = false
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
is DownloadState.Preparing -> {
|
is DownloadState.Preparing -> {
|
||||||
binding.textViewStatus.setText(R.string.preparing_)
|
binding.textViewStatus.setText(R.string.preparing_)
|
||||||
binding.progressBar.isIndeterminate = true
|
binding.progressBar.isIndeterminate = true
|
||||||
@@ -97,6 +103,7 @@ fun downloadItemAD(
|
|||||||
binding.buttonCancel.isVisible = true
|
binding.buttonCancel.isVisible = true
|
||||||
binding.buttonResume.isVisible = false
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
is DownloadState.Progress -> {
|
is DownloadState.Progress -> {
|
||||||
binding.textViewStatus.setText(R.string.manga_downloading_)
|
binding.textViewStatus.setText(R.string.manga_downloading_)
|
||||||
binding.progressBar.isIndeterminate = false
|
binding.progressBar.isIndeterminate = false
|
||||||
@@ -109,6 +116,7 @@ fun downloadItemAD(
|
|||||||
binding.buttonCancel.isVisible = true
|
binding.buttonCancel.isVisible = true
|
||||||
binding.buttonResume.isVisible = false
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
is DownloadState.Queued -> {
|
is DownloadState.Queued -> {
|
||||||
binding.textViewStatus.setText(R.string.queued)
|
binding.textViewStatus.setText(R.string.queued)
|
||||||
binding.progressBar.isIndeterminate = false
|
binding.progressBar.isIndeterminate = false
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ fun exploreSourceListItemAD(
|
|||||||
bind {
|
bind {
|
||||||
binding.textViewTitle.text = item.source.title
|
binding.textViewTitle.text = item.source.title
|
||||||
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
|
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
|
||||||
binding.imageViewIcon.newImageRequest(item.source.faviconUri())?.run {
|
binding.imageViewIcon.newImageRequest(item.source.faviconUri(), item.source)?.run {
|
||||||
fallback(fallbackIcon)
|
fallback(fallbackIcon)
|
||||||
placeholder(fallbackIcon)
|
placeholder(fallbackIcon)
|
||||||
error(fallbackIcon)
|
error(fallbackIcon)
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import android.graphics.Color
|
|||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.*
|
import android.view.View.OnClickListener
|
||||||
|
import android.view.View.OnLongClickListener
|
||||||
|
import android.view.View.OnTouchListener
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.ImageViewCompat
|
import androidx.core.widget.ImageViewCompat
|
||||||
@@ -16,7 +18,11 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.databinding.ItemCategoryBinding
|
import org.koitharu.kotatsu.databinding.ItemCategoryBinding
|
||||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
|
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.animatorDurationScale
|
||||||
|
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
||||||
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
|
import org.koitharu.kotatsu.utils.ext.getThemeColor
|
||||||
|
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||||
|
|
||||||
fun categoryAD(
|
fun categoryAD(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ import coil.target.ViewTarget
|
|||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
|
||||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.databinding.ActivityImageBinding
|
import org.koitharu.kotatsu.databinding.ActivityImageBinding
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.utils.ext.indicator
|
import org.koitharu.kotatsu.utils.ext.indicator
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ImageActivity : BaseActivity<ActivityImageBinding>() {
|
class ImageActivity : BaseActivity<ActivityImageBinding>() {
|
||||||
@@ -56,6 +57,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>() {
|
|||||||
.data(url)
|
.data(url)
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.lifecycle(this)
|
.lifecycle(this)
|
||||||
|
.tag(intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource)
|
||||||
.target(SsivTarget(binding.ssiv))
|
.target(SsivTarget(binding.ssiv))
|
||||||
.indicator(binding.progressBar)
|
.indicator(binding.progressBar)
|
||||||
.enqueueWith(coil)
|
.enqueueWith(coil)
|
||||||
@@ -88,9 +90,12 @@ class ImageActivity : BaseActivity<ActivityImageBinding>() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun newIntent(context: Context, url: String): Intent {
|
private const val EXTRA_SOURCE = "source"
|
||||||
|
|
||||||
|
fun newIntent(context: Context, url: String, source: MangaSource?): Intent {
|
||||||
return Intent(context, ImageActivity::class.java)
|
return Intent(context, ImageActivity::class.java)
|
||||||
.setData(Uri.parse(url))
|
.setData(Uri.parse(url))
|
||||||
|
.putExtra(EXTRA_SOURCE, source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ abstract class MangaListFragment :
|
|||||||
|
|
||||||
private fun onError(e: Throwable) {
|
private fun onError(e: Throwable) {
|
||||||
if (e is CloudFlareProtectedException) {
|
if (e is CloudFlareProtectedException) {
|
||||||
CloudFlareDialog.newInstance(e.url).show(childFragmentManager, CloudFlareDialog.TAG)
|
CloudFlareDialog.newInstance(e.url, e.headers).show(childFragmentManager, CloudFlareDialog.TAG)
|
||||||
} else {
|
} else {
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
binding.recyclerView,
|
binding.recyclerView,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
||||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.utils.ext.referer
|
|
||||||
import org.koitharu.kotatsu.utils.image.CoverSizeResolver
|
import org.koitharu.kotatsu.utils.image.CoverSizeResolver
|
||||||
|
|
||||||
fun mangaGridItemAD(
|
fun mangaGridItemAD(
|
||||||
@@ -39,8 +38,7 @@ fun mangaGridItemAD(
|
|||||||
bind { payloads ->
|
bind { payloads ->
|
||||||
binding.textViewTitle.text = item.title
|
binding.textViewTitle.text = item.title
|
||||||
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
|
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
|
||||||
binding.imageViewCover.newImageRequest(item.coverUrl)?.run {
|
binding.imageViewCover.newImageRequest(item.coverUrl, item.source)?.run {
|
||||||
referer(item.manga.publicUrl)
|
|
||||||
size(CoverSizeResolver(binding.imageViewCover))
|
size(CoverSizeResolver(binding.imageViewCover))
|
||||||
placeholder(R.drawable.ic_placeholder)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
|
|||||||
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
||||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.utils.ext.referer
|
|
||||||
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||||
import org.koitharu.kotatsu.utils.image.CoverSizeResolver
|
import org.koitharu.kotatsu.utils.image.CoverSizeResolver
|
||||||
|
|
||||||
@@ -52,8 +51,7 @@ fun mangaListDetailedItemAD(
|
|||||||
binding.textViewTitle.text = item.title
|
binding.textViewTitle.text = item.title
|
||||||
binding.textViewSubtitle.textAndVisible = item.subtitle
|
binding.textViewSubtitle.textAndVisible = item.subtitle
|
||||||
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
|
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
|
||||||
binding.imageViewCover.newImageRequest(item.coverUrl)?.run {
|
binding.imageViewCover.newImageRequest(item.coverUrl, item.source)?.run {
|
||||||
referer(item.manga.publicUrl)
|
|
||||||
size(CoverSizeResolver(binding.imageViewCover))
|
size(CoverSizeResolver(binding.imageViewCover))
|
||||||
placeholder(R.drawable.ic_placeholder)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import org.koitharu.kotatsu.databinding.ItemMangaListBinding
|
|||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.MangaListModel
|
import org.koitharu.kotatsu.list.ui.model.MangaListModel
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
||||||
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
|
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||||
|
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||||
|
|
||||||
fun mangaListItemAD(
|
fun mangaListItemAD(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
@@ -31,8 +34,7 @@ fun mangaListItemAD(
|
|||||||
bind {
|
bind {
|
||||||
binding.textViewTitle.text = item.title
|
binding.textViewTitle.text = item.title
|
||||||
binding.textViewSubtitle.textAndVisible = item.subtitle
|
binding.textViewSubtitle.textAndVisible = item.subtitle
|
||||||
binding.imageViewCover.newImageRequest(item.coverUrl)?.run {
|
binding.imageViewCover.newImageRequest(item.coverUrl, item.source)?.run {
|
||||||
referer(item.manga.publicUrl)
|
|
||||||
placeholder(R.drawable.ic_placeholder)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
error(R.drawable.ic_error_placeholder)
|
error(R.drawable.ic_error_placeholder)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.list.ui.model
|
package org.koitharu.kotatsu.list.ui.model
|
||||||
|
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
|
||||||
sealed interface MangaItemModel : ListModel {
|
sealed interface MangaItemModel : ListModel {
|
||||||
|
|
||||||
@@ -10,4 +11,7 @@ sealed interface MangaItemModel : ListModel {
|
|||||||
val coverUrl: String
|
val coverUrl: String
|
||||||
val counter: Int
|
val counter: Int
|
||||||
val progress: Float
|
val progress: Float
|
||||||
}
|
|
||||||
|
val source: MangaSource
|
||||||
|
get() = manga.source
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import org.koitharu.kotatsu.utils.PendingIntentCompat
|
|||||||
import org.koitharu.kotatsu.utils.ext.asArrayList
|
import org.koitharu.kotatsu.utils.ext.asArrayList
|
||||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.utils.ext.referer
|
|
||||||
import org.koitharu.kotatsu.utils.ext.report
|
import org.koitharu.kotatsu.utils.ext.report
|
||||||
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
|
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -99,7 +98,10 @@ class ImportService : CoroutineIntentService() {
|
|||||||
if (manga != null) {
|
if (manga != null) {
|
||||||
notification.setLargeIcon(
|
notification.setLargeIcon(
|
||||||
coil.execute(
|
coil.execute(
|
||||||
ImageRequest.Builder(applicationContext).data(manga.coverUrl).referer(manga.publicUrl).build(),
|
ImageRequest.Builder(applicationContext)
|
||||||
|
.data(manga.coverUrl)
|
||||||
|
.tag(manga.source)
|
||||||
|
.build(),
|
||||||
).toBitmapOrNull(),
|
).toBitmapOrNull(),
|
||||||
)
|
)
|
||||||
notification.setSubText(manga.title)
|
notification.setSubText(manga.title)
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ class PageLoader @Inject constructor(
|
|||||||
.header(CommonHeaders.REFERER, page.referer)
|
.header(CommonHeaders.REFERER, page.referer)
|
||||||
.header(CommonHeaders.ACCEPT, "image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8")
|
.header(CommonHeaders.ACCEPT, "image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8")
|
||||||
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
|
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
|
||||||
|
.tag(MangaSource::class.java, page.source)
|
||||||
.build()
|
.build()
|
||||||
okHttp.newCall(request).await().use { response ->
|
okHttp.newCall(request).await().use { response ->
|
||||||
check(response.isSuccessful) {
|
check(response.isSuccessful) {
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||||
@@ -21,6 +19,8 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
|
|||||||
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
|
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
|
||||||
import org.koitharu.kotatsu.utils.ext.getParcelableCompat
|
import org.koitharu.kotatsu.utils.ext.getParcelableCompat
|
||||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemClickListener<ChapterListItem> {
|
class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemClickListener<ChapterListItem> {
|
||||||
@@ -41,7 +41,6 @@ class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemC
|
|||||||
}
|
}
|
||||||
val currentId = requireArguments().getLong(ARG_CURRENT_ID, 0L)
|
val currentId = requireArguments().getLong(ARG_CURRENT_ID, 0L)
|
||||||
val currentPosition = chapters.indexOfFirst { it.id == currentId }
|
val currentPosition = chapters.indexOfFirst { it.id == currentId }
|
||||||
val dateFormat = settings.getDateFormat()
|
|
||||||
val items = chapters.mapIndexed { index, chapter ->
|
val items = chapters.mapIndexed { index, chapter ->
|
||||||
chapter.toListItem(
|
chapter.toListItem(
|
||||||
isCurrent = index == currentPosition,
|
isCurrent = index == currentPosition,
|
||||||
@@ -49,7 +48,6 @@ class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemC
|
|||||||
isNew = false,
|
isNew = false,
|
||||||
isMissing = false,
|
isMissing = false,
|
||||||
isDownloaded = false,
|
isDownloaded = false,
|
||||||
dateFormat = dateFormat,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
binding.recyclerView.adapter = ChaptersAdapter(this).also { adapter ->
|
binding.recyclerView.adapter = ChaptersAdapter(this).also { adapter ->
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ import coil.ImageLoader
|
|||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.size.Scale
|
import coil.size.Scale
|
||||||
import coil.size.ViewSizeResolver
|
import coil.size.ViewSizeResolver
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
import com.google.android.material.slider.LabelFormatter
|
import com.google.android.material.slider.LabelFormatter
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
@@ -27,7 +25,13 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
import org.koitharu.kotatsu.parsers.util.format
|
import org.koitharu.kotatsu.parsers.util.format
|
||||||
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||||
|
import org.koitharu.kotatsu.utils.ext.decodeRegion
|
||||||
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
|
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
|
||||||
|
import org.koitharu.kotatsu.utils.ext.setValueRounded
|
||||||
|
import javax.inject.Inject
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ColorFilterConfigActivity :
|
class ColorFilterConfigActivity :
|
||||||
@@ -112,9 +116,9 @@ class ColorFilterConfigActivity :
|
|||||||
if (preview == null) return
|
if (preview == null) return
|
||||||
ImageRequest.Builder(this@ColorFilterConfigActivity)
|
ImageRequest.Builder(this@ColorFilterConfigActivity)
|
||||||
.data(preview.url)
|
.data(preview.url)
|
||||||
.referer(preview.referer)
|
|
||||||
.scale(Scale.FILL)
|
.scale(Scale.FILL)
|
||||||
.decodeRegion()
|
.decodeRegion()
|
||||||
|
.tag(preview.source)
|
||||||
.error(R.drawable.ic_error_placeholder)
|
.error(R.drawable.ic_error_placeholder)
|
||||||
.size(ViewSizeResolver(binding.imageViewBefore))
|
.size(ViewSizeResolver(binding.imageViewBefore))
|
||||||
.allowRgb565(false)
|
.allowRgb565(false)
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ import coil.ImageLoader
|
|||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.size.Scale
|
import coil.size.Scale
|
||||||
import coil.size.Size
|
import coil.size.Size
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.databinding.ItemPageThumbBinding
|
import org.koitharu.kotatsu.databinding.ItemPageThumbBinding
|
||||||
@@ -16,9 +19,9 @@ import org.koitharu.kotatsu.reader.domain.PageLoader
|
|||||||
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
|
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
|
||||||
import org.koitharu.kotatsu.utils.ext.decodeRegion
|
import org.koitharu.kotatsu.utils.ext.decodeRegion
|
||||||
import org.koitharu.kotatsu.utils.ext.isLowRamDevice
|
import org.koitharu.kotatsu.utils.ext.isLowRamDevice
|
||||||
import org.koitharu.kotatsu.utils.ext.referer
|
|
||||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.utils.ext.setTextColorAttr
|
import org.koitharu.kotatsu.utils.ext.setTextColorAttr
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
fun pageThumbnailAD(
|
fun pageThumbnailAD(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
@@ -40,7 +43,7 @@ fun pageThumbnailAD(
|
|||||||
coil.execute(
|
coil.execute(
|
||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
.data(url)
|
.data(url)
|
||||||
.referer(item.page.referer)
|
.tag(item.page.source)
|
||||||
.size(thumbSize)
|
.size(thumbSize)
|
||||||
.scale(Scale.FILL)
|
.scale(Scale.FILL)
|
||||||
.allowRgb565(true)
|
.allowRgb565(true)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ fun searchSuggestionSourceAD(
|
|||||||
lifecycleOwner: LifecycleOwner,
|
lifecycleOwner: LifecycleOwner,
|
||||||
listener: SearchSuggestionListener,
|
listener: SearchSuggestionListener,
|
||||||
) = adapterDelegateViewBinding<SearchSuggestionItem.Source, SearchSuggestionItem, ItemSearchSuggestionSourceBinding>(
|
) = adapterDelegateViewBinding<SearchSuggestionItem.Source, SearchSuggestionItem, ItemSearchSuggestionSourceBinding>(
|
||||||
{ inflater, parent -> ItemSearchSuggestionSourceBinding.inflate(inflater, parent, false) }
|
{ inflater, parent -> ItemSearchSuggestionSourceBinding.inflate(inflater, parent, false) },
|
||||||
) {
|
) {
|
||||||
|
|
||||||
binding.switchLocal.setOnCheckedChangeListener { _, isChecked ->
|
binding.switchLocal.setOnCheckedChangeListener { _, isChecked ->
|
||||||
@@ -31,7 +31,7 @@ fun searchSuggestionSourceAD(
|
|||||||
binding.textViewTitle.text = item.source.title
|
binding.textViewTitle.text = item.source.title
|
||||||
binding.switchLocal.isChecked = item.isEnabled
|
binding.switchLocal.isChecked = item.isEnabled
|
||||||
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
|
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
|
||||||
binding.imageViewCover.newImageRequest(item.source.faviconUri())?.run {
|
binding.imageViewCover.newImageRequest(item.source.faviconUri(), item.source)?.run {
|
||||||
fallback(fallbackIcon)
|
fallback(fallbackIcon)
|
||||||
placeholder(fallbackIcon)
|
placeholder(fallbackIcon)
|
||||||
error(fallbackIcon)
|
error(fallbackIcon)
|
||||||
@@ -43,4 +43,4 @@ fun searchSuggestionSourceAD(
|
|||||||
onViewRecycled {
|
onViewRecycled {
|
||||||
binding.imageViewCover.disposeImageRequest()
|
binding.imageViewCover.disposeImageRequest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ private fun searchSuggestionMangaGridAD(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bind {
|
bind {
|
||||||
binding.imageViewCover.newImageRequest(item.coverUrl)?.run {
|
binding.imageViewCover.newImageRequest(item.coverUrl, item.source)?.run {
|
||||||
placeholder(R.drawable.ic_placeholder)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
error(R.drawable.ic_error_placeholder)
|
error(R.drawable.ic_error_placeholder)
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import org.koitharu.kotatsu.utils.ext.getLocalesConfig
|
|||||||
import org.koitharu.kotatsu.utils.ext.map
|
import org.koitharu.kotatsu.utils.ext.map
|
||||||
import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat
|
import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat
|
||||||
import org.koitharu.kotatsu.utils.ext.toList
|
import org.koitharu.kotatsu.utils.ext.toList
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -55,20 +54,6 @@ class AppearanceSettingsFragment :
|
|||||||
entryValues = ListMode.values().names()
|
entryValues = ListMode.values().names()
|
||||||
setDefaultValueCompat(ListMode.GRID.name)
|
setDefaultValueCompat(ListMode.GRID.name)
|
||||||
}
|
}
|
||||||
findPreference<ListPreference>(AppSettings.KEY_DATE_FORMAT)?.run {
|
|
||||||
entryValues = resources.getStringArray(R.array.date_formats)
|
|
||||||
val now = Date().time
|
|
||||||
entries = entryValues.map { value ->
|
|
||||||
val formattedDate = settings.getDateFormat(value.toString()).format(now)
|
|
||||||
if (value == "") {
|
|
||||||
getString(R.string.default_s, formattedDate)
|
|
||||||
} else {
|
|
||||||
formattedDate
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
setDefaultValueCompat("")
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
|
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
|
||||||
?.isChecked = !settings.appPassword.isNullOrEmpty()
|
?.isChecked = !settings.appPassword.isNullOrEmpty()
|
||||||
findPreference<ActivityListPreference>(AppSettings.KEY_APP_LOCALE)?.run {
|
findPreference<ActivityListPreference>(AppSettings.KEY_APP_LOCALE)?.run {
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveError(error: Throwable) {
|
private fun resolveError(error: Throwable) {
|
||||||
|
view ?: return
|
||||||
viewLifecycleScope.launch {
|
viewLifecycleScope.launch {
|
||||||
if (exceptionResolver.resolve(error)) {
|
if (exceptionResolver.resolve(error)) {
|
||||||
val pref = findPreference<Preference>(KEY_AUTH) ?: return@launch
|
val pref = findPreference<Preference>(KEY_AUTH) ?: return@launch
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.koitharu.kotatsu.utils.image.FaviconFallbackDrawable
|
|||||||
|
|
||||||
fun sourceConfigHeaderDelegate() =
|
fun sourceConfigHeaderDelegate() =
|
||||||
adapterDelegateViewBinding<SourceConfigItem.Header, SourceConfigItem, ItemFilterHeaderBinding>(
|
adapterDelegateViewBinding<SourceConfigItem.Header, SourceConfigItem, ItemFilterHeaderBinding>(
|
||||||
{ layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) }
|
{ layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) },
|
||||||
) {
|
) {
|
||||||
|
|
||||||
bind {
|
bind {
|
||||||
@@ -31,7 +31,7 @@ fun sourceConfigHeaderDelegate() =
|
|||||||
fun sourceConfigGroupDelegate(
|
fun sourceConfigGroupDelegate(
|
||||||
listener: SourceConfigListener,
|
listener: SourceConfigListener,
|
||||||
) = adapterDelegateViewBinding<SourceConfigItem.LocaleGroup, SourceConfigItem, ItemExpandableBinding>(
|
) = adapterDelegateViewBinding<SourceConfigItem.LocaleGroup, SourceConfigItem, ItemExpandableBinding>(
|
||||||
{ layoutInflater, parent -> ItemExpandableBinding.inflate(layoutInflater, parent, false) }
|
{ layoutInflater, parent -> ItemExpandableBinding.inflate(layoutInflater, parent, false) },
|
||||||
) {
|
) {
|
||||||
|
|
||||||
binding.root.setOnClickListener {
|
binding.root.setOnClickListener {
|
||||||
@@ -50,7 +50,7 @@ fun sourceConfigItemDelegate(
|
|||||||
lifecycleOwner: LifecycleOwner,
|
lifecycleOwner: LifecycleOwner,
|
||||||
) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigBinding>(
|
) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigBinding>(
|
||||||
{ layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) },
|
{ layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) },
|
||||||
on = { item, _, _ -> item is SourceConfigItem.SourceItem && !item.isDraggable }
|
on = { item, _, _ -> item is SourceConfigItem.SourceItem && !item.isDraggable },
|
||||||
) {
|
) {
|
||||||
|
|
||||||
binding.switchToggle.setOnCheckedChangeListener { _, isChecked ->
|
binding.switchToggle.setOnCheckedChangeListener { _, isChecked ->
|
||||||
@@ -62,7 +62,7 @@ fun sourceConfigItemDelegate(
|
|||||||
binding.switchToggle.isChecked = item.isEnabled
|
binding.switchToggle.isChecked = item.isEnabled
|
||||||
binding.textViewDescription.textAndVisible = item.summary
|
binding.textViewDescription.textAndVisible = item.summary
|
||||||
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
|
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
|
||||||
binding.imageViewIcon.newImageRequest(item.source.faviconUri())?.run {
|
binding.imageViewIcon.newImageRequest(item.source.faviconUri(), item.source)?.run {
|
||||||
crossfade(context)
|
crossfade(context)
|
||||||
error(fallbackIcon)
|
error(fallbackIcon)
|
||||||
placeholder(fallbackIcon)
|
placeholder(fallbackIcon)
|
||||||
@@ -82,7 +82,7 @@ fun sourceConfigDraggableItemDelegate(
|
|||||||
listener: SourceConfigListener,
|
listener: SourceConfigListener,
|
||||||
) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigDraggableBinding>(
|
) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigDraggableBinding>(
|
||||||
{ layoutInflater, parent -> ItemSourceConfigDraggableBinding.inflate(layoutInflater, parent, false) },
|
{ layoutInflater, parent -> ItemSourceConfigDraggableBinding.inflate(layoutInflater, parent, false) },
|
||||||
on = { item, _, _ -> item is SourceConfigItem.SourceItem && item.isDraggable }
|
on = { item, _, _ -> item is SourceConfigItem.SourceItem && item.isDraggable },
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val eventListener = object :
|
val eventListener = object :
|
||||||
@@ -117,5 +117,5 @@ fun sourceConfigDraggableItemDelegate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sourceConfigEmptySearchDelegate() = adapterDelegate<SourceConfigItem.EmptySearchResult, SourceConfigItem>(
|
fun sourceConfigEmptySearchDelegate() = adapterDelegate<SourceConfigItem.EmptySearchResult, SourceConfigItem>(
|
||||||
R.layout.item_sources_empty
|
R.layout.item_sources_empty,
|
||||||
) { }
|
) { }
|
||||||
|
|||||||
@@ -11,21 +11,22 @@ import androidx.activity.result.contract.ActivityResultContract
|
|||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.browser.BrowserCallback
|
import org.koitharu.kotatsu.browser.BrowserCallback
|
||||||
import org.koitharu.kotatsu.browser.BrowserClient
|
import org.koitharu.kotatsu.browser.BrowserClient
|
||||||
import org.koitharu.kotatsu.browser.ProgressChromeClient
|
import org.koitharu.kotatsu.browser.ProgressChromeClient
|
||||||
import org.koitharu.kotatsu.core.network.UserAgentInterceptor
|
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||||
|
import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
|
||||||
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.databinding.ActivityBrowserBinding
|
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
||||||
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
|
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.utils.TaggedActivityResult
|
import org.koitharu.kotatsu.utils.TaggedActivityResult
|
||||||
|
import javax.inject.Inject
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
|
class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
|
||||||
@@ -44,7 +45,8 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
|
|||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
authProvider = (mangaRepositoryFactory.create(source) as? RemoteMangaRepository)?.getAuthProvider() ?: run {
|
val repository = mangaRepositoryFactory.create(source) as? RemoteMangaRepository
|
||||||
|
authProvider = (repository)?.getAuthProvider() ?: run {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this,
|
this,
|
||||||
getString(R.string.auth_not_supported_by, source.title),
|
getString(R.string.auth_not_supported_by, source.title),
|
||||||
@@ -59,7 +61,8 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
|
|||||||
}
|
}
|
||||||
with(binding.webView.settings) {
|
with(binding.webView.settings) {
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
userAgentString = UserAgentInterceptor.userAgentChrome
|
userAgentString = repository.headers?.get(CommonHeaders.USER_AGENT)
|
||||||
|
?: CommonHeadersInterceptor.userAgentFallback
|
||||||
}
|
}
|
||||||
binding.webView.webViewClient = BrowserClient(this)
|
binding.webView.webViewClient = BrowserClient(this)
|
||||||
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
|
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
|
||||||
@@ -96,6 +99,7 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
|
|||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ fun feedItemAD(
|
|||||||
bind {
|
bind {
|
||||||
binding.textViewTitle.isBold = item.isNew
|
binding.textViewTitle.isBold = item.isNew
|
||||||
binding.textViewSummary.isBold = item.isNew
|
binding.textViewSummary.isBold = item.isNew
|
||||||
binding.imageViewCover.newImageRequest(item.imageUrl)?.run {
|
binding.imageViewCover.newImageRequest(item.imageUrl, item.manga.source)?.run {
|
||||||
placeholder(R.drawable.ic_placeholder)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
error(R.drawable.ic_error_placeholder)
|
error(R.drawable.ic_error_placeholder)
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
|
|||||||
import org.koitharu.kotatsu.tracker.domain.Tracker
|
import org.koitharu.kotatsu.tracker.domain.Tracker
|
||||||
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
||||||
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
||||||
import org.koitharu.kotatsu.utils.ext.referer
|
|
||||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
|
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
|
||||||
import org.koitharu.kotatsu.utils.ext.trySetForeground
|
import org.koitharu.kotatsu.utils.ext.trySetForeground
|
||||||
@@ -155,7 +154,10 @@ class TrackWorker @AssistedInject constructor(
|
|||||||
setNumber(newChapters.size)
|
setNumber(newChapters.size)
|
||||||
setLargeIcon(
|
setLargeIcon(
|
||||||
coil.execute(
|
coil.execute(
|
||||||
ImageRequest.Builder(applicationContext).data(manga.coverUrl).referer(manga.publicUrl).build(),
|
ImageRequest.Builder(applicationContext)
|
||||||
|
.data(manga.coverUrl)
|
||||||
|
.tag(manga.source)
|
||||||
|
.build(),
|
||||||
).toBitmapOrNull(),
|
).toBitmapOrNull(),
|
||||||
)
|
)
|
||||||
setSmallIcon(R.drawable.ic_stat_book_plus)
|
setSmallIcon(R.drawable.ic_stat_book_plus)
|
||||||
|
|||||||
@@ -10,19 +10,19 @@ import coil.request.ImageResult
|
|||||||
import coil.request.SuccessResult
|
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 okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.utils.image.RegionBitmapDecoder
|
import org.koitharu.kotatsu.utils.image.RegionBitmapDecoder
|
||||||
import org.koitharu.kotatsu.utils.progress.ImageRequestIndicatorListener
|
import org.koitharu.kotatsu.utils.progress.ImageRequestIndicatorListener
|
||||||
|
|
||||||
fun ImageView.newImageRequest(url: Any?): ImageRequest.Builder? {
|
fun ImageView.newImageRequest(url: Any?, mangaSource: MangaSource? = null): ImageRequest.Builder? {
|
||||||
val current = CoilUtils.result(this)
|
val current = CoilUtils.result(this)
|
||||||
if (current != null && current.request.data == url) {
|
if (current != null && current.request.data == url) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return ImageRequest.Builder(context)
|
return ImageRequest.Builder(context)
|
||||||
.data(url)
|
.data(url)
|
||||||
|
.tag(mangaSource)
|
||||||
.crossfade(context)
|
.crossfade(context)
|
||||||
.target(this)
|
.target(this)
|
||||||
}
|
}
|
||||||
@@ -45,22 +45,8 @@ fun ImageResult.toBitmapOrNull() = when (this) {
|
|||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
is ErrorResult -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ImageRequest.Builder.referer(referer: String): ImageRequest.Builder {
|
is ErrorResult -> null
|
||||||
if (referer.isEmpty()) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
setHeader(CommonHeaders.REFERER, referer)
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
val baseUrl = referer.baseUrl()
|
|
||||||
if (baseUrl != null) {
|
|
||||||
setHeader(CommonHeaders.REFERER, baseUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ImageRequest.Builder.indicator(indicator: BaseProgressIndicator<*>): ImageRequest.Builder {
|
fun ImageRequest.Builder.indicator(indicator: BaseProgressIndicator<*>): ImageRequest.Builder {
|
||||||
@@ -80,11 +66,3 @@ fun ImageRequest.Builder.crossfade(context: Context?): ImageRequest.Builder {
|
|||||||
val duration = context.resources.getInteger(R.integer.config_defaultAnimTime) * context.animatorDurationScale
|
val duration = context.resources.getInteger(R.integer.config_defaultAnimTime) * context.animatorDurationScale
|
||||||
return crossfade(duration.toInt())
|
return crossfade(duration.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.baseUrl(): String? {
|
|
||||||
return (this.toHttpUrlOrNull()?.newBuilder("/") ?: return null)
|
|
||||||
.username("")
|
|
||||||
.password("")
|
|
||||||
.build()
|
|
||||||
.toString()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class RecentListFactory(
|
|||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
.data(item.coverUrl)
|
.data(item.coverUrl)
|
||||||
.size(coverSize)
|
.size(coverSize)
|
||||||
|
.tag(item.source)
|
||||||
.transformations(transformation)
|
.transformations(transformation)
|
||||||
.build(),
|
.build(),
|
||||||
).requireBitmap()
|
).requireBitmap()
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class ShelfListFactory(
|
|||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
.data(item.coverUrl)
|
.data(item.coverUrl)
|
||||||
.size(coverSize)
|
.size(coverSize)
|
||||||
|
.tag(item.source)
|
||||||
.transformations(transformation)
|
.transformations(transformation)
|
||||||
.build(),
|
.build(),
|
||||||
).requireBitmap()
|
).requireBitmap()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
android:background="?selectableItemBackground"
|
android:background="?selectableItemBackground"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="6dp"
|
android:padding="6dp"
|
||||||
tools:theme="@style/Theme.Kotatsu.Mint">
|
tools:theme="@style/Theme.Kotatsu.Miku">
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/card"
|
android:id="@+id/card"
|
||||||
|
|||||||
@@ -61,7 +61,6 @@
|
|||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingStart="0dp"
|
android:paddingStart="0dp"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
android:scrollIndicators="start|end"
|
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Colored themes -->
|
<!-- Colored themes -->
|
||||||
<style name="Theme.Kotatsu.Mint">
|
<style name="Theme.Kotatsu.Miku">
|
||||||
<item name="colorPrimary">#4CDBCE</item>
|
<item name="colorPrimary">#4CDBCE</item>
|
||||||
<item name="colorOnPrimary">#003733</item>
|
<item name="colorOnPrimary">#003733</item>
|
||||||
<item name="colorPrimaryContainer">#00504A</item>
|
<item name="colorPrimaryContainer">#00504A</item>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<item name="colorPrimaryInverse">#006A63</item>
|
<item name="colorPrimaryInverse">#006A63</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Kotatsu.October">
|
<style name="Theme.Kotatsu.Asuka">
|
||||||
<item name="colorPrimary">#FFB3AF</item>
|
<item name="colorPrimary">#FFB3AF</item>
|
||||||
<item name="colorOnPrimary">#68000E</item>
|
<item name="colorOnPrimary">#68000E</item>
|
||||||
<item name="colorPrimaryContainer">#930018</item>
|
<item name="colorPrimaryContainer">#930018</item>
|
||||||
@@ -58,4 +58,91 @@
|
|||||||
<item name="colorSurfaceInverse">#EDE0DE</item>
|
<item name="colorSurfaceInverse">#EDE0DE</item>
|
||||||
<item name="colorPrimaryInverse">#BA1928</item>
|
<item name="colorPrimaryInverse">#BA1928</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Kotatsu.Mion">
|
||||||
|
<item name="colorPrimary">#7FDA8E</item>
|
||||||
|
<item name="colorOnPrimary">#003915</item>
|
||||||
|
<item name="colorPrimaryContainer">#005321</item>
|
||||||
|
<item name="colorOnPrimaryContainer">#9AF7A8</item>
|
||||||
|
<item name="colorSecondary">#EAC32E</item>
|
||||||
|
<item name="colorOnSecondary">#3C2F00</item>
|
||||||
|
<item name="colorSecondaryContainer">#564500</item>
|
||||||
|
<item name="colorOnSecondaryContainer">#FFE080</item>
|
||||||
|
<item name="colorTertiary">#5AD5F9</item>
|
||||||
|
<item name="colorOnTertiary">#003542</item>
|
||||||
|
<item name="colorTertiaryContainer">#004E5F</item>
|
||||||
|
<item name="colorOnTertiaryContainer">#B4EBFF</item>
|
||||||
|
<item name="colorError">#FFB4AB</item>
|
||||||
|
<item name="colorErrorContainer">#93000A</item>
|
||||||
|
<item name="colorOnError">#690005</item>
|
||||||
|
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||||
|
<item name="android:colorBackground">#1A1C19</item>
|
||||||
|
<item name="colorOnBackground">#E2E3DD</item>
|
||||||
|
<item name="colorSurface">#1A1C19</item>
|
||||||
|
<item name="colorOnSurface">#E2E3DD</item>
|
||||||
|
<item name="colorSurfaceVariant">#414941</item>
|
||||||
|
<item name="colorOnSurfaceVariant">#C1C9BE</item>
|
||||||
|
<item name="colorOutline">#8B9389</item>
|
||||||
|
<item name="colorOnSurfaceInverse">#1A1C19</item>
|
||||||
|
<item name="colorSurfaceInverse">#E2E3DD</item>
|
||||||
|
<item name="colorPrimaryInverse">#006E2F</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Kotatsu.Rikka">
|
||||||
|
<item name="colorPrimary">#C2C1FF</item>
|
||||||
|
<item name="colorOnPrimary">#1800A7</item>
|
||||||
|
<item name="colorPrimaryContainer">#2C1BD7</item>
|
||||||
|
<item name="colorOnPrimaryContainer">#E2DFFF</item>
|
||||||
|
<item name="colorSecondary">#F7ACFF</item>
|
||||||
|
<item name="colorOnSecondary">#560067</item>
|
||||||
|
<item name="colorSecondaryContainer">#761789</item>
|
||||||
|
<item name="colorOnSecondaryContainer">#FFD6FF</item>
|
||||||
|
<item name="colorTertiary">#E9B9D2</item>
|
||||||
|
<item name="colorOnTertiary">#47263A</item>
|
||||||
|
<item name="colorTertiaryContainer">#5F3C51</item>
|
||||||
|
<item name="colorOnTertiaryContainer">#FFD8EB</item>
|
||||||
|
<item name="colorError">#FFB4AB</item>
|
||||||
|
<item name="colorErrorContainer">#93000A</item>
|
||||||
|
<item name="colorOnError">#690005</item>
|
||||||
|
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||||
|
<item name="android:colorBackground">#1C1B1F</item>
|
||||||
|
<item name="colorOnBackground">#E5E1E6</item>
|
||||||
|
<item name="colorSurface">#1C1B1F</item>
|
||||||
|
<item name="colorOnSurface">#E5E1E6</item>
|
||||||
|
<item name="colorSurfaceVariant">#47464F</item>
|
||||||
|
<item name="colorOnSurfaceVariant">#C8C5D0</item>
|
||||||
|
<item name="colorOutline">#918F9A</item>
|
||||||
|
<item name="colorOnSurfaceInverse">#1C1B1F</item>
|
||||||
|
<item name="colorSurfaceInverse">#E5E1E6</item>
|
||||||
|
<item name="colorPrimaryInverse">#4841EE</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Kotatsu.Sakura">
|
||||||
|
<item name="colorPrimary">#FFB1C8</item>
|
||||||
|
<item name="colorOnPrimary">#650033</item>
|
||||||
|
<item name="colorPrimaryContainer">#8E004A</item>
|
||||||
|
<item name="colorOnPrimaryContainer">#FFD9E2</item>
|
||||||
|
<item name="colorSecondary">#FFB5A0</item>
|
||||||
|
<item name="colorOnSecondary">#601500</item>
|
||||||
|
<item name="colorSecondaryContainer">#872100</item>
|
||||||
|
<item name="colorOnSecondaryContainer">#FFDBD1</item>
|
||||||
|
<item name="colorTertiary">#EFBD94</item>
|
||||||
|
<item name="colorOnTertiary">#48290B</item>
|
||||||
|
<item name="colorTertiaryContainer">#613F20</item>
|
||||||
|
<item name="colorOnTertiaryContainer">#FFDCC1</item>
|
||||||
|
<item name="colorError">#FFB4AB</item>
|
||||||
|
<item name="colorErrorContainer">#93000A</item>
|
||||||
|
<item name="colorOnError">#690005</item>
|
||||||
|
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||||
|
<item name="android:colorBackground">#201A1B</item>
|
||||||
|
<item name="colorOnBackground">#EBE0E1</item>
|
||||||
|
<item name="colorSurface">#201A1B</item>
|
||||||
|
<item name="colorOnSurface">#EBE0E1</item>
|
||||||
|
<item name="colorSurfaceVariant">#514347</item>
|
||||||
|
<item name="colorOnSurfaceVariant">#D5C2C6</item>
|
||||||
|
<item name="colorOutline">#9E8C90</item>
|
||||||
|
<item name="colorOnSurfaceInverse">#201A1B</item>
|
||||||
|
<item name="colorSurfaceInverse">#EBE0E1</item>
|
||||||
|
<item name="colorPrimaryInverse">#B31A62</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Colored themes -->
|
<!-- Colored themes -->
|
||||||
<style name="Theme.Kotatsu.Mint">
|
<style name="Theme.Kotatsu.Miku">
|
||||||
<item name="colorPrimary">#006A63</item>
|
<item name="colorPrimary">#006A63</item>
|
||||||
<item name="colorOnPrimary">#FFFFFF</item>
|
<item name="colorOnPrimary">#FFFFFF</item>
|
||||||
<item name="colorPrimaryContainer">#6EF8EA</item>
|
<item name="colorPrimaryContainer">#6EF8EA</item>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<item name="colorPrimaryInverse">#4CDBCE</item>
|
<item name="colorPrimaryInverse">#4CDBCE</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Kotatsu.October">
|
<style name="Theme.Kotatsu.Asuka">
|
||||||
<item name="colorPrimary">#BA1928</item>
|
<item name="colorPrimary">#BA1928</item>
|
||||||
<item name="colorOnPrimary">#FFFFFF</item>
|
<item name="colorOnPrimary">#FFFFFF</item>
|
||||||
<item name="colorPrimaryContainer">#FFDAD7</item>
|
<item name="colorPrimaryContainer">#FFDAD7</item>
|
||||||
@@ -58,4 +58,91 @@
|
|||||||
<item name="colorSurfaceInverse">#362F2E</item>
|
<item name="colorSurfaceInverse">#362F2E</item>
|
||||||
<item name="colorPrimaryInverse">#FFB3AF</item>
|
<item name="colorPrimaryInverse">#FFB3AF</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Kotatsu.Mion">
|
||||||
|
<item name="colorPrimary">#006E2F</item>
|
||||||
|
<item name="colorOnPrimary">#FFFFFF</item>
|
||||||
|
<item name="colorPrimaryContainer">#9AF7A8</item>
|
||||||
|
<item name="colorOnPrimaryContainer">#002109</item>
|
||||||
|
<item name="colorSecondary">#725C00</item>
|
||||||
|
<item name="colorOnSecondary">#FFFFFF</item>
|
||||||
|
<item name="colorSecondaryContainer">#FFE080</item>
|
||||||
|
<item name="colorOnSecondaryContainer">#231B00</item>
|
||||||
|
<item name="colorTertiary">#00677E</item>
|
||||||
|
<item name="colorOnTertiary">#FFFFFF</item>
|
||||||
|
<item name="colorTertiaryContainer">#B4EBFF</item>
|
||||||
|
<item name="colorOnTertiaryContainer">#001F27</item>
|
||||||
|
<item name="colorError">#BA1A1A</item>
|
||||||
|
<item name="colorErrorContainer">#FFDAD6</item>
|
||||||
|
<item name="colorOnError">#FFFFFF</item>
|
||||||
|
<item name="colorOnErrorContainer">#410002</item>
|
||||||
|
<item name="android:colorBackground">#FCFDF7</item>
|
||||||
|
<item name="colorOnBackground">#1A1C19</item>
|
||||||
|
<item name="colorSurface">#FCFDF7</item>
|
||||||
|
<item name="colorOnSurface">#1A1C19</item>
|
||||||
|
<item name="colorSurfaceVariant">#DDE5DA</item>
|
||||||
|
<item name="colorOnSurfaceVariant">#414941</item>
|
||||||
|
<item name="colorOutline">#727970</item>
|
||||||
|
<item name="colorOnSurfaceInverse">#F0F1EC</item>
|
||||||
|
<item name="colorSurfaceInverse">#2E312E</item>
|
||||||
|
<item name="colorPrimaryInverse">#7FDA8E</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Kotatsu.Rikka">
|
||||||
|
<item name="colorPrimary">#4841EE</item>
|
||||||
|
<item name="colorOnPrimary">#FFFFFF</item>
|
||||||
|
<item name="colorPrimaryContainer">#E2DFFF</item>
|
||||||
|
<item name="colorOnPrimaryContainer">#0B006B</item>
|
||||||
|
<item name="colorSecondary">#9135A3</item>
|
||||||
|
<item name="colorOnSecondary">#FFFFFF</item>
|
||||||
|
<item name="colorSecondaryContainer">#FFD6FF</item>
|
||||||
|
<item name="colorOnSecondaryContainer">#350040</item>
|
||||||
|
<item name="colorTertiary">#795369</item>
|
||||||
|
<item name="colorOnTertiary">#FFFFFF</item>
|
||||||
|
<item name="colorTertiaryContainer">#FFD8EB</item>
|
||||||
|
<item name="colorOnTertiaryContainer">#2F1124</item>
|
||||||
|
<item name="colorError">#BA1A1A</item>
|
||||||
|
<item name="colorErrorContainer">#FFDAD6</item>
|
||||||
|
<item name="colorOnError">#FFFFFF</item>
|
||||||
|
<item name="colorOnErrorContainer">#410002</item>
|
||||||
|
<item name="android:colorBackground">#FFFBFF</item>
|
||||||
|
<item name="colorOnBackground">#1C1B1F</item>
|
||||||
|
<item name="colorSurface">#FFFBFF</item>
|
||||||
|
<item name="colorOnSurface">#1C1B1F</item>
|
||||||
|
<item name="colorSurfaceVariant">#E4E1EC</item>
|
||||||
|
<item name="colorOnSurfaceVariant">#47464F</item>
|
||||||
|
<item name="colorOutline">#777680</item>
|
||||||
|
<item name="colorOnSurfaceInverse">#F3EFF4</item>
|
||||||
|
<item name="colorSurfaceInverse">#313034</item>
|
||||||
|
<item name="colorPrimaryInverse">#C2C1FF</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Kotatsu.Sakura">
|
||||||
|
<item name="colorPrimary">#B31A62</item>
|
||||||
|
<item name="colorOnPrimary">#FFFFFF</item>
|
||||||
|
<item name="colorPrimaryContainer">#FFD9E2</item>
|
||||||
|
<item name="colorOnPrimaryContainer">#3E001D</item>
|
||||||
|
<item name="colorSecondary">#B12E00</item>
|
||||||
|
<item name="colorOnSecondary">#FFFFFF</item>
|
||||||
|
<item name="colorSecondaryContainer">#FFDBD1</item>
|
||||||
|
<item name="colorOnSecondaryContainer">#3B0900</item>
|
||||||
|
<item name="colorTertiary">#7C5635</item>
|
||||||
|
<item name="colorOnTertiary">#FFFFFF</item>
|
||||||
|
<item name="colorTertiaryContainer">#FFDCC1</item>
|
||||||
|
<item name="colorOnTertiaryContainer">#2E1500</item>
|
||||||
|
<item name="colorError">#BA1A1A</item>
|
||||||
|
<item name="colorErrorContainer">#FFDAD6</item>
|
||||||
|
<item name="colorOnError">#FFFFFF</item>
|
||||||
|
<item name="colorOnErrorContainer">#410002</item>
|
||||||
|
<item name="android:colorBackground">#FFFBFF</item>
|
||||||
|
<item name="colorOnBackground">#201A1B</item>
|
||||||
|
<item name="colorSurface">#FFFBFF</item>
|
||||||
|
<item name="colorOnSurface">#201A1B</item>
|
||||||
|
<item name="colorSurfaceVariant">#F2DDE1</item>
|
||||||
|
<item name="colorOnSurfaceVariant">#514347</item>
|
||||||
|
<item name="colorOutline">#837377</item>
|
||||||
|
<item name="colorOnSurfaceInverse">#FAEEEF</item>
|
||||||
|
<item name="colorSurfaceInverse">#352F30</item>
|
||||||
|
<item name="colorPrimaryInverse">#FFB1C8</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.Kotatsu.Mint" />
|
<style name="Theme.Kotatsu.Miku" />
|
||||||
|
|
||||||
<style name="Theme.Kotatsu.October" />
|
<style name="Theme.Kotatsu.Asuka" />
|
||||||
|
|
||||||
|
<style name="Theme.Kotatsu.Mion" />
|
||||||
|
|
||||||
|
<style name="Theme.Kotatsu.Rikka" />
|
||||||
|
|
||||||
|
<style name="Theme.Kotatsu.Sakura" />
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -38,12 +38,4 @@
|
|||||||
<item>2</item>
|
<item>2</item>
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="date_formats">
|
|
||||||
<item />
|
|
||||||
<item>MM/dd/yy</item>
|
|
||||||
<item>dd/MM/yy</item>
|
|
||||||
<item>yyyy-MM-dd</item>
|
|
||||||
<item>dd MMM yyyy</item>
|
|
||||||
<item>MMM dd, yyyy</item>
|
|
||||||
</string-array>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -408,9 +408,12 @@
|
|||||||
<string name="enable_logging">Enable logging</string>
|
<string name="enable_logging">Enable logging</string>
|
||||||
<string name="enable_logging_summary">Record some actions for debug purposes</string>
|
<string name="enable_logging_summary">Record some actions for debug purposes</string>
|
||||||
<string name="show_suspicious_content">Show suspicious content</string>
|
<string name="show_suspicious_content">Show suspicious content</string>
|
||||||
<string name="theme_name_mint">Mint</string>
|
|
||||||
<string name="theme_name_dynamic">Dynamic</string>
|
<string name="theme_name_dynamic">Dynamic</string>
|
||||||
<string name="color_theme">Color scheme</string>
|
<string name="color_theme">Color scheme</string>
|
||||||
<string name="theme_name_october">October</string>
|
|
||||||
<string name="show_in_grid_view">Show in grid view</string>
|
<string name="show_in_grid_view">Show in grid view</string>
|
||||||
|
<string name="theme_name_miku">Miku</string>
|
||||||
|
<string name="theme_name_asuka">Asuka</string>
|
||||||
|
<string name="theme_name_mion">Mion</string>
|
||||||
|
<string name="theme_name_rikka">Rikka</string>
|
||||||
|
<string name="theme_name_sakura">Sakura</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,10 +27,6 @@
|
|||||||
android:title="@string/language"
|
android:title="@string/language"
|
||||||
app:allowDividerAbove="true" />
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:key="date_format"
|
|
||||||
android:title="@string/date_format" />
|
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:entries="@array/list_modes"
|
android:entries="@array/list_modes"
|
||||||
android:key="list_mode_2"
|
android:key="list_mode_2"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
|
|||||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
|
||||||
class DummyParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.DUMMY) {
|
class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.DUMMY) {
|
||||||
|
|
||||||
override val configKeyDomain: ConfigKey.Domain
|
override val configKeyDomain: ConfigKey.Domain
|
||||||
get() = ConfigKey.Domain("localhost", null)
|
get() = ConfigKey.Domain("localhost", null)
|
||||||
|
|||||||
Reference in New Issue
Block a user