Compare commits
7 Commits
v5.2
...
feature/ki
| Author | SHA1 | Date | |
|---|---|---|---|
| fd01367601 | |||
| cb64740349 | |||
| 6fdcaf0d02 | |||
| 56de725cf1 | |||
| 7a2ad47405 | |||
| 41551451b0 | |||
| d5c24cd5c8 |
3
.weblate
3
.weblate
@@ -1,3 +0,0 @@
|
|||||||
[weblate]
|
|
||||||
url = https://hosted.weblate.org/api/
|
|
||||||
translation = kotatsu/strings
|
|
||||||
@@ -19,7 +19,7 @@ Kotatsu is a free and open source manga reader for Android.
|
|||||||
* 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
|
||||||
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList
|
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList, Kitsu
|
||||||
* 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 552
|
versionCode 547
|
||||||
versionName '5.2'
|
versionName '5.1.3'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -79,17 +79,17 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation('com.github.KotatsuApp:kotatsu-parsers:f732582d55') {
|
implementation('com.github.KotatsuApp:kotatsu-parsers:44e28b40d3') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.22'
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.21'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.core:core-ktx:1.10.1'
|
implementation 'androidx.core:core-ktx:1.10.1'
|
||||||
implementation 'androidx.activity:activity-ktx:1.7.2'
|
implementation 'androidx.activity:activity-ktx:1.7.2'
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.6.0'
|
implementation 'androidx.fragment:fragment-ktx:1.5.7'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-service:2.6.1'
|
implementation 'androidx.lifecycle:lifecycle-service:2.6.1'
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltAndroidTest
|
@HiltAndroidTest
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class AppShortcutManagerTest {
|
class ShortcutsUpdaterTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
var hiltRule = HiltAndroidRule(this)
|
var hiltRule = HiltAndroidRule(this)
|
||||||
@@ -32,7 +32,7 @@ class AppShortcutManagerTest {
|
|||||||
lateinit var historyRepository: HistoryRepository
|
lateinit var historyRepository: HistoryRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appShortcutManager: AppShortcutManager
|
lateinit var shortcutsUpdater: ShortcutsUpdater
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var database: MangaDatabase
|
lateinit var database: MangaDatabase
|
||||||
@@ -72,6 +72,6 @@ class AppShortcutManagerTest {
|
|||||||
private suspend fun awaitUpdate() {
|
private suspend fun awaitUpdate() {
|
||||||
val instrumentation = InstrumentationRegistry.getInstrumentation()
|
val instrumentation = InstrumentationRegistry.getInstrumentation()
|
||||||
instrumentation.awaitForIdle()
|
instrumentation.awaitForIdle()
|
||||||
appShortcutManager.await()
|
shortcutsUpdater.await()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.koitharu.kotatsu.core.util
|
package org.koitharu.kotatsu.util
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
|
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
package org.koitharu.kotatsu.core.util.ext
|
package org.koitharu.kotatsu.util.ext
|
||||||
|
|
||||||
fun Throwable.printStackTraceDebug() = printStackTrace()
|
fun Throwable.printStackTraceDebug() = printStackTrace()
|
||||||
@@ -102,10 +102,6 @@
|
|||||||
android:name="org.koitharu.kotatsu.browser.BrowserActivity"
|
android:name="org.koitharu.kotatsu.browser.BrowserActivity"
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
<activity
|
|
||||||
android:name="org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity"
|
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
|
||||||
android:windowSoftInputMode="adjustResize" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity"
|
android:name="org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity"
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||||
@@ -163,9 +159,21 @@
|
|||||||
<data android:host="shikimori-auth" />
|
<data android:host="shikimori-auth" />
|
||||||
<data android:host="anilist-auth" />
|
<data android:host="anilist-auth" />
|
||||||
<data android:host="mal-auth" />
|
<data android:host="mal-auth" />
|
||||||
|
<data android:host="kitsu-auth" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".scrobbling.kitsu.ui.KitsuAuthActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:label="@string/kitsu"
|
||||||
|
tools:ignore="AppLinkUrlError">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:scheme="kotatsu+kitsu" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<service android:name="org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService" />
|
<service android:name="org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService" />
|
||||||
<service
|
<service
|
||||||
|
|||||||
@@ -67,10 +67,6 @@ class KotatsuApp : Application(), Configuration.Provider {
|
|||||||
reportFormat = StringFormat.JSON
|
reportFormat = StringFormat.JSON
|
||||||
excludeMatchingSharedPreferencesKeys = listOf(
|
excludeMatchingSharedPreferencesKeys = listOf(
|
||||||
"sources_\\w+",
|
"sources_\\w+",
|
||||||
AppSettings.KEY_APP_PASSWORD,
|
|
||||||
AppSettings.KEY_PROXY_LOGIN,
|
|
||||||
AppSettings.KEY_PROXY_ADDRESS,
|
|
||||||
AppSettings.KEY_PROXY_PASSWORD,
|
|
||||||
)
|
)
|
||||||
httpSender {
|
httpSender {
|
||||||
uri = getString(R.string.url_error_report)
|
uri = getString(R.string.url_error_report)
|
||||||
@@ -87,7 +83,6 @@ class KotatsuApp : Application(), Configuration.Provider {
|
|||||||
ReportField.PHONE_MODEL,
|
ReportField.PHONE_MODEL,
|
||||||
ReportField.STACK_TRACE,
|
ReportField.STACK_TRACE,
|
||||||
ReportField.CRASH_CONFIGURATION,
|
ReportField.CRASH_CONFIGURATION,
|
||||||
ReportField.CUSTOM_DATA,
|
|
||||||
ReportField.SHARED_PREFERENCES,
|
ReportField.SHARED_PREFERENCES,
|
||||||
)
|
)
|
||||||
dialog {
|
dialog {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.bookmarks.domain
|
package org.koitharu.kotatsu.bookmarks.domain
|
||||||
|
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class Bookmark(
|
class Bookmark(
|
||||||
@@ -15,20 +14,6 @@ class Bookmark(
|
|||||||
val percent: Float,
|
val percent: Float,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val directImageUrl: String?
|
|
||||||
get() = if (isImageUrlDirect()) imageUrl else null
|
|
||||||
|
|
||||||
fun toMangaPage() = MangaPage(
|
|
||||||
id = pageId,
|
|
||||||
url = imageUrl,
|
|
||||||
preview = null,
|
|
||||||
source = manga.source,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun isImageUrlDirect(): Boolean {
|
|
||||||
return imageUrl.substringAfterLast('.').length in 2..4
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import org.koitharu.kotatsu.core.db.entity.toEntity
|
|||||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||||
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
||||||
import org.koitharu.kotatsu.core.util.ext.mapItems
|
import org.koitharu.kotatsu.core.util.ext.mapItems
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Reusable
|
@Reusable
|
||||||
|
|||||||
@@ -95,11 +95,8 @@ class BookmarksFragment :
|
|||||||
|
|
||||||
override fun onItemClick(item: Bookmark, view: View) {
|
override fun onItemClick(item: Bookmark, view: View) {
|
||||||
if (selectionController?.onItemClick(item.manga, item.pageId) != true) {
|
if (selectionController?.onItemClick(item.manga, item.pageId) != true) {
|
||||||
val intent = ReaderActivity.IntentBuilder(view.context)
|
val intent = ReaderActivity.newIntent(view.context, item)
|
||||||
.bookmark(item)
|
startActivity(intent, scaleUpActivityOptionsOf(view).toBundle())
|
||||||
.incognito(true)
|
|
||||||
.build()
|
|
||||||
startActivity(intent, scaleUpActivityOptionsOf(view))
|
|
||||||
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
|
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ fun bookmarkListAD(
|
|||||||
binding.root.setOnLongClickListener(listener)
|
binding.root.setOnLongClickListener(listener)
|
||||||
|
|
||||||
bind {
|
bind {
|
||||||
val data: Any = item.directImageUrl ?: item.toMangaPage()
|
binding.imageViewThumb.newImageRequest(lifecycleOwner, item.imageUrl)?.run {
|
||||||
binding.imageViewThumb.newImageRequest(lifecycleOwner, data)?.run {
|
|
||||||
size(CoverSizeResolver(binding.imageViewThumb))
|
size(CoverSizeResolver(binding.imageViewThumb))
|
||||||
placeholder(R.drawable.ic_placeholder)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
fallback(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
viewBinding.webView.stopLoading()
|
|
||||||
viewBinding.webView.destroy()
|
viewBinding.webView.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.browser.cloudflare
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.webkit.CookieManager
|
|
||||||
import android.webkit.WebSettings
|
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
|
||||||
import androidx.core.graphics.Insets
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.view.isInvisible
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.core.view.updatePadding
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
|
|
||||||
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.ui.BaseActivity
|
|
||||||
import org.koitharu.kotatsu.core.util.TaggedActivityResult
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.catchingWebViewUnavailability
|
|
||||||
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
|
||||||
import javax.inject.Inject
|
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCallback {
|
|
||||||
|
|
||||||
private var pendingResult = RESULT_CANCELED
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var cookieJar: MutableCookieJar
|
|
||||||
|
|
||||||
private var onBackPressedCallback: WebViewBackPressedCallback? = null
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
if (!catchingWebViewUnavailability { setContentView(ActivityBrowserBinding.inflate(layoutInflater)) }) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
supportActionBar?.run {
|
|
||||||
setDisplayHomeAsUpEnabled(true)
|
|
||||||
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
|
|
||||||
}
|
|
||||||
val url = intent?.dataString.orEmpty()
|
|
||||||
with(viewBinding.webView.settings) {
|
|
||||||
javaScriptEnabled = true
|
|
||||||
cacheMode = WebSettings.LOAD_DEFAULT
|
|
||||||
domStorageEnabled = true
|
|
||||||
databaseEnabled = true
|
|
||||||
userAgentString = intent?.getStringExtra(ARG_UA) ?: CommonHeadersInterceptor.userAgentFallback
|
|
||||||
}
|
|
||||||
viewBinding.webView.webViewClient = CloudFlareClient(cookieJar, this, url)
|
|
||||||
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView).also {
|
|
||||||
onBackPressedDispatcher.addCallback(it)
|
|
||||||
}
|
|
||||||
CookieManager.getInstance().setAcceptThirdPartyCookies(viewBinding.webView, true)
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (url.isEmpty()) {
|
|
||||||
finishAfterTransition()
|
|
||||||
} else {
|
|
||||||
onTitleChanged(getString(R.string.loading_), url)
|
|
||||||
viewBinding.webView.loadUrl(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
viewBinding.webView.run {
|
|
||||||
stopLoading()
|
|
||||||
destroy()
|
|
||||||
}
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
viewBinding.webView.saveState(outState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
|
||||||
super.onRestoreInstanceState(savedInstanceState)
|
|
||||||
viewBinding.webView.restoreState(savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) {
|
|
||||||
viewBinding.appbar.updatePadding(
|
|
||||||
top = insets.top,
|
|
||||||
)
|
|
||||||
viewBinding.root.updatePadding(
|
|
||||||
left = insets.left,
|
|
||||||
right = insets.right,
|
|
||||||
bottom = insets.bottom,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
|
||||||
android.R.id.home -> {
|
|
||||||
viewBinding.webView.stopLoading()
|
|
||||||
finishAfterTransition()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
viewBinding.webView.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
viewBinding.webView.onPause()
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun finish() {
|
|
||||||
setResult(pendingResult)
|
|
||||||
super.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPageLoaded() {
|
|
||||||
viewBinding.progressBar.isInvisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCheckPassed() {
|
|
||||||
pendingResult = RESULT_OK
|
|
||||||
finishAfterTransition()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadingStateChanged(isLoading: Boolean) {
|
|
||||||
viewBinding.progressBar.isVisible = isLoading
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onHistoryChanged() {
|
|
||||||
onBackPressedCallback?.onHistoryChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {
|
|
||||||
setTitle(title)
|
|
||||||
supportActionBar?.subtitle = subtitle?.toString()?.toHttpUrlOrNull()?.topPrivateDomain() ?: subtitle
|
|
||||||
}
|
|
||||||
|
|
||||||
class Contract : ActivityResultContract<Pair<String, Headers?>, TaggedActivityResult>() {
|
|
||||||
override fun createIntent(context: Context, input: Pair<String, Headers?>): Intent {
|
|
||||||
return newIntent(context, input.first, input.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun parseResult(resultCode: Int, intent: Intent?): TaggedActivityResult {
|
|
||||||
return TaggedActivityResult(TAG, resultCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
const val TAG = "CloudFlareActivity"
|
|
||||||
private const val ARG_UA = "ua"
|
|
||||||
|
|
||||||
fun newIntent(
|
|
||||||
context: Context,
|
|
||||||
url: String,
|
|
||||||
headers: Headers?,
|
|
||||||
) = Intent(context, CloudFlareActivity::class.java).apply {
|
|
||||||
data = url.toUri()
|
|
||||||
headers?.get(CommonHeaders.USER_AGENT)?.let {
|
|
||||||
putExtra(ARG_UA, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package org.koitharu.kotatsu.browser.cloudflare
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.webkit.CookieManager
|
||||||
|
import android.webkit.WebSettings
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.fragment.app.setFragmentResult
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import okhttp3.Headers
|
||||||
|
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
|
||||||
|
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.ui.AlertDialogFragment
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||||
|
import org.koitharu.kotatsu.databinding.FragmentCloudflareBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), CloudFlareCallback {
|
||||||
|
|
||||||
|
private lateinit var url: String
|
||||||
|
private val pendingResult = Bundle(1)
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var cookieJar: MutableCookieJar
|
||||||
|
|
||||||
|
private var onBackPressedCallback: WebViewBackPressedCallback? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
url = requireArguments().getString(ARG_URL).orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewBinding(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
) = FragmentCloudflareBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
override fun onViewBindingCreated(binding: FragmentCloudflareBinding, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewBindingCreated(binding, savedInstanceState)
|
||||||
|
with(binding.webView.settings) {
|
||||||
|
javaScriptEnabled = true
|
||||||
|
cacheMode = WebSettings.LOAD_DEFAULT
|
||||||
|
domStorageEnabled = true
|
||||||
|
databaseEnabled = true
|
||||||
|
userAgentString = arguments?.getString(ARG_UA) ?: CommonHeadersInterceptor.userAgentChrome
|
||||||
|
}
|
||||||
|
binding.webView.webViewClient = CloudFlareClient(cookieJar, this, url)
|
||||||
|
CookieManager.getInstance().setAcceptThirdPartyCookies(binding.webView, true)
|
||||||
|
if (url.isEmpty()) {
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
} else {
|
||||||
|
binding.webView.loadUrl(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
requireViewBinding().webView.stopLoading()
|
||||||
|
requireViewBinding().webView.destroy()
|
||||||
|
onBackPressedCallback = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
||||||
|
return super.onBuildDialog(builder).setNegativeButton(android.R.string.cancel, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDialogCreated(dialog: AlertDialog) {
|
||||||
|
super.onDialogCreated(dialog)
|
||||||
|
onBackPressedCallback = WebViewBackPressedCallback(requireViewBinding().webView).also {
|
||||||
|
dialog.onBackPressedDispatcher.addCallback(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
requireViewBinding().webView.onResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
requireViewBinding().webView.onPause()
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
|
setFragmentResult(TAG, pendingResult)
|
||||||
|
super.onDismiss(dialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageLoaded() {
|
||||||
|
viewBinding?.progressBar?.isInvisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCheckPassed() {
|
||||||
|
pendingResult.putBoolean(EXTRA_RESULT, true)
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHistoryChanged() {
|
||||||
|
onBackPressedCallback?.onHistoryChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val TAG = "CloudFlareDialog"
|
||||||
|
const val EXTRA_RESULT = "result"
|
||||||
|
private const val ARG_URL = "url"
|
||||||
|
private const val ARG_UA = "ua"
|
||||||
|
|
||||||
|
fun newInstance(url: String, headers: Headers?) = CloudFlareDialog().withArgs(2) {
|
||||||
|
putString(ARG_URL, url)
|
||||||
|
headers?.get(CommonHeaders.USER_AGENT)?.let {
|
||||||
|
putString(ARG_UA, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,18 +29,19 @@ import org.koitharu.kotatsu.core.cache.MemoryContentCache
|
|||||||
import org.koitharu.kotatsu.core.cache.StubContentCache
|
import org.koitharu.kotatsu.core.cache.StubContentCache
|
||||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
import org.koitharu.kotatsu.core.network.*
|
import org.koitharu.kotatsu.core.network.*
|
||||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
|
||||||
import org.koitharu.kotatsu.core.os.NetworkState
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
|
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
|
||||||
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
|
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher
|
import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher
|
||||||
import org.koitharu.kotatsu.core.ui.image.CoilImageGetter
|
import org.koitharu.kotatsu.core.ui.image.CoilImageGetter
|
||||||
import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
|
import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
|
||||||
import org.koitharu.kotatsu.core.util.AcraScreenLogger
|
|
||||||
import org.koitharu.kotatsu.core.util.IncognitoModeIndicator
|
import org.koitharu.kotatsu.core.util.IncognitoModeIndicator
|
||||||
import org.koitharu.kotatsu.core.util.ext.activityManager
|
import org.koitharu.kotatsu.core.util.ext.activityManager
|
||||||
import org.koitharu.kotatsu.core.util.ext.connectivityManager
|
import org.koitharu.kotatsu.core.util.ext.connectivityManager
|
||||||
import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
|
import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
|
||||||
|
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||||
|
import org.koitharu.kotatsu.list.domain.ListExtraProviderImpl
|
||||||
import org.koitharu.kotatsu.local.data.CacheDir
|
import org.koitharu.kotatsu.local.data.CacheDir
|
||||||
import org.koitharu.kotatsu.local.data.CbzFetcher
|
import org.koitharu.kotatsu.local.data.CbzFetcher
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||||
@@ -64,6 +65,9 @@ interface AppModule {
|
|||||||
@Binds
|
@Binds
|
||||||
fun bindImageGetter(coilImageGetter: CoilImageGetter): Html.ImageGetter
|
fun bindImageGetter(coilImageGetter: CoilImageGetter): Html.ImageGetter
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindListExtraProvider(impl: ListExtraProviderImpl): ListExtraProvider
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -126,12 +130,12 @@ interface AppModule {
|
|||||||
@ElementsIntoSet
|
@ElementsIntoSet
|
||||||
fun provideDatabaseObservers(
|
fun provideDatabaseObservers(
|
||||||
widgetUpdater: WidgetUpdater,
|
widgetUpdater: WidgetUpdater,
|
||||||
appShortcutManager: AppShortcutManager,
|
shortcutsUpdater: ShortcutsUpdater,
|
||||||
backupObserver: BackupObserver,
|
backupObserver: BackupObserver,
|
||||||
syncController: SyncController,
|
syncController: SyncController,
|
||||||
): Set<@JvmSuppressWildcards InvalidationTracker.Observer> = arraySetOf(
|
): Set<@JvmSuppressWildcards InvalidationTracker.Observer> = arraySetOf(
|
||||||
widgetUpdater,
|
widgetUpdater,
|
||||||
appShortcutManager,
|
shortcutsUpdater,
|
||||||
backupObserver,
|
backupObserver,
|
||||||
syncController,
|
syncController,
|
||||||
)
|
)
|
||||||
@@ -142,12 +146,10 @@ interface AppModule {
|
|||||||
appProtectHelper: AppProtectHelper,
|
appProtectHelper: AppProtectHelper,
|
||||||
activityRecreationHandle: ActivityRecreationHandle,
|
activityRecreationHandle: ActivityRecreationHandle,
|
||||||
incognitoModeIndicator: IncognitoModeIndicator,
|
incognitoModeIndicator: IncognitoModeIndicator,
|
||||||
acraScreenLogger: AcraScreenLogger,
|
|
||||||
): Set<@JvmSuppressWildcards Application.ActivityLifecycleCallbacks> = arraySetOf(
|
): Set<@JvmSuppressWildcards Application.ActivityLifecycleCallbacks> = arraySetOf(
|
||||||
appProtectHelper,
|
appProtectHelper,
|
||||||
activityRecreationHandle,
|
activityRecreationHandle,
|
||||||
incognitoModeIndicator,
|
incognitoModeIndicator,
|
||||||
acraScreenLogger,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ import androidx.annotation.StringRes
|
|||||||
import androidx.collection.ArrayMap
|
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 okhttp3.Headers
|
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.CloudFlareActivity
|
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
|
||||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||||
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
|
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
|
||||||
import org.koitharu.kotatsu.core.util.TaggedActivityResult
|
import org.koitharu.kotatsu.core.util.TaggedActivityResult
|
||||||
|
import org.koitharu.kotatsu.core.util.isSuccess
|
||||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
@@ -21,26 +23,20 @@ import kotlin.coroutines.Continuation
|
|||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
class ExceptionResolver private constructor(
|
||||||
|
private val activity: FragmentActivity?,
|
||||||
|
private val fragment: Fragment?,
|
||||||
|
) : ActivityResultCallback<TaggedActivityResult> {
|
||||||
|
|
||||||
private val continuations = ArrayMap<String, Continuation<Boolean>>(1)
|
private val continuations = ArrayMap<String, Continuation<Boolean>>(1)
|
||||||
private val activity: FragmentActivity?
|
private lateinit var sourceAuthContract: ActivityResultLauncher<MangaSource>
|
||||||
private val fragment: Fragment?
|
|
||||||
private val sourceAuthContract: ActivityResultLauncher<MangaSource>
|
|
||||||
private val cloudflareContract: ActivityResultLauncher<Pair<String, Headers?>>
|
|
||||||
|
|
||||||
constructor(activity: FragmentActivity) {
|
constructor(activity: FragmentActivity) : this(activity = activity, fragment = null) {
|
||||||
this.activity = activity
|
|
||||||
fragment = null
|
|
||||||
sourceAuthContract = activity.registerForActivityResult(SourceAuthActivity.Contract(), this)
|
sourceAuthContract = activity.registerForActivityResult(SourceAuthActivity.Contract(), this)
|
||||||
cloudflareContract = activity.registerForActivityResult(CloudFlareActivity.Contract(), this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(fragment: Fragment) {
|
constructor(fragment: Fragment) : this(activity = null, fragment = fragment) {
|
||||||
this.fragment = fragment
|
|
||||||
activity = null
|
|
||||||
sourceAuthContract = fragment.registerForActivityResult(SourceAuthActivity.Contract(), this)
|
sourceAuthContract = fragment.registerForActivityResult(SourceAuthActivity.Contract(), this)
|
||||||
cloudflareContract = fragment.registerForActivityResult(CloudFlareActivity.Contract(), this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(result: TaggedActivityResult) {
|
override fun onActivityResult(result: TaggedActivityResult) {
|
||||||
@@ -62,9 +58,22 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun resolveCF(url: String, headers: Headers): Boolean = suspendCoroutine { cont ->
|
private suspend fun resolveCF(url: String, headers: Headers): Boolean {
|
||||||
continuations[CloudFlareActivity.TAG] = cont
|
val dialog = CloudFlareDialog.newInstance(url, headers)
|
||||||
cloudflareContract.launch(url to headers)
|
val fm = getFragmentManager()
|
||||||
|
return suspendCancellableCoroutine { cont ->
|
||||||
|
fm.clearFragmentResult(CloudFlareDialog.TAG)
|
||||||
|
continuations[CloudFlareDialog.TAG] = cont
|
||||||
|
fm.setFragmentResultListener(CloudFlareDialog.TAG, checkNotNull(fragment ?: activity)) { key, result ->
|
||||||
|
continuations.remove(key)?.resume(result.getBoolean(CloudFlareDialog.EXTRA_RESULT))
|
||||||
|
}
|
||||||
|
dialog.show(fm, CloudFlareDialog.TAG)
|
||||||
|
cont.invokeOnCancellation {
|
||||||
|
continuations.remove(CloudFlareDialog.TAG, cont)
|
||||||
|
fm.clearFragmentResultListener(CloudFlareDialog.TAG)
|
||||||
|
dialog.dismissAllowingStateLoss()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun resolveAuthException(source: MangaSource): Boolean = suspendCoroutine { cont ->
|
private suspend fun resolveAuthException(source: MangaSource): Boolean = suspendCoroutine { cont ->
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import org.koitharu.kotatsu.BuildConfig
|
|||||||
import org.koitharu.kotatsu.core.network.BaseHttpClient
|
import org.koitharu.kotatsu.core.network.BaseHttpClient
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.util.ext.asArrayList
|
import org.koitharu.kotatsu.core.util.ext.asArrayList
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import org.koitharu.kotatsu.parsers.util.await
|
import org.koitharu.kotatsu.parsers.util.await
|
||||||
import org.koitharu.kotatsu.parsers.util.byte2HexFormatted
|
import org.koitharu.kotatsu.parsers.util.byte2HexFormatted
|
||||||
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
|
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
|
||||||
import org.koitharu.kotatsu.parsers.util.parseJsonArray
|
import org.koitharu.kotatsu.parsers.util.parseJsonArray
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
|||||||
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
||||||
import org.koitharu.kotatsu.core.util.ext.subdir
|
import org.koitharu.kotatsu.core.util.ext.subdir
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.core.network
|
package org.koitharu.kotatsu.core.network
|
||||||
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ 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.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.mergeWith
|
import org.koitharu.kotatsu.parsers.util.mergeWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.net.IDN
|
import java.net.IDN
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.dnsoverhttps.DnsOverHttps
|
import okhttp3.dnsoverhttps.DnsOverHttps
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.core.network
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.Cookie
|
import okhttp3.Cookie
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
|
|
||||||
private const val PREFS_NAME = "cookies"
|
private const val PREFS_NAME = "cookies"
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.core.graphics.drawable.toBitmap
|
|||||||
import androidx.room.InvalidationTracker
|
import androidx.room.InvalidationTracker
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
|
import coil.size.Precision
|
||||||
import coil.size.Scale
|
import coil.size.Scale
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -24,19 +25,18 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
||||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.image.ThumbnailTransformation
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||||
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppShortcutManager @Inject constructor(
|
class ShortcutsUpdater @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val coil: ImageLoader,
|
private val coil: ImageLoader,
|
||||||
private val historyRepository: HistoryRepository,
|
private val historyRepository: HistoryRepository,
|
||||||
@@ -128,8 +128,8 @@ class AppShortcutManager @Inject constructor(
|
|||||||
.data(manga.coverUrl)
|
.data(manga.coverUrl)
|
||||||
.size(iconSize.width, iconSize.height)
|
.size(iconSize.width, iconSize.height)
|
||||||
.tag(manga.source)
|
.tag(manga.source)
|
||||||
|
.precision(Precision.EXACT)
|
||||||
.scale(Scale.FILL)
|
.scale(Scale.FILL)
|
||||||
.transformations(ThumbnailTransformation())
|
|
||||||
.build(),
|
.build(),
|
||||||
).getDrawableOrThrow().toBitmap()
|
).getDrawableOrThrow().toBitmap()
|
||||||
}.fold(
|
}.fold(
|
||||||
@@ -142,9 +142,8 @@ class AppShortcutManager @Inject constructor(
|
|||||||
.setLongLabel(manga.title)
|
.setLongLabel(manga.title)
|
||||||
.setIcon(icon)
|
.setIcon(icon)
|
||||||
.setIntent(
|
.setIntent(
|
||||||
ReaderActivity.IntentBuilder(context)
|
ReaderActivity.newIntent(context, manga.id)
|
||||||
.mangaId(manga.id)
|
.setAction(ReaderActivity.ACTION_MANGA_READ),
|
||||||
.build(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.koitharu.kotatsu.core.parser
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
import dagger.Reusable
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Reusable
|
||||||
|
class MangaTagHighlighter @Inject constructor(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val dict by lazy {
|
||||||
|
context.resources.openRawResource(R.raw.tags_redlist).use {
|
||||||
|
val set = HashSet<String>()
|
||||||
|
it.bufferedReader().forEachLine { x ->
|
||||||
|
val line = x.trim()
|
||||||
|
if (line.isNotEmpty()) {
|
||||||
|
set.add(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorRes
|
||||||
|
fun getTint(tag: MangaTag): Int {
|
||||||
|
return if (tag.title.lowercase() in dict) {
|
||||||
|
R.color.warning
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -428,6 +428,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_SHIKIMORI = "shikimori"
|
const val KEY_SHIKIMORI = "shikimori"
|
||||||
const val KEY_ANILIST = "anilist"
|
const val KEY_ANILIST = "anilist"
|
||||||
const val KEY_MAL = "mal"
|
const val KEY_MAL = "mal"
|
||||||
|
const val KEY_KITSU = "kitsu"
|
||||||
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
|
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
|
||||||
const val KEY_DOWNLOADS_WIFI = "downloads_wifi"
|
const val KEY_DOWNLOADS_WIFI = "downloads_wifi"
|
||||||
const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible"
|
const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible"
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ abstract class AlertDialogFragment<B : ViewBinding> : DialogFragment() {
|
|||||||
.setView(binding.root)
|
.setView(binding.root)
|
||||||
.run(::onBuildDialog)
|
.run(::onBuildDialog)
|
||||||
.create()
|
.create()
|
||||||
.also(::onDialogCreated)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final override fun onCreateView(
|
final override fun onCreateView(
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.appcompat.widget.ActionBarContextView
|
import androidx.appcompat.widget.ActionBarContextView
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
@@ -44,7 +45,7 @@ abstract class BaseActivity<B : ViewBinding> :
|
|||||||
protected val exceptionResolver = ExceptionResolver(this)
|
protected val exceptionResolver = ExceptionResolver(this)
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
protected val insetsDelegate = WindowInsetsDelegate()
|
protected val insetsDelegate = WindowInsetsDelegate(this)
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val actionModeDelegate = ActionModeDelegate()
|
val actionModeDelegate = ActionModeDelegate()
|
||||||
@@ -61,7 +62,6 @@ abstract class BaseActivity<B : ViewBinding> :
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
insetsDelegate.handleImeInsets = true
|
insetsDelegate.handleImeInsets = true
|
||||||
insetsDelegate.addInsetsListener(this)
|
|
||||||
putDataToExtras(intent)
|
putDataToExtras(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +103,7 @@ abstract class BaseActivity<B : ViewBinding> :
|
|||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
// ActivityCompat.recreate(this)
|
ActivityCompat.recreate(this)
|
||||||
error("Test")
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.onKeyDown(keyCode, event)
|
return super.onKeyDown(keyCode, event)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ abstract class BaseFragment<B : ViewBinding> :
|
|||||||
protected val exceptionResolver = ExceptionResolver(this)
|
protected val exceptionResolver = ExceptionResolver(this)
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
protected val insetsDelegate = WindowInsetsDelegate()
|
protected val insetsDelegate = WindowInsetsDelegate(this)
|
||||||
|
|
||||||
protected val actionModeDelegate: ActionModeDelegate
|
protected val actionModeDelegate: ActionModeDelegate
|
||||||
get() = (requireActivity() as BaseActivity<*>).actionModeDelegate
|
get() = (requireActivity() as BaseActivity<*>).actionModeDelegate
|
||||||
@@ -44,13 +44,11 @@ abstract class BaseFragment<B : ViewBinding> :
|
|||||||
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
insetsDelegate.onViewCreated(view)
|
insetsDelegate.onViewCreated(view)
|
||||||
insetsDelegate.addInsetsListener(this)
|
|
||||||
onViewBindingCreated(requireViewBinding(), savedInstanceState)
|
onViewBindingCreated(requireViewBinding(), savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
viewBinding = null
|
viewBinding = null
|
||||||
insetsDelegate.removeInsetsListener(this)
|
|
||||||
insetsDelegate.onDestroyView()
|
insetsDelegate.onDestroyView()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ import android.graphics.Color
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
|
|
||||||
abstract class BaseFullscreenActivity<B : ViewBinding> :
|
abstract class BaseFullscreenActivity<B : ViewBinding> :
|
||||||
BaseActivity<B>() {
|
BaseActivity<B>() {
|
||||||
@@ -20,11 +18,7 @@ abstract class BaseFullscreenActivity<B : ViewBinding> :
|
|||||||
with(window) {
|
with(window) {
|
||||||
insetsControllerCompat = WindowInsetsControllerCompat(this, decorView)
|
insetsControllerCompat = WindowInsetsControllerCompat(this, decorView)
|
||||||
statusBarColor = Color.TRANSPARENT
|
statusBarColor = Color.TRANSPARENT
|
||||||
navigationBarColor = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
navigationBarColor = Color.TRANSPARENT
|
||||||
ContextCompat.getColor(this@BaseFullscreenActivity, R.color.dim)
|
|
||||||
} else {
|
|
||||||
Color.TRANSPARENT
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
attributes.layoutInDisplayCutoutMode =
|
attributes.layoutInDisplayCutoutMode =
|
||||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
|
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
|
||||||
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
|
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.settings.SettingsHeadersFragment
|
||||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Suppress("LeakingThis")
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
||||||
PreferenceFragmentCompat(),
|
PreferenceFragmentCompat(),
|
||||||
@@ -26,28 +26,27 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
|||||||
lateinit var settings: AppSettings
|
lateinit var settings: AppSettings
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
protected val insetsDelegate = WindowInsetsDelegate()
|
protected val insetsDelegate = WindowInsetsDelegate(this)
|
||||||
|
|
||||||
override val recyclerView: RecyclerView
|
override val recyclerView: RecyclerView
|
||||||
get() = listView
|
get() = listView
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
view.setBackgroundColor(view.context.getThemeColor(android.R.attr.colorBackground))
|
|
||||||
listView.clipToPadding = false
|
listView.clipToPadding = false
|
||||||
insetsDelegate.onViewCreated(view)
|
insetsDelegate.onViewCreated(view)
|
||||||
insetsDelegate.addInsetsListener(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
insetsDelegate.removeInsetsListener(this)
|
|
||||||
insetsDelegate.onDestroyView()
|
insetsDelegate.onDestroyView()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
setTitle(if (titleId != 0) getString(titleId) else null)
|
if (titleId != 0) {
|
||||||
|
setTitle(getString(titleId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
@@ -57,7 +56,9 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun setTitle(title: CharSequence?) {
|
@Suppress("UsePropertyAccessSyntax")
|
||||||
(activity as? SettingsActivity)?.setSectionTitle(title)
|
protected fun setTitle(title: CharSequence) {
|
||||||
|
(parentFragment as? SettingsHeadersFragment)?.setTitle(title)
|
||||||
|
?: activity?.setTitle(title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import kotlinx.coroutines.launch
|
|||||||
import org.koitharu.kotatsu.core.util.ext.EventFlow
|
import org.koitharu.kotatsu.core.util.ext.EventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
|
|
||||||
abstract class CoroutineIntentService : BaseService() {
|
abstract class CoroutineIntentService : BaseService() {
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.ui.image
|
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.media.ThumbnailUtils
|
|
||||||
import coil.size.Size
|
|
||||||
import coil.size.pxOrElse
|
|
||||||
import coil.transform.Transformation
|
|
||||||
|
|
||||||
class ThumbnailTransformation : Transformation {
|
|
||||||
|
|
||||||
override val cacheKey: String = javaClass.name
|
|
||||||
|
|
||||||
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
|
|
||||||
return ThumbnailUtils.extractThumbnail(
|
|
||||||
input,
|
|
||||||
size.width.pxOrElse { input.width },
|
|
||||||
size.height.pxOrElse { input.height },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?) = other is ThumbnailTransformation
|
|
||||||
|
|
||||||
override fun hashCode() = javaClass.hashCode()
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ import android.util.AttributeSet
|
|||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.annotation.*
|
import androidx.annotation.*
|
||||||
@@ -25,7 +24,6 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
import org.koitharu.kotatsu.core.util.ext.isLayoutReversed
|
import org.koitharu.kotatsu.core.util.ext.isLayoutReversed
|
||||||
import org.koitharu.kotatsu.core.util.ext.parents
|
|
||||||
import org.koitharu.kotatsu.databinding.FastScrollerBinding
|
import org.koitharu.kotatsu.databinding.FastScrollerBinding
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
@@ -117,9 +115,6 @@ class FastScroller @JvmOverloads constructor(
|
|||||||
return viewHeight * proportion
|
return viewHeight * proportion
|
||||||
}
|
}
|
||||||
|
|
||||||
val isScrollbarVisible: Boolean
|
|
||||||
get() = binding.scrollbar.isVisible
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
clipChildren = false
|
clipChildren = false
|
||||||
orientation = HORIZONTAL
|
orientation = HORIZONTAL
|
||||||
@@ -170,9 +165,7 @@ class FastScroller @JvmOverloads constructor(
|
|||||||
|
|
||||||
when (event.actionMasked) {
|
when (event.actionMasked) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
if (!isScrollbarVisible || event.x.toInt() !in binding.scrollbar.left..binding.scrollbar.right) {
|
if (event.x.toInt() !in binding.scrollbar.left..binding.scrollbar.right) return false
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
requestDisallowInterceptTouchEvent(true)
|
requestDisallowInterceptTouchEvent(true)
|
||||||
setHandleSelected(true)
|
setHandleSelected(true)
|
||||||
@@ -303,12 +296,10 @@ class FastScroller @JvmOverloads constructor(
|
|||||||
|
|
||||||
if (parent is ViewGroup) {
|
if (parent is ViewGroup) {
|
||||||
setLayoutParams(parent as ViewGroup)
|
setLayoutParams(parent as ViewGroup)
|
||||||
} else {
|
} else if (recyclerView.parent is ViewGroup) {
|
||||||
val viewGroup = findValidParent(recyclerView)
|
val viewGroup = recyclerView.parent as ViewGroup
|
||||||
if (viewGroup != null) {
|
viewGroup.addView(this)
|
||||||
viewGroup.addView(this)
|
setLayoutParams(viewGroup)
|
||||||
setLayoutParams(viewGroup)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(scrollListener)
|
recyclerView.addOnScrollListener(scrollListener)
|
||||||
@@ -522,14 +513,6 @@ class FastScroller @JvmOverloads constructor(
|
|||||||
return BubbleSize.values().getOrNull(ordinal) ?: defaultValue
|
return BubbleSize.values().getOrNull(ordinal) ?: defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findValidParent(view: View): ViewGroup? = view.parents.firstNotNullOfOrNull { p ->
|
|
||||||
if (p is FrameLayout || p is ConstraintLayout || p is CoordinatorLayout || p is RelativeLayout) {
|
|
||||||
p as ViewGroup
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val BubbleSize.textSize
|
private val BubbleSize.textSize
|
||||||
@Px get() = resources.getDimension(textSizeId)
|
@Px get() = resources.getDimension(textSizeId)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
|
import androidx.activity.ComponentDialog
|
||||||
import androidx.activity.OnBackPressedDispatcher
|
import androidx.activity.OnBackPressedDispatcher
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
@@ -35,7 +36,7 @@ abstract class BaseAdaptiveSheet<B : ViewBinding> : AppCompatDialogFragment() {
|
|||||||
get() = behavior?.state == AdaptiveSheetBehavior.STATE_EXPANDED
|
get() = behavior?.state == AdaptiveSheetBehavior.STATE_EXPANDED
|
||||||
|
|
||||||
val onBackPressedDispatcher: OnBackPressedDispatcher
|
val onBackPressedDispatcher: OnBackPressedDispatcher
|
||||||
get() = requireComponentDialog().onBackPressedDispatcher
|
get() = (requireDialog() as ComponentDialog).onBackPressedDispatcher
|
||||||
|
|
||||||
final override fun onCreateView(
|
final override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class ActionModeDelegate : OnBackPressedCallback(false) {
|
|||||||
get() = activeActionMode != null
|
get() = activeActionMode != null
|
||||||
|
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
finishActionMode()
|
activeActionMode?.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSupportActionModeStarted(mode: ActionMode) {
|
fun onSupportActionModeStarted(mode: ActionMode) {
|
||||||
@@ -45,10 +45,6 @@ class ActionModeDelegate : OnBackPressedCallback(false) {
|
|||||||
owner.lifecycle.addObserver(ListenerLifecycleObserver(listener))
|
owner.lifecycle.addObserver(ListenerLifecycleObserver(listener))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finishActionMode() {
|
|
||||||
activeActionMode?.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class ListenerLifecycleObserver(
|
private inner class ListenerLifecycleObserver(
|
||||||
private val listener: ActionModeListener,
|
private val listener: ActionModeListener,
|
||||||
) : DefaultLifecycleObserver {
|
) : DefaultLifecycleObserver {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
|
|
||||||
fun interface ReversibleHandle {
|
fun interface ReversibleHandle {
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import androidx.core.graphics.Insets
|
|||||||
import androidx.core.view.OnApplyWindowInsetsListener
|
import androidx.core.view.OnApplyWindowInsetsListener
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import java.util.LinkedList
|
|
||||||
|
|
||||||
class WindowInsetsDelegate : OnApplyWindowInsetsListener, View.OnLayoutChangeListener {
|
class WindowInsetsDelegate(
|
||||||
|
private val listener: WindowInsetsListener,
|
||||||
|
) : OnApplyWindowInsetsListener, View.OnLayoutChangeListener {
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
var handleImeInsets: Boolean = false
|
var handleImeInsets: Boolean = false
|
||||||
@@ -15,7 +16,6 @@ class WindowInsetsDelegate : OnApplyWindowInsetsListener, View.OnLayoutChangeLis
|
|||||||
@JvmField
|
@JvmField
|
||||||
var interceptingWindowInsetsListener: OnApplyWindowInsetsListener? = null
|
var interceptingWindowInsetsListener: OnApplyWindowInsetsListener? = null
|
||||||
|
|
||||||
private val listeners = LinkedList<WindowInsetsListener>()
|
|
||||||
private var lastInsets: Insets? = null
|
private var lastInsets: Insets? = null
|
||||||
|
|
||||||
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
|
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
|
||||||
@@ -29,7 +29,7 @@ class WindowInsetsDelegate : OnApplyWindowInsetsListener, View.OnLayoutChangeLis
|
|||||||
handledInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
handledInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
}
|
}
|
||||||
if (newInsets != lastInsets) {
|
if (newInsets != lastInsets) {
|
||||||
listeners.forEach { it.onWindowInsetsChanged(newInsets) }
|
listener.onWindowInsetsChanged(newInsets)
|
||||||
lastInsets = newInsets
|
lastInsets = newInsets
|
||||||
}
|
}
|
||||||
return handledInsets
|
return handledInsets
|
||||||
@@ -52,15 +52,6 @@ class WindowInsetsDelegate : OnApplyWindowInsetsListener, View.OnLayoutChangeLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addInsetsListener(listener: WindowInsetsListener) {
|
|
||||||
listeners.add(listener)
|
|
||||||
lastInsets?.let { listener.onWindowInsetsChanged(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeInsetsListener(listener: WindowInsetsListener) {
|
|
||||||
listeners.remove(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onViewCreated(view: View) {
|
fun onViewCreated(view: View) {
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(view, this)
|
ViewCompat.setOnApplyWindowInsetsListener(view, this)
|
||||||
view.addOnLayoutChangeListener(this)
|
view.addOnLayoutChangeListener(this)
|
||||||
|
|||||||
@@ -92,14 +92,6 @@ class SlidingBottomNavigationView @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showOrHide(show: Boolean) {
|
|
||||||
if (show) {
|
|
||||||
show()
|
|
||||||
} else {
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) {
|
private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) {
|
||||||
currentAnimator = animate()
|
currentAnimator = animate()
|
||||||
.translationY(targetY)
|
.translationY(targetY)
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.util
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks
|
|
||||||
import org.acra.ACRA
|
|
||||||
import org.koitharu.kotatsu.core.ui.DefaultActivityLifecycleCallbacks
|
|
||||||
import java.text.DateFormat
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class AcraScreenLogger @Inject constructor() : FragmentLifecycleCallbacks(), DefaultActivityLifecycleCallbacks {
|
|
||||||
|
|
||||||
private val timeFormat = SimpleDateFormat.getTimeInstance(DateFormat.DEFAULT, Locale.ROOT)
|
|
||||||
|
|
||||||
override fun onFragmentAttached(fm: FragmentManager, f: Fragment, context: Context) {
|
|
||||||
super.onFragmentAttached(fm, f, context)
|
|
||||||
ACRA.errorReporter.putCustomData(f.key(), "${time()}: ${f.arguments}")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFragmentDetached(fm: FragmentManager, f: Fragment) {
|
|
||||||
super.onFragmentDetached(fm, f)
|
|
||||||
ACRA.errorReporter.removeCustomData(f.key())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(activity, savedInstanceState)
|
|
||||||
ACRA.errorReporter.putCustomData(activity.key(), "${time()}: ${activity.intent}")
|
|
||||||
(activity as? FragmentActivity)?.supportFragmentManager?.registerFragmentLifecycleCallbacks(this, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityDestroyed(activity: Activity) {
|
|
||||||
super.onActivityDestroyed(activity)
|
|
||||||
ACRA.errorReporter.removeCustomData(activity.key())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Activity.key() = "Activity[${javaClass.simpleName}]"
|
|
||||||
|
|
||||||
private fun Fragment.key() = "Fragment[${javaClass.simpleName}]"
|
|
||||||
|
|
||||||
private fun time() = timeFormat.format(Date())
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,7 @@ import android.app.Activity
|
|||||||
class TaggedActivityResult(
|
class TaggedActivityResult(
|
||||||
val tag: String,
|
val tag: String,
|
||||||
val result: Int,
|
val result: Int,
|
||||||
) {
|
)
|
||||||
|
|
||||||
val isSuccess: Boolean
|
val TaggedActivityResult.isSuccess: Boolean
|
||||||
get() = result == Activity.RESULT_OK
|
get() = this.result == Activity.RESULT_OK
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import android.database.SQLException
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewPropertyAnimator
|
import android.view.ViewPropertyAnimator
|
||||||
@@ -44,6 +43,7 @@ import org.jsoup.internal.StringUtil.StringJoiner
|
|||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
@@ -148,17 +148,13 @@ val Context.ramAvailable: Long
|
|||||||
return result.availMem
|
return result.availMem
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scaleUpActivityOptionsOf(view: View): Bundle? = if (view.context.isAnimationsEnabled) {
|
fun scaleUpActivityOptionsOf(view: View): ActivityOptions = ActivityOptions.makeScaleUpAnimation(
|
||||||
ActivityOptions.makeScaleUpAnimation(
|
view,
|
||||||
view,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
view.width,
|
||||||
view.width,
|
view.height,
|
||||||
view.height,
|
)
|
||||||
).toBundle()
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Resources.getLocalesConfig(): LocaleListCompat {
|
fun Resources.getLocalesConfig(): LocaleListCompat {
|
||||||
val tagsList = StringJoiner(",")
|
val tagsList = StringJoiner(",")
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ fun File.takeIfReadable() = takeIf { it.exists() && it.canRead() }
|
|||||||
|
|
||||||
fun File.takeIfWriteable() = takeIf { it.exists() && it.canWrite() }
|
fun File.takeIfWriteable() = takeIf { it.exists() && it.canWrite() }
|
||||||
|
|
||||||
fun File.isNotEmpty() = length() != 0L
|
|
||||||
|
|
||||||
fun ZipFile.readText(entry: ZipEntry) = getInputStream(entry).bufferedReader().use {
|
fun ZipFile.readText(entry: ZipEntry) = getInputStream(entry).bufferedReader().use {
|
||||||
it.readText()
|
it.readText()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import org.koitharu.kotatsu.core.util.Event
|
|||||||
fun <T> Flow<T>.observe(owner: LifecycleOwner, collector: FlowCollector<T>) {
|
fun <T> Flow<T>.observe(owner: LifecycleOwner, collector: FlowCollector<T>) {
|
||||||
val start = if (this is StateFlow) CoroutineStart.UNDISPATCHED else CoroutineStart.DEFAULT
|
val start = if (this is StateFlow) CoroutineStart.UNDISPATCHED else CoroutineStart.DEFAULT
|
||||||
owner.lifecycleScope.launch(start = start) {
|
owner.lifecycleScope.launch(start = start) {
|
||||||
collect(collector)
|
owner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
collect(collector)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,11 +52,3 @@ suspend fun Fragment.awaitViewLifecycle(): LifecycleOwner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DialogFragment.showDistinct(fm: FragmentManager, tag: String) {
|
|
||||||
val existing = fm.findFragmentByTag(tag) as? DialogFragment?
|
|
||||||
if (existing != null && existing.isVisible && existing.arguments == this.arguments) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
show(fm, tag)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package org.koitharu.kotatsu.core.util.ext
|
|||||||
|
|
||||||
import androidx.annotation.FloatRange
|
import androidx.annotation.FloatRange
|
||||||
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
|
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
|
||||||
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
inline fun <C : CharSequence> C?.ifNullOrEmpty(defaultValue: () -> C): C {
|
inline fun String?.ifNullOrEmpty(defaultValue: () -> String): String {
|
||||||
return if (this.isNullOrEmpty()) defaultValue() else this
|
return if (this.isNullOrEmpty()) defaultValue() else this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,9 +35,3 @@ fun String.almostEquals(other: String, @FloatRange(from = 0.0) threshold: Float)
|
|||||||
val diff = lowercase().levenshteinDistance(other.lowercase()) / ((length + other.length) / 2f)
|
val diff = lowercase().levenshteinDistance(other.lowercase()) / ((length + other.length) / 2f)
|
||||||
return diff < threshold
|
return diff < threshold
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CharSequence.sanitize(): CharSequence {
|
|
||||||
return filterNot { c -> c.isReplacement() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Char.isReplacement() = this in '\uFFF0'..'\uFFFF'
|
|
||||||
|
|||||||
@@ -87,6 +87,3 @@ fun Throwable.isWebViewUnavailable(): Boolean {
|
|||||||
return (this is AndroidRuntimeException && message?.contains("WebView") == true) ||
|
return (this is AndroidRuntimeException && message?.contains("WebView") == true) ||
|
||||||
cause?.isWebViewUnavailable() == true
|
cause?.isWebViewUnavailable() == true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
fun NoSpaceLeftException() = IOException(MSG_NO_SPACE_LEFT)
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.details.ui
|
|
||||||
|
|
||||||
import android.transition.TransitionManager
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.constraintlayout.widget.ConstraintSet
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
import androidx.core.graphics.Insets
|
|
||||||
import androidx.core.view.setMargins
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeDimensionPixelSize
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
|
||||||
import org.koitharu.kotatsu.databinding.ItemTipBinding
|
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
|
|
||||||
class ButtonTip(
|
|
||||||
private val root: ViewGroup,
|
|
||||||
private val insetsDelegate: WindowInsetsDelegate,
|
|
||||||
private val viewModel: DetailsViewModel,
|
|
||||||
) : View.OnClickListener, WindowInsetsDelegate.WindowInsetsListener {
|
|
||||||
|
|
||||||
private var selfBinding = ItemTipBinding.inflate(LayoutInflater.from(root.context), root, false)
|
|
||||||
private val actionBarSize = root.context.getThemeDimensionPixelSize(materialR.attr.actionBarSize)
|
|
||||||
|
|
||||||
init {
|
|
||||||
selfBinding.textView.setText(R.string.details_button_tip)
|
|
||||||
selfBinding.imageViewIcon.setImageResource(R.drawable.ic_tap)
|
|
||||||
selfBinding.root.id = R.id.layout_tip
|
|
||||||
selfBinding.buttonClose.setOnClickListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(v: View?) {
|
|
||||||
remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) {
|
|
||||||
if (root is CoordinatorLayout) {
|
|
||||||
selfBinding.root.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
|
||||||
bottomMargin = topMargin + insets.bottom + insets.top + actionBarSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addToRoot() {
|
|
||||||
val lp: ViewGroup.LayoutParams = when (root) {
|
|
||||||
is CoordinatorLayout -> CoordinatorLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
||||||
).apply {
|
|
||||||
// anchorId = R.id.layout_bottom
|
|
||||||
// anchorGravity = Gravity.TOP
|
|
||||||
gravity = Gravity.BOTTOM
|
|
||||||
setMargins(root.resources.getDimensionPixelOffset(R.dimen.margin_normal))
|
|
||||||
bottomMargin += actionBarSize
|
|
||||||
}
|
|
||||||
|
|
||||||
is ConstraintLayout -> ConstraintLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
||||||
).apply {
|
|
||||||
width = root.resources.getDimensionPixelSize(R.dimen.m3_side_sheet_width)
|
|
||||||
setMargins(root.resources.getDimensionPixelOffset(R.dimen.margin_normal))
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
||||||
}
|
|
||||||
root.addView(selfBinding.root, lp)
|
|
||||||
if (root is ConstraintLayout) {
|
|
||||||
val cs = ConstraintSet()
|
|
||||||
cs.clone(root)
|
|
||||||
cs.connect(R.id.layout_tip, ConstraintSet.TOP, R.id.appbar, ConstraintSet.BOTTOM)
|
|
||||||
cs.connect(R.id.layout_tip, ConstraintSet.START, R.id.card_chapters, ConstraintSet.START)
|
|
||||||
cs.connect(R.id.layout_tip, ConstraintSet.END, R.id.card_chapters, ConstraintSet.END)
|
|
||||||
cs.applyTo(root)
|
|
||||||
}
|
|
||||||
insetsDelegate.addInsetsListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove() {
|
|
||||||
if (root.context.isAnimationsEnabled) {
|
|
||||||
TransitionManager.beginDelayedTransition(root)
|
|
||||||
}
|
|
||||||
insetsDelegate.removeInsetsListener(this)
|
|
||||||
root.removeView(selfBinding.root)
|
|
||||||
viewModel.onButtonTipClosed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,7 @@ import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
|
|||||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||||
import org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService
|
import org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
|
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -79,11 +79,12 @@ class ChaptersFragment :
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
startActivity(
|
startActivity(
|
||||||
IntentBuilder(view.context)
|
ReaderActivity.newIntent(
|
||||||
.manga(viewModel.manga.value ?: return)
|
context = view.context,
|
||||||
.state(ReaderState(item.chapter.id, 0, 0))
|
manga = viewModel.manga.value ?: return,
|
||||||
.build(),
|
state = ReaderState(item.chapter.id, 0, 0),
|
||||||
scaleUpActivityOptionsOf(view),
|
),
|
||||||
|
scaleUpActivityOptionsOf(view).toBundle(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.filterNotNull
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
|
||||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.core.ui.dialog.RecyclerViewAlertDialog
|
import org.koitharu.kotatsu.core.ui.dialog.RecyclerViewAlertDialog
|
||||||
@@ -53,9 +53,8 @@ import org.koitharu.kotatsu.details.ui.model.MangaBranch
|
|||||||
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
|
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
|
||||||
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
|
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
|
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||||
import org.koitharu.kotatsu.reader.ui.thumbnails.PagesThumbnailsSheet
|
import org.koitharu.kotatsu.reader.ui.thumbnails.PagesThumbnailsSheet
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@@ -68,10 +67,9 @@ class DetailsActivity :
|
|||||||
PopupMenu.OnMenuItemClickListener {
|
PopupMenu.OnMenuItemClickListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appShortcutManager: AppShortcutManager
|
lateinit var shortcutsUpdater: ShortcutsUpdater
|
||||||
|
|
||||||
private lateinit var viewBadge: ViewBadge
|
private lateinit var viewBadge: ViewBadge
|
||||||
private var buttonTip: WeakReference<ButtonTip>? = null
|
|
||||||
|
|
||||||
private val viewModel: DetailsViewModel by viewModels()
|
private val viewModel: DetailsViewModel by viewModels()
|
||||||
private lateinit var chaptersMenuProvider: ChaptersMenuProvider
|
private lateinit var chaptersMenuProvider: ChaptersMenuProvider
|
||||||
@@ -124,7 +122,6 @@ class DetailsActivity :
|
|||||||
viewModel.onShowToast.observeEvent(this) {
|
viewModel.onShowToast.observeEvent(this) {
|
||||||
makeSnackbar(getString(it), Snackbar.LENGTH_SHORT).show()
|
makeSnackbar(getString(it), Snackbar.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
viewModel.onShowTip.observeEvent(this) { showTip() }
|
|
||||||
viewModel.historyInfo.observe(this, ::onHistoryChanged)
|
viewModel.historyInfo.observe(this, ::onHistoryChanged)
|
||||||
viewModel.selectedBranch.observe(this) {
|
viewModel.selectedBranch.observe(this) {
|
||||||
viewBinding.toolbarChapters?.subtitle = it
|
viewBinding.toolbarChapters?.subtitle = it
|
||||||
@@ -147,7 +144,7 @@ class DetailsActivity :
|
|||||||
activity = this,
|
activity = this,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
snackbarHost = viewBinding.containerChapters,
|
snackbarHost = viewBinding.containerChapters,
|
||||||
appShortcutManager = appShortcutManager,
|
shortcutsUpdater = shortcutsUpdater,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -165,8 +162,6 @@ class DetailsActivity :
|
|||||||
|
|
||||||
override fun onLongClick(v: View): Boolean = when (v.id) {
|
override fun onLongClick(v: View): Boolean = when (v.id) {
|
||||||
R.id.button_read -> {
|
R.id.button_read -> {
|
||||||
buttonTip?.get()?.remove()
|
|
||||||
buttonTip = null
|
|
||||||
val menu = PopupMenu(v.context, v)
|
val menu = PopupMenu(v.context, v)
|
||||||
menu.inflate(R.menu.popup_read)
|
menu.inflate(R.menu.popup_read)
|
||||||
menu.setOnMenuItemClickListener(this)
|
menu.setOnMenuItemClickListener(this)
|
||||||
@@ -249,7 +244,7 @@ class DetailsActivity :
|
|||||||
bottomMargin = insets.bottom + marginEnd
|
bottomMargin = insets.bottom + marginEnd
|
||||||
}
|
}
|
||||||
viewBinding.dragHandle?.updateLayoutParams<MarginLayoutParams> {
|
viewBinding.dragHandle?.updateLayoutParams<MarginLayoutParams> {
|
||||||
bottomMargin = insets.top
|
bottomMargin = insets.bottom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,11 +296,12 @@ class DetailsActivity :
|
|||||||
snackbar.show()
|
snackbar.show()
|
||||||
} else {
|
} else {
|
||||||
startActivity(
|
startActivity(
|
||||||
IntentBuilder(this)
|
ReaderActivity.newIntent(
|
||||||
.manga(manga)
|
context = this,
|
||||||
.branch(viewModel.selectedBranchValue)
|
manga = manga,
|
||||||
.incognito(isIncognitoMode)
|
branch = viewModel.selectedBranchValue,
|
||||||
.build(),
|
isIncognitoMode = isIncognitoMode,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if (isIncognitoMode) {
|
if (isIncognitoMode) {
|
||||||
Toast.makeText(this, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
|
||||||
@@ -349,16 +345,8 @@ class DetailsActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showTip() {
|
|
||||||
val tip = ButtonTip(viewBinding.root as ViewGroup, insetsDelegate, viewModel)
|
|
||||||
tip.addToRoot()
|
|
||||||
buttonTip = WeakReference(tip)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val TIP_BUTTON = "btn_read"
|
|
||||||
|
|
||||||
fun newIntent(context: Context, manga: Manga): Intent {
|
fun newIntent(context: Context, manga: Manga): Intent {
|
||||||
return Intent(context, DetailsActivity::class.java)
|
return Intent(context, DetailsActivity::class.java)
|
||||||
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
|
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||||
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
|
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
|
||||||
import org.koitharu.kotatsu.core.model.countChaptersByBranch
|
import org.koitharu.kotatsu.core.model.countChaptersByBranch
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
|
||||||
import org.koitharu.kotatsu.core.ui.BaseFragment
|
import org.koitharu.kotatsu.core.ui.BaseFragment
|
||||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
@@ -44,7 +45,6 @@ import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingItemDecoration
|
|||||||
import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
|
import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
|
||||||
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
||||||
import org.koitharu.kotatsu.image.ui.ImageActivity
|
import org.koitharu.kotatsu.image.ui.ImageActivity
|
||||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
|
||||||
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
|
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
@@ -67,7 +67,7 @@ class DetailsFragment :
|
|||||||
lateinit var coil: ImageLoader
|
lateinit var coil: ImageLoader
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var tagHighlighter: ListExtraProvider
|
lateinit var tagHighlighter: MangaTagHighlighter
|
||||||
|
|
||||||
private val viewModel by activityViewModels<DetailsViewModel>()
|
private val viewModel by activityViewModels<DetailsViewModel>()
|
||||||
|
|
||||||
@@ -96,8 +96,8 @@ class DetailsFragment :
|
|||||||
|
|
||||||
override fun onItemClick(item: Bookmark, view: View) {
|
override fun onItemClick(item: Bookmark, view: View) {
|
||||||
startActivity(
|
startActivity(
|
||||||
ReaderActivity.IntentBuilder(view.context).bookmark(item).incognito(true).build(),
|
ReaderActivity.newIntent(view.context, item),
|
||||||
scaleUpActivityOptionsOf(view),
|
scaleUpActivityOptionsOf(view).toBundle(),
|
||||||
)
|
)
|
||||||
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
|
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,7 @@ class DetailsFragment :
|
|||||||
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
|
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
|
||||||
manga.source,
|
manga.source,
|
||||||
),
|
),
|
||||||
scaleUpActivityOptionsOf(v),
|
scaleUpActivityOptionsOf(v).toBundle(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,7 @@ class DetailsFragment :
|
|||||||
manga.tags.map { tag ->
|
manga.tags.map { tag ->
|
||||||
ChipsView.ChipModel(
|
ChipsView.ChipModel(
|
||||||
title = tag.title,
|
title = tag.title,
|
||||||
tint = tagHighlighter.getTagTint(tag),
|
tint = tagHighlighter.getTint(tag),
|
||||||
icon = 0,
|
icon = 0,
|
||||||
data = tag,
|
data = tag,
|
||||||
isCheckable = false,
|
isCheckable = false,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import com.google.android.material.snackbar.Snackbar
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
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.core.os.AppShortcutManager
|
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
|
||||||
import org.koitharu.kotatsu.core.util.ShareHelper
|
import org.koitharu.kotatsu.core.util.ShareHelper
|
||||||
import org.koitharu.kotatsu.details.ui.model.MangaBranch
|
import org.koitharu.kotatsu.details.ui.model.MangaBranch
|
||||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
|
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
|
||||||
@@ -29,7 +29,7 @@ class DetailsMenuProvider(
|
|||||||
private val activity: FragmentActivity,
|
private val activity: FragmentActivity,
|
||||||
private val viewModel: DetailsViewModel,
|
private val viewModel: DetailsViewModel,
|
||||||
private val snackbarHost: View,
|
private val snackbarHost: View,
|
||||||
private val appShortcutManager: AppShortcutManager,
|
private val shortcutsUpdater: ShortcutsUpdater,
|
||||||
) : MenuProvider {
|
) : MenuProvider {
|
||||||
|
|
||||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
@@ -112,7 +112,7 @@ class DetailsMenuProvider(
|
|||||||
R.id.action_shortcut -> {
|
R.id.action_shortcut -> {
|
||||||
viewModel.manga.value?.let {
|
viewModel.manga.value?.let {
|
||||||
activity.lifecycleScope.launch {
|
activity.lifecycleScope.launch {
|
||||||
if (!appShortcutManager.requestPinShortcut(it)) {
|
if (!shortcutsUpdater.requestPinShortcut(it)) {
|
||||||
Snackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT)
|
Snackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
import kotlinx.coroutines.flow.filterNot
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -39,7 +37,6 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
|||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.computeSize
|
import org.koitharu.kotatsu.core.util.ext.computeSize
|
||||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||||
import org.koitharu.kotatsu.core.util.ext.sanitize
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
|
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
|
||||||
import org.koitharu.kotatsu.details.domain.BranchComparator
|
import org.koitharu.kotatsu.details.domain.BranchComparator
|
||||||
import org.koitharu.kotatsu.details.domain.DetailsInteractor
|
import org.koitharu.kotatsu.details.domain.DetailsInteractor
|
||||||
@@ -80,7 +77,6 @@ class DetailsViewModel @Inject constructor(
|
|||||||
private var loadingJob: Job
|
private var loadingJob: Job
|
||||||
|
|
||||||
val onShowToast = MutableEventFlow<Int>()
|
val onShowToast = MutableEventFlow<Int>()
|
||||||
val onShowTip = MutableEventFlow<Unit>()
|
|
||||||
val onDownloadStarted = MutableEventFlow<Unit>()
|
val onDownloadStarted = MutableEventFlow<Unit>()
|
||||||
|
|
||||||
val manga = doubleManga.map { it?.any }
|
val manga = doubleManga.map { it?.any }
|
||||||
@@ -139,7 +135,7 @@ class DetailsViewModel @Inject constructor(
|
|||||||
if (description.isNullOrEmpty()) {
|
if (description.isNullOrEmpty()) {
|
||||||
emit(null)
|
emit(null)
|
||||||
} else {
|
} else {
|
||||||
emit(description.parseAsHtml().filterSpans().sanitize())
|
emit(description.parseAsHtml().filterSpans())
|
||||||
emit(description.parseAsHtml(imageGetter = imageGetter).filterSpans())
|
emit(description.parseAsHtml(imageGetter = imageGetter).filterSpans())
|
||||||
}
|
}
|
||||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), null)
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), null)
|
||||||
@@ -194,12 +190,6 @@ class DetailsViewModel @Inject constructor(
|
|||||||
localStorageChanges
|
localStorageChanges
|
||||||
.collect { onDownloadComplete(it) }
|
.collect { onDownloadComplete(it) }
|
||||||
}
|
}
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
if (settings.isTipEnabled(DetailsActivity.TIP_BUTTON)) {
|
|
||||||
manga.filterNot { it?.chapters.isNullOrEmpty() }.first()
|
|
||||||
onShowTip.call(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reload() {
|
fun reload() {
|
||||||
@@ -286,10 +276,6 @@ class DetailsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onButtonTipClosed() {
|
|
||||||
settings.closeTip(DetailsActivity.TIP_BUTTON)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doLoad() = launchLoadingJob(Dispatchers.Default) {
|
private fun doLoad() = launchLoadingJob(Dispatchers.Default) {
|
||||||
val result = doubleMangaLoadUseCase(intent)
|
val result = doubleMangaLoadUseCase(intent)
|
||||||
val manga = result.requireAny()
|
val manga = result.requireAny()
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
|||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.sanitize
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
||||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||||
import org.koitharu.kotatsu.databinding.SheetScrobblingBinding
|
import org.koitharu.kotatsu.databinding.SheetScrobblingBinding
|
||||||
@@ -110,7 +109,7 @@ class ScrobblingInfoSheet :
|
|||||||
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, null), options)
|
startActivity(ImageActivity.newIntent(v.context, coverUrl, null), options.toBundle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +122,7 @@ class ScrobblingInfoSheet :
|
|||||||
}
|
}
|
||||||
requireViewBinding().textViewTitle.text = scrobbling.title
|
requireViewBinding().textViewTitle.text = scrobbling.title
|
||||||
requireViewBinding().ratingBar.rating = scrobbling.rating * requireViewBinding().ratingBar.numStars
|
requireViewBinding().ratingBar.rating = scrobbling.rating * requireViewBinding().ratingBar.numStars
|
||||||
requireViewBinding().textViewDescription.text = scrobbling.description?.sanitize()
|
requireViewBinding().textViewDescription.text = scrobbling.description
|
||||||
requireViewBinding().spinnerStatus.setSelection(scrobbling.status?.ordinal ?: -1)
|
requireViewBinding().spinnerStatus.setSelection(scrobbling.status?.ordinal ?: -1)
|
||||||
requireViewBinding().imageViewLogo.contentDescription = getString(scrobbling.scrobbler.titleResId)
|
requireViewBinding().imageViewLogo.contentDescription = getString(scrobbling.scrobbler.titleResId)
|
||||||
requireViewBinding().imageViewLogo.setImageResource(scrobbling.scrobbler.iconResId)
|
requireViewBinding().imageViewLogo.setImageResource(scrobbling.scrobbler.iconResId)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.view.MenuItem
|
|||||||
import androidx.core.view.MenuProvider
|
import androidx.core.view.MenuProvider
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
|
||||||
|
|
||||||
class DownloadsMenuProvider(
|
class DownloadsMenuProvider(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -24,10 +23,6 @@ class DownloadsMenuProvider(
|
|||||||
R.id.action_resume -> viewModel.resumeAll()
|
R.id.action_resume -> viewModel.resumeAll()
|
||||||
R.id.action_cancel_all -> confirmCancelAll()
|
R.id.action_cancel_all -> confirmCancelAll()
|
||||||
R.id.action_remove_completed -> confirmRemoveCompleted()
|
R.id.action_remove_completed -> confirmRemoveCompleted()
|
||||||
R.id.action_settings -> {
|
|
||||||
context.startActivity(SettingsActivity.newDownloadsSettingsIntent(context))
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
|||||||
import org.koitharu.kotatsu.parsers.util.format
|
import org.koitharu.kotatsu.parsers.util.format
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
|||||||
import org.koitharu.kotatsu.parsers.util.await
|
import org.koitharu.kotatsu.parsers.util.await
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.suggestions.domain.TagsBlacklist
|
import org.koitharu.kotatsu.suggestions.domain.TagsBlacklist
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ExploreRepository @Inject constructor(
|
class ExploreRepository @Inject constructor(
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class FavouriteCategoriesActivity :
|
|||||||
}
|
}
|
||||||
val intent = FavouritesActivity.newIntent(this, item)
|
val intent = FavouritesActivity.newIntent(this, item)
|
||||||
val options = scaleUpActivityOptionsOf(view)
|
val options = scaleUpActivityOptionsOf(view)
|
||||||
startActivity(intent, options)
|
startActivity(intent, options.toBundle())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
|
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
|||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.showDistinct
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||||
import org.koitharu.kotatsu.databinding.SheetFavoriteCategoriesBinding
|
import org.koitharu.kotatsu.databinding.SheetFavoriteCategoriesBinding
|
||||||
import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter
|
import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter
|
||||||
@@ -82,6 +81,6 @@ class FavouriteCategoriesSheet :
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}.showDistinct(fm, TAG)
|
}.show(fm, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import kotlinx.coroutines.plus
|
|||||||
import org.koitharu.kotatsu.core.model.ids
|
import org.koitharu.kotatsu.core.model.ids
|
||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
|
||||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet.Companion.KEY_MANGA_LIST
|
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet.Companion.KEY_MANGA_LIST
|
||||||
import org.koitharu.kotatsu.favourites.ui.categories.select.model.CategoriesHeaderItem
|
import org.koitharu.kotatsu.favourites.ui.categories.select.model.CategoriesHeaderItem
|
||||||
@@ -26,7 +25,7 @@ class MangaCategoriesViewModel @Inject constructor(
|
|||||||
private val favouritesRepository: FavouritesRepository,
|
private val favouritesRepository: FavouritesRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val manga = savedStateHandle.require<List<ParcelableManga>>(KEY_MANGA_LIST).map { it.manga }
|
private val manga = requireNotNull(savedStateHandle.get<List<ParcelableManga>>(KEY_MANGA_LIST)).map { it.manga }
|
||||||
private val header = CategoriesHeaderItem()
|
private val header = CategoriesHeaderItem()
|
||||||
|
|
||||||
val content: StateFlow<List<ListModel>> = combine(
|
val content: StateFlow<List<ListModel>> = combine(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
@@ -20,6 +21,8 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
|||||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.ARG_CATEGORY_ID
|
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.ARG_CATEGORY_ID
|
||||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||||
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
|
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
||||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||||
@@ -27,16 +30,19 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
|
|||||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class FavouritesListViewModel @Inject constructor(
|
class FavouritesListViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
private val repository: FavouritesRepository,
|
private val repository: FavouritesRepository,
|
||||||
private val listExtraProvider: ListExtraProvider,
|
private val trackingRepository: TrackingRepository,
|
||||||
settings: AppSettings,
|
private val historyRepository: HistoryRepository,
|
||||||
|
private val settings: AppSettings,
|
||||||
|
private val tagHighlighter: MangaTagHighlighter,
|
||||||
downloadScheduler: DownloadWorker.Scheduler,
|
downloadScheduler: DownloadWorker.Scheduler,
|
||||||
) : MangaListViewModel(settings, downloadScheduler) {
|
) : MangaListViewModel(settings, downloadScheduler), ListExtraProvider {
|
||||||
|
|
||||||
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID
|
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID
|
||||||
|
|
||||||
@@ -70,7 +76,7 @@ class FavouritesListViewModel @Inject constructor(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> list.toUi(mode, listExtraProvider)
|
else -> list.toUi(mode, this, tagHighlighter)
|
||||||
}
|
}
|
||||||
}.catch {
|
}.catch {
|
||||||
emit(listOf(it.toErrorState(canRetry = false)))
|
emit(listOf(it.toErrorState(canRetry = false)))
|
||||||
@@ -102,4 +108,20 @@ class FavouritesListViewModel @Inject constructor(
|
|||||||
repository.setCategoryOrder(categoryId, order)
|
repository.setCategoryOrder(categoryId, order)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getCounter(mangaId: Long): Int {
|
||||||
|
return if (settings.isTrackerEnabled) {
|
||||||
|
trackingRepository.getNewChaptersCount(mangaId)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getProgress(mangaId: Long): Float {
|
||||||
|
return if (settings.isReadingIndicatorsEnabled) {
|
||||||
|
historyRepository.getProgress(mangaId)
|
||||||
|
} else {
|
||||||
|
PROGRESS_NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import org.koitharu.kotatsu.parsers.util.SuspendLazy
|
|||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
|
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
|
||||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior
|
|||||||
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
|
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
|
||||||
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.showDistinct
|
|
||||||
import org.koitharu.kotatsu.databinding.SheetFilterBinding
|
import org.koitharu.kotatsu.databinding.SheetFilterBinding
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
|
||||||
@@ -55,6 +54,6 @@ class FilterSheetFragment :
|
|||||||
|
|
||||||
private const val TAG = "FilterBottomSheet"
|
private const val TAG = "FilterBottomSheet"
|
||||||
|
|
||||||
fun show(fm: FragmentManager) = FilterSheetFragment().showDistinct(fm, TAG)
|
fun show(fm: FragmentManager) = FilterSheetFragment().show(fm, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.history.data
|
package org.koitharu.kotatsu.history.data
|
||||||
|
|
||||||
import androidx.room.Dao
|
import androidx.room.*
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.OnConflictStrategy
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Transaction
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||||
@@ -24,10 +20,6 @@ abstract class HistoryDao {
|
|||||||
@Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC")
|
@Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC")
|
||||||
abstract fun observeAll(): Flow<List<HistoryWithManga>>
|
abstract fun observeAll(): Flow<List<HistoryWithManga>>
|
||||||
|
|
||||||
@Transaction
|
|
||||||
@Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC LIMIT :limit")
|
|
||||||
abstract fun observeAll(limit: Int): Flow<List<HistoryWithManga>>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM history WHERE deleted_at = 0)")
|
@Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM history WHERE deleted_at = 0)")
|
||||||
abstract suspend fun findAllManga(): List<MangaEntity>
|
abstract suspend fun findAllManga(): List<MangaEntity>
|
||||||
|
|
||||||
|
|||||||
@@ -51,12 +51,6 @@ class HistoryRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun observeAll(limit: Int): Flow<List<Manga>> {
|
|
||||||
return db.historyDao.observeAll(limit).mapItems {
|
|
||||||
it.manga.toManga(it.tags.toMangaTags())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun observeAllWithHistory(): Flow<List<MangaWithHistory>> {
|
fun observeAllWithHistory(): Flow<List<MangaWithHistory>> {
|
||||||
return db.historyDao.observeAll().mapItems {
|
return db.historyDao.observeAll().mapItems {
|
||||||
MangaWithHistory(
|
MangaWithHistory(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.history.data.HistoryRepository
|
|||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class HistoryUpdateUseCase @Inject constructor(
|
class HistoryUpdateUseCase @Inject constructor(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.onStart
|
|||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||||
@@ -20,8 +21,8 @@ import org.koitharu.kotatsu.core.util.ext.daysDiff
|
|||||||
import org.koitharu.kotatsu.core.util.ext.onFirst
|
import org.koitharu.kotatsu.core.util.ext.onFirst
|
||||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
|
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
||||||
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
|
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
|
||||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
@@ -30,6 +31,7 @@ import org.koitharu.kotatsu.list.ui.model.toErrorState
|
|||||||
import org.koitharu.kotatsu.list.ui.model.toGridModel
|
import org.koitharu.kotatsu.list.ui.model.toGridModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
|
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.toListModel
|
import org.koitharu.kotatsu.list.ui.model.toListModel
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -38,7 +40,8 @@ import javax.inject.Inject
|
|||||||
class HistoryListViewModel @Inject constructor(
|
class HistoryListViewModel @Inject constructor(
|
||||||
private val repository: HistoryRepository,
|
private val repository: HistoryRepository,
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
private val extraProvider: ListExtraProvider,
|
private val trackingRepository: TrackingRepository,
|
||||||
|
private val tagHighlighter: MangaTagHighlighter,
|
||||||
downloadScheduler: DownloadWorker.Scheduler,
|
downloadScheduler: DownloadWorker.Scheduler,
|
||||||
) : MangaListViewModel(settings, downloadScheduler) {
|
) : MangaListViewModel(settings, downloadScheduler) {
|
||||||
|
|
||||||
@@ -103,6 +106,7 @@ class HistoryListViewModel @Inject constructor(
|
|||||||
mode: ListMode,
|
mode: ListMode,
|
||||||
): List<ListModel> {
|
): List<ListModel> {
|
||||||
val result = ArrayList<ListModel>(if (grouped) (list.size * 1.4).toInt() else list.size + 1)
|
val result = ArrayList<ListModel>(if (grouped) (list.size * 1.4).toInt() else list.size + 1)
|
||||||
|
val showPercent = settings.isReadingIndicatorsEnabled
|
||||||
var prevDate: DateTimeAgo? = null
|
var prevDate: DateTimeAgo? = null
|
||||||
for ((manga, history) in list) {
|
for ((manga, history) in list) {
|
||||||
if (grouped) {
|
if (grouped) {
|
||||||
@@ -112,10 +116,16 @@ class HistoryListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
prevDate = date
|
prevDate = date
|
||||||
}
|
}
|
||||||
|
val counter = if (settings.isTrackerEnabled) {
|
||||||
|
trackingRepository.getNewChaptersCount(manga.id)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
val percent = if (showPercent) history.percent else PROGRESS_NONE
|
||||||
result += when (mode) {
|
result += when (mode) {
|
||||||
ListMode.LIST -> manga.toListModel(extraProvider)
|
ListMode.LIST -> manga.toListModel(counter, percent)
|
||||||
ListMode.DETAILED_LIST -> manga.toListDetailedModel(extraProvider)
|
ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter, percent, tagHighlighter)
|
||||||
ListMode.GRID -> manga.toGridModel(extraProvider)
|
ListMode.GRID -> manga.toGridModel(counter, percent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,60 +1,8 @@
|
|||||||
package org.koitharu.kotatsu.list.domain
|
package org.koitharu.kotatsu.list.domain
|
||||||
|
|
||||||
import android.content.Context
|
interface ListExtraProvider {
|
||||||
import androidx.annotation.ColorRes
|
|
||||||
import dagger.Reusable
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
|
||||||
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
|
||||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Reusable
|
suspend fun getCounter(mangaId: Long): Int
|
||||||
class ListExtraProvider @Inject constructor(
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
private val settings: AppSettings,
|
|
||||||
private val trackingRepository: TrackingRepository,
|
|
||||||
private val historyRepository: HistoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val dict by lazy {
|
suspend fun getProgress(mangaId: Long): Float
|
||||||
context.resources.openRawResource(R.raw.tags_redlist).use {
|
}
|
||||||
val set = HashSet<String>()
|
|
||||||
it.bufferedReader().forEachLine { x ->
|
|
||||||
val line = x.trim()
|
|
||||||
if (line.isNotEmpty()) {
|
|
||||||
set.add(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getCounter(mangaId: Long): Int {
|
|
||||||
return if (settings.isTrackerEnabled) {
|
|
||||||
trackingRepository.getNewChaptersCount(mangaId)
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getProgress(mangaId: Long): Float {
|
|
||||||
return if (settings.isReadingIndicatorsEnabled) {
|
|
||||||
historyRepository.getProgress(mangaId)
|
|
||||||
} else {
|
|
||||||
PROGRESS_NONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ColorRes
|
|
||||||
fun getTagTint(tag: MangaTag): Int {
|
|
||||||
return if (tag.title.lowercase() in dict) {
|
|
||||||
R.color.warning
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.koitharu.kotatsu.list.domain
|
||||||
|
|
||||||
|
import dagger.Reusable
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
|
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Reusable
|
||||||
|
class ListExtraProviderImpl @Inject constructor(
|
||||||
|
private val settings: AppSettings,
|
||||||
|
private val trackingRepository: TrackingRepository,
|
||||||
|
private val historyRepository: HistoryRepository,
|
||||||
|
) : ListExtraProvider {
|
||||||
|
|
||||||
|
override suspend fun getCounter(mangaId: Long): Int {
|
||||||
|
return if (settings.isTrackerEnabled) {
|
||||||
|
trackingRepository.getNewChaptersCount(mangaId)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getProgress(mangaId: Long): Float {
|
||||||
|
return if (settings.isReadingIndicatorsEnabled) {
|
||||||
|
historyRepository.getProgress(mangaId)
|
||||||
|
} else {
|
||||||
|
PROGRESS_NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
|||||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||||
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
||||||
import org.koitharu.kotatsu.core.util.ext.setValueRounded
|
import org.koitharu.kotatsu.core.util.ext.setValueRounded
|
||||||
import org.koitharu.kotatsu.core.util.ext.showDistinct
|
|
||||||
import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter
|
import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter
|
||||||
import org.koitharu.kotatsu.databinding.DialogListModeBinding
|
import org.koitharu.kotatsu.databinding.DialogListModeBinding
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -73,6 +72,6 @@ class ListModeBottomSheet :
|
|||||||
|
|
||||||
private const val TAG = "ListModeSelectDialog"
|
private const val TAG = "ListModeSelectDialog"
|
||||||
|
|
||||||
fun show(fm: FragmentManager) = ListModeBottomSheet().showDistinct(fm, TAG)
|
fun show(fm: FragmentManager) = ListModeBottomSheet().show(fm, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
|||||||
import org.koitharu.kotatsu.core.util.ShareHelper
|
import org.koitharu.kotatsu.core.util.ShareHelper
|
||||||
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
||||||
import org.koitharu.kotatsu.core.util.ext.clearItemDecorations
|
import org.koitharu.kotatsu.core.util.ext.clearItemDecorations
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
import org.koitharu.kotatsu.core.util.ext.measureHeight
|
import org.koitharu.kotatsu.core.util.ext.measureHeight
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
@@ -55,7 +56,7 @@ import org.koitharu.kotatsu.main.ui.MainActivity
|
|||||||
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
|
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
|
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -113,6 +114,8 @@ abstract class MangaListFragment :
|
|||||||
fastScroller.setFastScrollListener(this@MangaListFragment)
|
fastScroller.setFastScrollListener(this@MangaListFragment)
|
||||||
}
|
}
|
||||||
with(binding.swipeRefreshLayout) {
|
with(binding.swipeRefreshLayout) {
|
||||||
|
setProgressBackgroundColorSchemeColor(context.getThemeColor(com.google.android.material.R.attr.colorPrimary))
|
||||||
|
setColorSchemeColors(context.getThemeColor(com.google.android.material.R.attr.colorOnPrimary))
|
||||||
setOnRefreshListener(this@MangaListFragment)
|
setOnRefreshListener(this@MangaListFragment)
|
||||||
isEnabled = isSwipeRefreshEnabled
|
isEnabled = isSwipeRefreshEnabled
|
||||||
}
|
}
|
||||||
@@ -148,8 +151,8 @@ abstract class MangaListFragment :
|
|||||||
|
|
||||||
override fun onReadClick(manga: Manga, view: View) {
|
override fun onReadClick(manga: Manga, view: View) {
|
||||||
if (selectionController?.onItemClick(manga.id) != true) {
|
if (selectionController?.onItemClick(manga.id) != true) {
|
||||||
val intent = IntentBuilder(view.context).manga(manga).build()
|
val intent = ReaderActivity.newIntent(context ?: return, manga)
|
||||||
startActivity(intent, scaleUpActivityOptionsOf(view))
|
startActivity(intent, scaleUpActivityOptionsOf(view).toBundle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.list.ui.model
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
|
||||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||||
import org.koitharu.kotatsu.core.util.ext.ifZero
|
import org.koitharu.kotatsu.core.util.ext.ifZero
|
||||||
@@ -13,31 +14,34 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
suspend fun Manga.toListModel(
|
fun Manga.toListModel(
|
||||||
extraProvider: ListExtraProvider?
|
counter: Int,
|
||||||
|
progress: Float,
|
||||||
) = MangaListModel(
|
) = MangaListModel(
|
||||||
id = id,
|
id = id,
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = tags.joinToString(", ") { it.title },
|
subtitle = tags.joinToString(", ") { it.title },
|
||||||
coverUrl = coverUrl,
|
coverUrl = coverUrl,
|
||||||
manga = this,
|
manga = this,
|
||||||
counter = extraProvider?.getCounter(id) ?: 0,
|
counter = counter,
|
||||||
progress = extraProvider?.getProgress(id) ?: PROGRESS_NONE,
|
progress = progress,
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun Manga.toListDetailedModel(
|
fun Manga.toListDetailedModel(
|
||||||
extraProvider: ListExtraProvider?,
|
counter: Int,
|
||||||
|
progress: Float,
|
||||||
|
tagHighlighter: MangaTagHighlighter?,
|
||||||
) = MangaListDetailedModel(
|
) = MangaListDetailedModel(
|
||||||
id = id,
|
id = id,
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = altTitle,
|
subtitle = altTitle,
|
||||||
coverUrl = coverUrl,
|
coverUrl = coverUrl,
|
||||||
manga = this,
|
manga = this,
|
||||||
counter = extraProvider?.getCounter(id) ?: 0,
|
counter = counter,
|
||||||
progress = extraProvider?.getProgress(id) ?: PROGRESS_NONE,
|
progress = progress,
|
||||||
tags = tags.map {
|
tags = tags.map {
|
||||||
ChipsView.ChipModel(
|
ChipsView.ChipModel(
|
||||||
tint = extraProvider?.getTagTint(it) ?: 0,
|
tint = tagHighlighter?.getTint(it) ?: 0,
|
||||||
title = it.title,
|
title = it.title,
|
||||||
icon = 0,
|
icon = 0,
|
||||||
isCheckable = false,
|
isCheckable = false,
|
||||||
@@ -47,30 +51,53 @@ suspend fun Manga.toListDetailedModel(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun Manga.toGridModel(
|
fun Manga.toGridModel(counter: Int, progress: Float) = MangaGridModel(
|
||||||
extraProvider: ListExtraProvider?,
|
|
||||||
) = MangaGridModel(
|
|
||||||
id = id,
|
id = id,
|
||||||
title = title,
|
title = title,
|
||||||
coverUrl = coverUrl,
|
coverUrl = coverUrl,
|
||||||
manga = this,
|
manga = this,
|
||||||
counter = extraProvider?.getCounter(id) ?: 0,
|
counter = counter,
|
||||||
progress = extraProvider?.getProgress(id) ?: PROGRESS_NONE,
|
progress = progress,
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun List<Manga>.toUi(
|
suspend fun List<Manga>.toUi(
|
||||||
mode: ListMode,
|
mode: ListMode,
|
||||||
extraProvider: ListExtraProvider,
|
extraProvider: ListExtraProvider,
|
||||||
): List<MangaItemModel> = toUi(ArrayList(size), mode, extraProvider)
|
tagHighlighter: MangaTagHighlighter?,
|
||||||
|
): List<MangaItemModel> = toUi(ArrayList(size), mode, extraProvider, tagHighlighter)
|
||||||
|
|
||||||
|
fun List<Manga>.toUi(
|
||||||
|
mode: ListMode,
|
||||||
|
tagHighlighter: MangaTagHighlighter?,
|
||||||
|
): List<MangaItemModel> = toUi(ArrayList(size), mode, tagHighlighter)
|
||||||
|
|
||||||
|
fun <C : MutableCollection<in MangaItemModel>> List<Manga>.toUi(
|
||||||
|
destination: C,
|
||||||
|
mode: ListMode,
|
||||||
|
tagHighlighter: MangaTagHighlighter?,
|
||||||
|
): C = when (mode) {
|
||||||
|
ListMode.LIST -> mapTo(destination) { it.toListModel(0, PROGRESS_NONE) }
|
||||||
|
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(0, PROGRESS_NONE, tagHighlighter) }
|
||||||
|
ListMode.GRID -> mapTo(destination) { it.toGridModel(0, PROGRESS_NONE) }
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun <C : MutableCollection<in MangaItemModel>> List<Manga>.toUi(
|
suspend fun <C : MutableCollection<in MangaItemModel>> List<Manga>.toUi(
|
||||||
destination: C,
|
destination: C,
|
||||||
mode: ListMode,
|
mode: ListMode,
|
||||||
extraProvider: ListExtraProvider,
|
extraProvider: ListExtraProvider,
|
||||||
|
tagHighlighter: MangaTagHighlighter?,
|
||||||
): C = when (mode) {
|
): C = when (mode) {
|
||||||
ListMode.LIST -> mapTo(destination) { it.toListModel(extraProvider) }
|
ListMode.LIST -> mapTo(destination) {
|
||||||
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(extraProvider) }
|
it.toListModel(extraProvider.getCounter(it.id), extraProvider.getProgress(it.id))
|
||||||
ListMode.GRID -> mapTo(destination) { it.toGridModel(extraProvider) }
|
}
|
||||||
|
|
||||||
|
ListMode.DETAILED_LIST -> mapTo(destination) {
|
||||||
|
it.toListDetailedModel(extraProvider.getCounter(it.id), extraProvider.getProgress(it.id), tagHighlighter)
|
||||||
|
}
|
||||||
|
|
||||||
|
ListMode.GRID -> mapTo(destination) {
|
||||||
|
it.toGridModel(extraProvider.getCounter(it.id), extraProvider.getProgress(it.id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState(
|
fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState(
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ 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.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.local.data
|
package org.koitharu.kotatsu.local.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.StatFs
|
|
||||||
import com.tomclaw.cache.DiskLruCache
|
import com.tomclaw.cache.DiskLruCache
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -18,7 +17,7 @@ import org.koitharu.kotatsu.core.util.ext.takeIfWriteable
|
|||||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||||
import org.koitharu.kotatsu.parsers.util.SuspendLazy
|
import org.koitharu.kotatsu.parsers.util.SuspendLazy
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -34,8 +33,7 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
}
|
}
|
||||||
private val lruCache = SuspendLazy {
|
private val lruCache = SuspendLazy {
|
||||||
val dir = cacheDir.get()
|
val dir = cacheDir.get()
|
||||||
val availableSize = (getAvailableSize() * 0.8).toLong()
|
val size = FileSize.MEGABYTES.convert(200, FileSize.BYTES)
|
||||||
val size = SIZE_DEFAULT.coerceAtMost(availableSize).coerceAtLeast(SIZE_MIN)
|
|
||||||
runCatchingCancellable {
|
runCatchingCancellable {
|
||||||
DiskLruCache.create(dir, size)
|
DiskLruCache.create(dir, size)
|
||||||
}.recoverCatching { error ->
|
}.recoverCatching { error ->
|
||||||
@@ -65,20 +63,4 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getAvailableSize(): Long = runCatchingCancellable {
|
|
||||||
val statFs = StatFs(cacheDir.get().absolutePath)
|
|
||||||
statFs.availableBytes
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTraceDebug()
|
|
||||||
}.getOrDefault(SIZE_DEFAULT)
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
|
|
||||||
val SIZE_MIN
|
|
||||||
get() = FileSize.MEGABYTES.convert(20, FileSize.BYTES)
|
|
||||||
|
|
||||||
val SIZE_DEFAULT
|
|
||||||
get() = FileSize.MEGABYTES.convert(200, FileSize.BYTES)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import org.koitharu.kotatsu.history.data.HistoryRepository
|
|||||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.AlertDialogFragment
|
import org.koitharu.kotatsu.core.ui.AlertDialogFragment
|
||||||
import org.koitharu.kotatsu.databinding.DialogImportBinding
|
import org.koitharu.kotatsu.databinding.DialogImportBinding
|
||||||
|
import org.koitharu.kotatsu.settings.backup.BackupDialogFragment
|
||||||
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
|
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
|
||||||
|
|
||||||
class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.OnClickListener {
|
class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.OnClickListener {
|
||||||
@@ -63,10 +64,9 @@ class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.On
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreBackup(uri: Uri?) {
|
private fun restoreBackup(uri: Uri?) {
|
||||||
if (uri != null) {
|
RestoreDialogFragment.newInstance(uri ?: return)
|
||||||
RestoreDialogFragment.show(parentFragmentManager, uri)
|
.show(parentFragmentManager, BackupDialogFragment.TAG)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
@@ -24,6 +25,7 @@ class LocalListViewModel @Inject constructor(
|
|||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
mangaRepositoryFactory: MangaRepository.Factory,
|
mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
filter: FilterCoordinator,
|
filter: FilterCoordinator,
|
||||||
|
tagHighlighter: MangaTagHighlighter,
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
downloadScheduler: DownloadWorker.Scheduler,
|
downloadScheduler: DownloadWorker.Scheduler,
|
||||||
listExtraProvider: ListExtraProvider,
|
listExtraProvider: ListExtraProvider,
|
||||||
@@ -33,6 +35,7 @@ class LocalListViewModel @Inject constructor(
|
|||||||
savedStateHandle,
|
savedStateHandle,
|
||||||
mangaRepositoryFactory,
|
mangaRepositoryFactory,
|
||||||
filter,
|
filter,
|
||||||
|
tagHighlighter,
|
||||||
settings,
|
settings,
|
||||||
listExtraProvider,
|
listExtraProvider,
|
||||||
downloadScheduler,
|
downloadScheduler,
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
|
|||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
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.reader.ui.ReaderActivity.IntentBuilder
|
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
||||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
|
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
|
||||||
@@ -126,10 +126,8 @@ class MainActivity :
|
|||||||
viewBinding.navRail?.headerView?.setOnClickListener(this)
|
viewBinding.navRail?.headerView?.setOnClickListener(this)
|
||||||
viewBinding.searchView.isVoiceSearchEnabled = voiceInputLauncher.resolve(this, null) != null
|
viewBinding.searchView.isVoiceSearchEnabled = voiceInputLauncher.resolve(this, null) != null
|
||||||
|
|
||||||
navigationDelegate = MainNavigationDelegate(
|
navigationDelegate =
|
||||||
navBar = checkNotNull(bottomNav ?: viewBinding.navRail),
|
MainNavigationDelegate(checkNotNull(bottomNav ?: viewBinding.navRail), supportFragmentManager)
|
||||||
fragmentManager = supportFragmentManager,
|
|
||||||
)
|
|
||||||
navigationDelegate.addOnFragmentChangedListener(this)
|
navigationDelegate.addOnFragmentChangedListener(this)
|
||||||
navigationDelegate.onCreate()
|
navigationDelegate.onCreate()
|
||||||
|
|
||||||
@@ -158,8 +156,6 @@ class MainActivity :
|
|||||||
override fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {
|
override fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {
|
||||||
adjustFabVisibility(topFragment = fragment)
|
adjustFabVisibility(topFragment = fragment)
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
actionModeDelegate.finishActionMode()
|
|
||||||
closeSearchCallback.handleOnBackPressed()
|
|
||||||
viewBinding.appbar.setExpanded(true)
|
viewBinding.appbar.setExpanded(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,21 +243,21 @@ class MainActivity :
|
|||||||
override fun onSupportActionModeStarted(mode: ActionMode) {
|
override fun onSupportActionModeStarted(mode: ActionMode) {
|
||||||
super.onSupportActionModeStarted(mode)
|
super.onSupportActionModeStarted(mode)
|
||||||
adjustFabVisibility()
|
adjustFabVisibility()
|
||||||
bottomNav?.hide()
|
showNav(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportActionModeFinished(mode: ActionMode) {
|
override fun onSupportActionModeFinished(mode: ActionMode) {
|
||||||
super.onSupportActionModeFinished(mode)
|
super.onSupportActionModeFinished(mode)
|
||||||
adjustFabVisibility()
|
adjustFabVisibility()
|
||||||
bottomNav?.show()
|
showNav(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onOpenReader(manga: Manga) {
|
private fun onOpenReader(manga: Manga) {
|
||||||
val fab = viewBinding.fab ?: viewBinding.navRail?.headerView
|
val fab = viewBinding.fab ?: viewBinding.navRail?.headerView
|
||||||
val options = fab?.let {
|
val options = fab?.let {
|
||||||
scaleUpActivityOptionsOf(it)
|
scaleUpActivityOptionsOf(it).toBundle()
|
||||||
}
|
}
|
||||||
startActivity(IntentBuilder(this).manga(manga).build(), options)
|
startActivity(ReaderActivity.newIntent(this, manga), options)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onCountersChanged(counters: SparseIntArray) {
|
private fun onCountersChanged(counters: SparseIntArray) {
|
||||||
@@ -305,6 +301,17 @@ class MainActivity :
|
|||||||
closeSearchCallback.isEnabled = false
|
closeSearchCallback.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showNav(visible: Boolean) {
|
||||||
|
bottomNav?.run {
|
||||||
|
if (visible) {
|
||||||
|
show()
|
||||||
|
} else {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewBinding.navRail?.isVisible = visible
|
||||||
|
}
|
||||||
|
|
||||||
private fun isSearchOpened(): Boolean {
|
private fun isSearchOpened(): Boolean {
|
||||||
return supportFragmentManager.findFragmentByTag(TAG_SEARCH) != null
|
return supportFragmentManager.findFragmentByTag(TAG_SEARCH) != null
|
||||||
}
|
}
|
||||||
@@ -333,7 +340,7 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun adjustFabVisibility(
|
private fun adjustFabVisibility(
|
||||||
isResumeEnabled: Boolean = viewModel.isResumeEnabled.value,
|
isResumeEnabled: Boolean = viewModel.isResumeEnabled.value == true,
|
||||||
topFragment: Fragment? = navigationDelegate.primaryFragment,
|
topFragment: Fragment? = navigationDelegate.primaryFragment,
|
||||||
isSearchOpened: Boolean = isSearchOpened(),
|
isSearchOpened: Boolean = isSearchOpened(),
|
||||||
) {
|
) {
|
||||||
@@ -376,7 +383,7 @@ class MainActivity :
|
|||||||
supportActionBar?.setHomeAsUpIndicator(
|
supportActionBar?.setHomeAsUpIndicator(
|
||||||
if (isOpened) materialR.drawable.abc_ic_ab_back_material else materialR.drawable.abc_ic_search_api_material,
|
if (isOpened) materialR.drawable.abc_ic_ab_back_material else materialR.drawable.abc_ic_search_api_material,
|
||||||
)
|
)
|
||||||
bottomNav?.showOrHide(!isOpened)
|
showNav(!isOpened)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestNotificationsPermission() {
|
private fun requestNotificationsPermission() {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ 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.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import dagger.hilt.android.lifecycle.RetainedLifecycle
|
|||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -33,7 +32,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
|||||||
import org.koitharu.kotatsu.core.util.FileSize
|
import org.koitharu.kotatsu.core.util.FileSize
|
||||||
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||||
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
||||||
import org.koitharu.kotatsu.core.util.ext.isNotEmpty
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.ramAvailable
|
import org.koitharu.kotatsu.core.util.ext.ramAvailable
|
||||||
import org.koitharu.kotatsu.core.util.ext.withProgress
|
import org.koitharu.kotatsu.core.util.ext.withProgress
|
||||||
import org.koitharu.kotatsu.core.util.progress.ProgressDeferred
|
import org.koitharu.kotatsu.core.util.progress.ProgressDeferred
|
||||||
@@ -42,7 +40,7 @@ import org.koitharu.kotatsu.local.data.PagesCache
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
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.reader.ui.pager.ReaderPage
|
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
@@ -105,7 +103,7 @@ class PageLoader @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<File, Float> {
|
fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<File, Float> {
|
||||||
var task = tasks[page.id]?.takeIf { it.isValid() }
|
var task = tasks[page.id]
|
||||||
if (force) {
|
if (force) {
|
||||||
task?.cancel()
|
task?.cancel()
|
||||||
} else if (task?.isCancelled == false) {
|
} else if (task?.isCancelled == false) {
|
||||||
@@ -218,15 +216,6 @@ class PageLoader @Inject constructor(
|
|||||||
return context.ramAvailable <= FileSize.MEGABYTES.convert(PREFETCH_MIN_RAM_MB, FileSize.BYTES)
|
return context.ramAvailable <= FileSize.MEGABYTES.convert(PREFETCH_MIN_RAM_MB, FileSize.BYTES)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Deferred<File>.isValid(): Boolean {
|
|
||||||
return if (isCompleted) {
|
|
||||||
val file = getCompleted()
|
|
||||||
file.exists() && file.isNotEmpty()
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
|
private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
|
||||||
CoroutineExceptionHandler {
|
CoroutineExceptionHandler {
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
|||||||
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
||||||
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
||||||
import org.koitharu.kotatsu.core.util.ext.getParcelableCompat
|
import org.koitharu.kotatsu.core.util.ext.getParcelableCompat
|
||||||
import org.koitharu.kotatsu.core.util.ext.showDistinct
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||||
import org.koitharu.kotatsu.databinding.SheetChaptersBinding
|
import org.koitharu.kotatsu.databinding.SheetChaptersBinding
|
||||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
||||||
@@ -88,6 +87,6 @@ class ChaptersSheet : BaseAdaptiveSheet<SheetChaptersBinding>(), OnListItemClick
|
|||||||
) = ChaptersSheet().withArgs(2) {
|
) = ChaptersSheet().withArgs(2) {
|
||||||
putParcelable(ARG_CHAPTERS, ParcelableMangaChapters(chapters))
|
putParcelable(ARG_CHAPTERS, ParcelableMangaChapters(chapters))
|
||||||
putLong(ARG_CURRENT_ID, currentId)
|
putLong(ARG_CURRENT_ID, currentId)
|
||||||
}.showDistinct(fm, TAG)
|
}.show(fm, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -398,44 +398,6 @@ class ReaderActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IntentBuilder(context: Context) {
|
|
||||||
|
|
||||||
private val intent = Intent(context, ReaderActivity::class.java)
|
|
||||||
.setAction(ACTION_MANGA_READ)
|
|
||||||
|
|
||||||
fun manga(manga: Manga) = apply {
|
|
||||||
intent.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mangaId(mangaId: Long) = apply {
|
|
||||||
intent.putExtra(MangaIntent.KEY_ID, mangaId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun incognito(incognito: Boolean) = apply {
|
|
||||||
intent.putExtra(EXTRA_INCOGNITO, incognito)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun branch(branch: String?) = apply {
|
|
||||||
intent.putExtra(EXTRA_BRANCH, branch)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun state(state: ReaderState?) = apply {
|
|
||||||
intent.putExtra(EXTRA_STATE, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bookmark(bookmark: Bookmark) = manga(
|
|
||||||
bookmark.manga,
|
|
||||||
).state(
|
|
||||||
ReaderState(
|
|
||||||
chapterId = bookmark.chapterId,
|
|
||||||
page = bookmark.page,
|
|
||||||
scroll = bookmark.scroll,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
fun build() = intent
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA"
|
const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA"
|
||||||
@@ -443,5 +405,38 @@ class ReaderActivity :
|
|||||||
const val EXTRA_BRANCH = "branch"
|
const val EXTRA_BRANCH = "branch"
|
||||||
const val EXTRA_INCOGNITO = "incognito"
|
const val EXTRA_INCOGNITO = "incognito"
|
||||||
private const val TOAST_DURATION = 1500L
|
private const val TOAST_DURATION = 1500L
|
||||||
|
|
||||||
|
fun newIntent(context: Context, manga: Manga): Intent {
|
||||||
|
return Intent(context, ReaderActivity::class.java)
|
||||||
|
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newIntent(context: Context, manga: Manga, branch: String?, isIncognitoMode: Boolean): Intent {
|
||||||
|
return Intent(context, ReaderActivity::class.java)
|
||||||
|
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
|
||||||
|
.putExtra(EXTRA_BRANCH, branch)
|
||||||
|
.putExtra(EXTRA_INCOGNITO, isIncognitoMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newIntent(context: Context, manga: Manga, state: ReaderState?): Intent {
|
||||||
|
return Intent(context, ReaderActivity::class.java)
|
||||||
|
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
|
||||||
|
.putExtra(EXTRA_STATE, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newIntent(context: Context, bookmark: Bookmark): Intent {
|
||||||
|
val state = ReaderState(
|
||||||
|
chapterId = bookmark.chapterId,
|
||||||
|
page = bookmark.page,
|
||||||
|
scroll = bookmark.scroll,
|
||||||
|
)
|
||||||
|
return newIntent(context, bookmark.manga, state)
|
||||||
|
.putExtra(EXTRA_INCOGNITO, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newIntent(context: Context, mangaId: Long): Intent {
|
||||||
|
return Intent(context, ReaderActivity::class.java)
|
||||||
|
.putExtra(MangaIntent.KEY_ID, mangaId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import org.koitharu.kotatsu.core.util.ext.measureDimension
|
|||||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
||||||
import org.koitharu.kotatsu.parsers.util.format
|
import org.koitharu.kotatsu.parsers.util.format
|
||||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
|
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import kotlinx.coroutines.plus
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
|
||||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
@@ -42,7 +42,6 @@ import org.koitharu.kotatsu.core.ui.BaseViewModel
|
|||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||||
import org.koitharu.kotatsu.details.domain.DoubleMangaLoadUseCase
|
import org.koitharu.kotatsu.details.domain.DoubleMangaLoadUseCase
|
||||||
import org.koitharu.kotatsu.details.domain.model.DoubleManga
|
import org.koitharu.kotatsu.details.domain.model.DoubleManga
|
||||||
@@ -57,6 +56,7 @@ import org.koitharu.kotatsu.reader.domain.DetectReaderModeUseCase
|
|||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
|
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
|
||||||
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class ReaderViewModel @Inject constructor(
|
|||||||
private val pageSaveHelper: PageSaveHelper,
|
private val pageSaveHelper: PageSaveHelper,
|
||||||
private val pageLoader: PageLoader,
|
private val pageLoader: PageLoader,
|
||||||
private val chaptersLoader: ChaptersLoader,
|
private val chaptersLoader: ChaptersLoader,
|
||||||
private val appShortcutManager: AppShortcutManager,
|
private val shortcutsUpdater: ShortcutsUpdater,
|
||||||
private val doubleMangaLoadUseCase: DoubleMangaLoadUseCase,
|
private val doubleMangaLoadUseCase: DoubleMangaLoadUseCase,
|
||||||
private val historyUpdateUseCase: HistoryUpdateUseCase,
|
private val historyUpdateUseCase: HistoryUpdateUseCase,
|
||||||
private val detectReaderModeUseCase: DetectReaderModeUseCase,
|
private val detectReaderModeUseCase: DetectReaderModeUseCase,
|
||||||
@@ -155,7 +155,7 @@ class ReaderViewModel @Inject constructor(
|
|||||||
}.launchIn(viewModelScope + Dispatchers.Default)
|
}.launchIn(viewModelScope + Dispatchers.Default)
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
val mangaId = mangaFlow.filterNotNull().first().id
|
val mangaId = mangaFlow.filterNotNull().first().id
|
||||||
appShortcutManager.notifyMangaOpened(mangaId)
|
shortcutsUpdater.notifyMangaOpened(mangaId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +288,7 @@ class ReaderViewModel @Inject constructor(
|
|||||||
chapterId = state.chapterId,
|
chapterId = state.chapterId,
|
||||||
page = state.page,
|
page = state.page,
|
||||||
scroll = state.scroll,
|
scroll = state.scroll,
|
||||||
imageUrl = page.preview.ifNullOrEmpty { page.url },
|
imageUrl = page.preview.ifNullOrEmpty { pageLoader.getPageUrl(page) },
|
||||||
createdAt = Date(),
|
createdAt = Date(),
|
||||||
percent = computePercent(state.chapterId, state.page),
|
percent = computePercent(state.chapterId, state.page),
|
||||||
)
|
)
|
||||||
@@ -312,10 +312,8 @@ class ReaderViewModel @Inject constructor(
|
|||||||
|
|
||||||
private fun loadImpl() {
|
private fun loadImpl() {
|
||||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||||
var manga = DoubleManga(
|
var manga =
|
||||||
dataRepository.resolveIntent(intent)
|
DoubleManga(dataRepository.resolveIntent(intent) ?: throw NotFoundException("Cannot find manga", ""))
|
||||||
?: throw NotFoundException("Cannot find manga", ""),
|
|
||||||
)
|
|
||||||
mangaData.value = manga
|
mangaData.value = manga
|
||||||
manga = doubleMangaLoadUseCase(intent)
|
manga = doubleMangaLoadUseCase(intent)
|
||||||
chaptersLoader.init(manga)
|
chaptersLoader.init(manga)
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ class ColorFilterConfigActivity :
|
|||||||
|
|
||||||
viewModel.colorFilter.observe(this, this::onColorFilterChanged)
|
viewModel.colorFilter.observe(this, this::onColorFilterChanged)
|
||||||
viewModel.isLoading.observe(this, this::onLoadingChanged)
|
viewModel.isLoading.observe(this, this::onLoadingChanged)
|
||||||
|
viewModel.preview.observe(this, this::onPreviewChanged)
|
||||||
viewModel.onDismiss.observeEvent(this) {
|
viewModel.onDismiss.observeEvent(this) {
|
||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
}
|
}
|
||||||
loadPreview(viewModel.preview)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
|
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
|
||||||
@@ -114,13 +114,13 @@ class ColorFilterConfigActivity :
|
|||||||
viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()
|
viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadPreview(page: MangaPage) {
|
private fun onPreviewChanged(preview: MangaPage?) {
|
||||||
val data: Any = page.preview?.takeUnless { it.isEmpty() } ?: page
|
if (preview == null) return
|
||||||
ImageRequest.Builder(this@ColorFilterConfigActivity)
|
ImageRequest.Builder(this@ColorFilterConfigActivity)
|
||||||
.data(data)
|
.data(preview)
|
||||||
.scale(Scale.FILL)
|
.scale(Scale.FILL)
|
||||||
.decodeRegion()
|
.decodeRegion()
|
||||||
.tag(page.source)
|
.tag(preview.source)
|
||||||
.indicator(listOf(viewBinding.progressBefore, viewBinding.progressAfter))
|
.indicator(listOf(viewBinding.progressBefore, viewBinding.progressAfter))
|
||||||
.error(R.drawable.ic_error_placeholder)
|
.error(R.drawable.ic_error_placeholder)
|
||||||
.size(ViewSizeResolver(viewBinding.imageViewBefore))
|
.size(ViewSizeResolver(viewBinding.imageViewBefore))
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPages
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPages
|
||||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
||||||
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity.Companion.EXTRA_MANGA
|
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity.Companion.EXTRA_MANGA
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -18,24 +19,38 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ColorFilterConfigViewModel @Inject constructor(
|
class ColorFilterConfigViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
private val mangaDataRepository: MangaDataRepository,
|
private val mangaDataRepository: MangaDataRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val manga = savedStateHandle.require<ParcelableManga>(EXTRA_MANGA).manga
|
private val manga = checkNotNull(savedStateHandle.get<ParcelableManga>(EXTRA_MANGA)?.manga)
|
||||||
|
|
||||||
private var initialColorFilter: ReaderColorFilter? = null
|
private var initialColorFilter: ReaderColorFilter? = null
|
||||||
val colorFilter = MutableStateFlow<ReaderColorFilter?>(null)
|
val colorFilter = MutableStateFlow<ReaderColorFilter?>(null)
|
||||||
val onDismiss = MutableEventFlow<Unit>()
|
val onDismiss = MutableEventFlow<Unit>()
|
||||||
val preview = savedStateHandle.require<ParcelableMangaPages>(ColorFilterConfigActivity.EXTRA_PAGES).pages.first()
|
val preview = MutableStateFlow<MangaPage?>(null)
|
||||||
|
|
||||||
val isChanged: Boolean
|
val isChanged: Boolean
|
||||||
get() = colorFilter.value != initialColorFilter
|
get() = colorFilter.value != initialColorFilter
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
val page = checkNotNull(
|
||||||
|
savedStateHandle.get<ParcelableMangaPages>(ColorFilterConfigActivity.EXTRA_PAGES)?.pages?.firstOrNull(),
|
||||||
|
)
|
||||||
launchLoadingJob {
|
launchLoadingJob {
|
||||||
initialColorFilter = mangaDataRepository.getColorFilter(manga.id)
|
initialColorFilter = mangaDataRepository.getColorFilter(manga.id)
|
||||||
colorFilter.value = initialColorFilter
|
colorFilter.value = initialColorFilter
|
||||||
}
|
}
|
||||||
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
|
val repository = mangaRepositoryFactory.create(page.source)
|
||||||
|
val url = repository.getPageUrl(page)
|
||||||
|
preview.value = MangaPage(
|
||||||
|
id = page.id,
|
||||||
|
url = url,
|
||||||
|
preview = page.preview,
|
||||||
|
source = page.source,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBrightness(brightness: Float) {
|
fun setBrightness(brightness: Float) {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
|||||||
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
||||||
import org.koitharu.kotatsu.core.util.ScreenOrientationHelper
|
import org.koitharu.kotatsu.core.util.ScreenOrientationHelper
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.showDistinct
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||||
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
|
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
|
||||||
@@ -193,6 +192,6 @@ class ReaderConfigSheet :
|
|||||||
|
|
||||||
fun show(fm: FragmentManager, mode: ReaderMode) = ReaderConfigSheet().withArgs(1) {
|
fun show(fm: FragmentManager, mode: ReaderMode) = ReaderConfigSheet().withArgs(1) {
|
||||||
putInt(ARG_MODE, mode.id)
|
putInt(ARG_MODE, mode.id)
|
||||||
}.showDistinct(fm, TAG)
|
}.show(fm, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,9 @@ abstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>() {
|
|||||||
protected val viewModel by activityViewModels<ReaderViewModel>()
|
protected val viewModel by activityViewModels<ReaderViewModel>()
|
||||||
private var stateToSave: ReaderState? = null
|
private var stateToSave: ReaderState? = null
|
||||||
|
|
||||||
protected var readerAdapter: BaseReaderAdapter<*>? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
override fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) {
|
override fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) {
|
||||||
super.onViewBindingCreated(binding, savedInstanceState)
|
super.onViewBindingCreated(binding, savedInstanceState)
|
||||||
var restoredState = savedInstanceState?.getParcelableCompat<ReaderState>(KEY_STATE)
|
var restoredState = savedInstanceState?.getParcelableCompat<ReaderState>(KEY_STATE)
|
||||||
readerAdapter = onCreateAdapter()
|
|
||||||
|
|
||||||
viewModel.content.observe(viewLifecycleOwner) {
|
viewModel.content.observe(viewLifecycleOwner) {
|
||||||
onPagesChanged(it.pages, restoredState ?: it.state)
|
onPagesChanged(it.pages, restoredState ?: it.state)
|
||||||
@@ -38,7 +34,6 @@ abstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>() {
|
|||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
stateToSave = getCurrentState()
|
stateToSave = getCurrentState()
|
||||||
readerAdapter = null
|
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,10 +45,6 @@ abstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>() {
|
|||||||
outState.putParcelable(KEY_STATE, stateToSave)
|
outState.putParcelable(KEY_STATE, stateToSave)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun requireAdapter() = checkNotNull(readerAdapter) {
|
|
||||||
"Adapter was not created or already destroyed"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) = Unit
|
override fun onWindowInsetsChanged(insets: Insets) = Unit
|
||||||
|
|
||||||
abstract fun switchPageBy(delta: Int)
|
abstract fun switchPageBy(delta: Int)
|
||||||
@@ -64,7 +55,5 @@ abstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>() {
|
|||||||
|
|
||||||
abstract fun getCurrentState(): ReaderState?
|
abstract fun getCurrentState(): ReaderState?
|
||||||
|
|
||||||
protected abstract fun onCreateAdapter(): BaseReaderAdapter<*>
|
protected abstract fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?)
|
||||||
|
|
||||||
protected abstract suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import org.koitharu.kotatsu.core.os.NetworkState
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,16 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.yield
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.os.NetworkState
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
|
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
|
||||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.recyclerView
|
import org.koitharu.kotatsu.core.util.ext.recyclerView
|
||||||
import org.koitharu.kotatsu.core.util.ext.resetTransformations
|
import org.koitharu.kotatsu.core.util.ext.resetTransformations
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||||
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
|
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
|
||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
@@ -35,6 +33,8 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var pageLoader: PageLoader
|
lateinit var pageLoader: PageLoader
|
||||||
|
|
||||||
|
private var pagerAdapter: ReversedPagesAdapter? = null
|
||||||
|
|
||||||
override fun onCreateViewBinding(
|
override fun onCreateViewBinding(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@@ -42,8 +42,15 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
|
|||||||
|
|
||||||
override fun onViewBindingCreated(binding: FragmentReaderStandardBinding, savedInstanceState: Bundle?) {
|
override fun onViewBindingCreated(binding: FragmentReaderStandardBinding, savedInstanceState: Bundle?) {
|
||||||
super.onViewBindingCreated(binding, savedInstanceState)
|
super.onViewBindingCreated(binding, savedInstanceState)
|
||||||
|
pagerAdapter = ReversedPagesAdapter(
|
||||||
|
lifecycleOwner = viewLifecycleOwner,
|
||||||
|
loader = pageLoader,
|
||||||
|
settings = viewModel.readerSettings,
|
||||||
|
networkState = networkState,
|
||||||
|
exceptionResolver = exceptionResolver,
|
||||||
|
)
|
||||||
with(binding.pager) {
|
with(binding.pager) {
|
||||||
adapter = readerAdapter
|
adapter = pagerAdapter
|
||||||
offscreenPageLimit = 2
|
offscreenPageLimit = 2
|
||||||
doOnPageChanged(::notifyPageChanged)
|
doOnPageChanged(::notifyPageChanged)
|
||||||
}
|
}
|
||||||
@@ -60,18 +67,11 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
pagerAdapter = null
|
||||||
requireViewBinding().pager.adapter = null
|
requireViewBinding().pager.adapter = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateAdapter() = ReversedPagesAdapter(
|
|
||||||
lifecycleOwner = viewLifecycleOwner,
|
|
||||||
loader = pageLoader,
|
|
||||||
settings = viewModel.readerSettings,
|
|
||||||
networkState = networkState,
|
|
||||||
exceptionResolver = exceptionResolver,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun switchPageBy(delta: Int) {
|
override fun switchPageBy(delta: Int) {
|
||||||
with(requireViewBinding().pager) {
|
with(requireViewBinding().pager) {
|
||||||
setCurrentItem(currentItem - delta, context.isAnimationsEnabled)
|
setCurrentItem(currentItem - delta, context.isAnimationsEnabled)
|
||||||
@@ -87,26 +87,24 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
|
override fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) {
|
||||||
val reversedPages = pages.asReversed()
|
val reversedPages = pages.asReversed()
|
||||||
val items = async {
|
viewLifecycleScope.launch {
|
||||||
requireAdapter().setItems(reversedPages)
|
val items = async {
|
||||||
yield()
|
pagerAdapter?.setItems(reversedPages)
|
||||||
}
|
|
||||||
if (pendingState != null) {
|
|
||||||
val position = reversedPages.indexOfLast {
|
|
||||||
it.chapterId == pendingState.chapterId && it.index == pendingState.page
|
|
||||||
}
|
}
|
||||||
items.await()
|
if (pendingState != null) {
|
||||||
if (position != -1) {
|
val position = reversedPages.indexOfLast {
|
||||||
requireViewBinding().pager.setCurrentItem(position, false)
|
it.chapterId == pendingState.chapterId && it.index == pendingState.page
|
||||||
notifyPageChanged(position)
|
}
|
||||||
|
items.await() ?: return@launch
|
||||||
|
if (position != -1) {
|
||||||
|
requireViewBinding().pager.setCurrentItem(position, false)
|
||||||
|
notifyPageChanged(position)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
|
items.await()
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
items.await()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +123,6 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun reversed(position: Int): Int {
|
private fun reversed(position: Int): Int {
|
||||||
return ((readerAdapter?.itemCount ?: 0) - position - 1).coerceAtLeast(0)
|
return ((pagerAdapter?.itemCount ?: 0) - position - 1).coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.koitharu.kotatsu.reader.domain.PageLoader
|
|||||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||||
import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder
|
import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder
|
||||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||||
|
import org.koitharu.kotatsu.util.ext.*
|
||||||
|
|
||||||
open class PageHolder(
|
open class PageHolder(
|
||||||
owner: LifecycleOwner,
|
owner: LifecycleOwner,
|
||||||
|
|||||||
@@ -4,18 +4,16 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.yield
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.os.NetworkState
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
|
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
|
||||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.recyclerView
|
import org.koitharu.kotatsu.core.util.ext.recyclerView
|
||||||
import org.koitharu.kotatsu.core.util.ext.resetTransformations
|
import org.koitharu.kotatsu.core.util.ext.resetTransformations
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||||
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
|
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
|
||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
@@ -34,6 +32,8 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>()
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var pageLoader: PageLoader
|
lateinit var pageLoader: PageLoader
|
||||||
|
|
||||||
|
private var pagesAdapter: PagesAdapter? = null
|
||||||
|
|
||||||
override fun onCreateViewBinding(
|
override fun onCreateViewBinding(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@@ -41,8 +41,15 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>()
|
|||||||
|
|
||||||
override fun onViewBindingCreated(binding: FragmentReaderStandardBinding, savedInstanceState: Bundle?) {
|
override fun onViewBindingCreated(binding: FragmentReaderStandardBinding, savedInstanceState: Bundle?) {
|
||||||
super.onViewBindingCreated(binding, savedInstanceState)
|
super.onViewBindingCreated(binding, savedInstanceState)
|
||||||
|
pagesAdapter = PagesAdapter(
|
||||||
|
lifecycleOwner = viewLifecycleOwner,
|
||||||
|
loader = pageLoader,
|
||||||
|
settings = viewModel.readerSettings,
|
||||||
|
networkState = networkState,
|
||||||
|
exceptionResolver = exceptionResolver,
|
||||||
|
)
|
||||||
with(binding.pager) {
|
with(binding.pager) {
|
||||||
adapter = readerAdapter
|
adapter = pagesAdapter
|
||||||
offscreenPageLimit = 2
|
offscreenPageLimit = 2
|
||||||
doOnPageChanged(::notifyPageChanged)
|
doOnPageChanged(::notifyPageChanged)
|
||||||
}
|
}
|
||||||
@@ -59,40 +66,31 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
pagesAdapter = null
|
||||||
requireViewBinding().pager.adapter = null
|
requireViewBinding().pager.adapter = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
|
override fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) {
|
||||||
val items = async {
|
viewLifecycleScope.launch {
|
||||||
requireAdapter().setItems(pages)
|
val items = async {
|
||||||
yield()
|
pagesAdapter?.setItems(pages)
|
||||||
}
|
|
||||||
if (pendingState != null) {
|
|
||||||
val position = pages.indexOfFirst {
|
|
||||||
it.chapterId == pendingState.chapterId && it.index == pendingState.page
|
|
||||||
}
|
}
|
||||||
items.await()
|
if (pendingState != null) {
|
||||||
if (position != -1) {
|
val position = pages.indexOfFirst {
|
||||||
requireViewBinding().pager.setCurrentItem(position, false)
|
it.chapterId == pendingState.chapterId && it.index == pendingState.page
|
||||||
notifyPageChanged(position)
|
}
|
||||||
|
items.await() ?: return@launch
|
||||||
|
if (position != -1) {
|
||||||
|
requireViewBinding().pager.setCurrentItem(position, false)
|
||||||
|
notifyPageChanged(position)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
|
items.await()
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
items.await()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateAdapter() = PagesAdapter(
|
|
||||||
lifecycleOwner = viewLifecycleOwner,
|
|
||||||
loader = pageLoader,
|
|
||||||
settings = viewModel.readerSettings,
|
|
||||||
networkState = networkState,
|
|
||||||
exceptionResolver = exceptionResolver,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun switchPageBy(delta: Int) {
|
override fun switchPageBy(delta: Int) {
|
||||||
with(requireViewBinding().pager) {
|
with(requireViewBinding().pager) {
|
||||||
setCurrentItem(currentItem + delta, context.isAnimationsEnabled)
|
setCurrentItem(currentItem + delta, context.isAnimationsEnabled)
|
||||||
|
|||||||
@@ -4,17 +4,15 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.yield
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.os.NetworkState
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
|
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
|
||||||
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
|
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
|
||||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||||
import org.koitharu.kotatsu.databinding.FragmentReaderWebtoonBinding
|
import org.koitharu.kotatsu.databinding.FragmentReaderWebtoonBinding
|
||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
@@ -33,6 +31,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
|||||||
lateinit var pageLoader: PageLoader
|
lateinit var pageLoader: PageLoader
|
||||||
|
|
||||||
private val scrollInterpolator = AccelerateDecelerateInterpolator()
|
private val scrollInterpolator = AccelerateDecelerateInterpolator()
|
||||||
|
private var webtoonAdapter: WebtoonAdapter? = null
|
||||||
|
|
||||||
override fun onCreateViewBinding(
|
override fun onCreateViewBinding(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@@ -41,9 +40,16 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
|||||||
|
|
||||||
override fun onViewBindingCreated(binding: FragmentReaderWebtoonBinding, savedInstanceState: Bundle?) {
|
override fun onViewBindingCreated(binding: FragmentReaderWebtoonBinding, savedInstanceState: Bundle?) {
|
||||||
super.onViewBindingCreated(binding, savedInstanceState)
|
super.onViewBindingCreated(binding, savedInstanceState)
|
||||||
|
webtoonAdapter = WebtoonAdapter(
|
||||||
|
lifecycleOwner = viewLifecycleOwner,
|
||||||
|
loader = pageLoader,
|
||||||
|
settings = viewModel.readerSettings,
|
||||||
|
networkState = networkState,
|
||||||
|
exceptionResolver = exceptionResolver,
|
||||||
|
)
|
||||||
with(binding.recyclerView) {
|
with(binding.recyclerView) {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
adapter = readerAdapter
|
adapter = webtoonAdapter
|
||||||
addOnPageScrollListener(PageScrollListener())
|
addOnPageScrollListener(PageScrollListener())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,43 +59,32 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
webtoonAdapter = null
|
||||||
requireViewBinding().recyclerView.adapter = null
|
requireViewBinding().recyclerView.adapter = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateAdapter() = WebtoonAdapter(
|
override fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) {
|
||||||
lifecycleOwner = viewLifecycleOwner,
|
viewLifecycleScope.launch {
|
||||||
loader = pageLoader,
|
val setItems = async { webtoonAdapter?.setItems(pages) }
|
||||||
settings = viewModel.readerSettings,
|
if (pendingState != null) {
|
||||||
networkState = networkState,
|
val position = pages.indexOfFirst {
|
||||||
exceptionResolver = exceptionResolver,
|
it.chapterId == pendingState.chapterId && it.index == pendingState.page
|
||||||
)
|
}
|
||||||
|
setItems.await() ?: return@launch
|
||||||
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
|
if (position != -1) {
|
||||||
val setItems = async {
|
with(requireViewBinding().recyclerView) {
|
||||||
requireAdapter().setItems(pages)
|
firstVisibleItemPosition = position
|
||||||
yield()
|
post {
|
||||||
}
|
(findViewHolderForAdapterPosition(position) as? WebtoonHolder)
|
||||||
if (pendingState != null) {
|
?.restoreScroll(pendingState.scroll)
|
||||||
val position = pages.indexOfFirst {
|
}
|
||||||
it.chapterId == pendingState.chapterId && it.index == pendingState.page
|
|
||||||
}
|
|
||||||
setItems.await()
|
|
||||||
if (position != -1) {
|
|
||||||
with(requireViewBinding().recyclerView) {
|
|
||||||
firstVisibleItemPosition = position
|
|
||||||
post {
|
|
||||||
(findViewHolderForAdapterPosition(position) as? WebtoonHolder)
|
|
||||||
?.restoreScroll(pendingState.scroll)
|
|
||||||
}
|
}
|
||||||
|
notifyPageChanged(position)
|
||||||
}
|
}
|
||||||
notifyPageChanged(position)
|
|
||||||
} else {
|
} else {
|
||||||
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
|
setItems.await()
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setItems.await()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,12 @@ import org.koitharu.kotatsu.core.util.ext.observe
|
|||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.plus
|
import org.koitharu.kotatsu.core.util.ext.plus
|
||||||
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
||||||
import org.koitharu.kotatsu.core.util.ext.showDistinct
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||||
import org.koitharu.kotatsu.databinding.SheetPagesBinding
|
import org.koitharu.kotatsu.databinding.SheetPagesBinding
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
|
import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
|
||||||
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.reader.ui.ReaderActivity.IntentBuilder
|
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
|
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -102,8 +101,8 @@ class PagesThumbnailsSheet :
|
|||||||
listener.onPageSelected(item.page)
|
listener.onPageSelected(item.page)
|
||||||
} else {
|
} else {
|
||||||
val state = ReaderState(item.page.chapterId, item.page.index, 0)
|
val state = ReaderState(item.page.chapterId, item.page.index, 0)
|
||||||
val intent = IntentBuilder(view.context).manga(viewModel.manga).state(state).build()
|
val intent = ReaderActivity.newIntent(view.context, viewModel.manga, state)
|
||||||
startActivity(intent, scaleUpActivityOptionsOf(view))
|
startActivity(intent, scaleUpActivityOptionsOf(view).toBundle())
|
||||||
}
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
@@ -195,7 +194,7 @@ class PagesThumbnailsSheet :
|
|||||||
putParcelable(ARG_MANGA, ParcelableManga(manga, true))
|
putParcelable(ARG_MANGA, ParcelableManga(manga, true))
|
||||||
putLong(ARG_CHAPTER_ID, chapterId)
|
putLong(ARG_CHAPTER_ID, chapterId)
|
||||||
putInt(ARG_CURRENT_PAGE, currentPage)
|
putInt(ARG_CURRENT_PAGE, currentPage)
|
||||||
}.showDistinct(fm, TAG)
|
}.show(fm, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
|
||||||
import org.koitharu.kotatsu.details.domain.DoubleMangaLoadUseCase
|
import org.koitharu.kotatsu.details.domain.DoubleMangaLoadUseCase
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListHeader
|
import org.koitharu.kotatsu.list.ui.model.ListHeader
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
@@ -27,7 +26,7 @@ class PagesThumbnailsViewModel @Inject constructor(
|
|||||||
|
|
||||||
private val currentPageIndex: Int = savedStateHandle[PagesThumbnailsSheet.ARG_CURRENT_PAGE] ?: -1
|
private val currentPageIndex: Int = savedStateHandle[PagesThumbnailsSheet.ARG_CURRENT_PAGE] ?: -1
|
||||||
private val initialChapterId: Long = savedStateHandle[PagesThumbnailsSheet.ARG_CHAPTER_ID] ?: 0L
|
private val initialChapterId: Long = savedStateHandle[PagesThumbnailsSheet.ARG_CHAPTER_ID] ?: 0L
|
||||||
val manga = savedStateHandle.require<ParcelableManga>(PagesThumbnailsSheet.ARG_MANGA).manga
|
val manga = requireNotNull(savedStateHandle.get<ParcelableManga>(PagesThumbnailsSheet.ARG_MANGA)).manga
|
||||||
|
|
||||||
private val repository = mangaRepositoryFactory.create(manga.source)
|
private val repository = mangaRepositoryFactory.create(manga.source)
|
||||||
private val mangaDetails = SuspendLazy {
|
private val mangaDetails = SuspendLazy {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
import org.koitharu.kotatsu.core.util.ext.require
|
||||||
@@ -36,7 +37,7 @@ import org.koitharu.kotatsu.list.ui.model.toUi
|
|||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
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.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val FILTER_MIN_INTERVAL = 250L
|
private const val FILTER_MIN_INTERVAL = 250L
|
||||||
@@ -46,6 +47,7 @@ open class RemoteListViewModel @Inject constructor(
|
|||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
mangaRepositoryFactory: MangaRepository.Factory,
|
mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
private val filter: FilterCoordinator,
|
private val filter: FilterCoordinator,
|
||||||
|
private val tagHighlighter: MangaTagHighlighter,
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
listExtraProvider: ListExtraProvider,
|
listExtraProvider: ListExtraProvider,
|
||||||
downloadScheduler: DownloadWorker.Scheduler,
|
downloadScheduler: DownloadWorker.Scheduler,
|
||||||
@@ -70,7 +72,7 @@ open class RemoteListViewModel @Inject constructor(
|
|||||||
list == null -> add(LoadingState)
|
list == null -> add(LoadingState)
|
||||||
list.isEmpty() -> add(createEmptyState(header.value.hasSelectedTags))
|
list.isEmpty() -> add(createEmptyState(header.value.hasSelectedTags))
|
||||||
else -> {
|
else -> {
|
||||||
list.toUi(this, mode, listExtraProvider)
|
list.toUi(this, mode, listExtraProvider, tagHighlighter)
|
||||||
when {
|
when {
|
||||||
error != null -> add(error.toErrorFooter())
|
error != null -> add(error.toErrorFooter())
|
||||||
hasNext -> add(LoadingFooter())
|
hasNext -> add(LoadingFooter())
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user