Compare commits
3 Commits
feature/ki
...
v5.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
| ef9b16da0b | |||
| 5d1ef983e9 | |||
| eb78a776cf |
@@ -15,8 +15,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 547
|
||||
versionName '5.1.3'
|
||||
versionCode 548
|
||||
versionName '5.1.4'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -78,7 +78,7 @@ afterEvaluate {
|
||||
}
|
||||
dependencies {
|
||||
//noinspection GradleDependency
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:fa7ea5b16a') {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:44e28b40d3') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
@@ -115,8 +115,8 @@ dependencies {
|
||||
implementation 'androidx.room:room-ktx:2.5.1'
|
||||
kapt 'androidx.room:room-compiler:2.5.1'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.11.0'
|
||||
implementation 'com.squareup.okio:okio:3.3.0'
|
||||
|
||||
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
|
||||
@@ -127,8 +127,8 @@ dependencies {
|
||||
implementation 'androidx.hilt:hilt-work:1.0.0'
|
||||
kapt 'androidx.hilt:hilt-compiler:1.0.0'
|
||||
|
||||
implementation 'io.coil-kt:coil-base:2.3.0'
|
||||
implementation 'io.coil-kt:coil-svg:2.3.0'
|
||||
implementation 'io.coil-kt:coil-base:2.4.0'
|
||||
implementation 'io.coil-kt:coil-svg:2.4.0'
|
||||
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:1b19231b2f'
|
||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||
implementation 'io.noties.markwon:core:4.6.2'
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.sqlite.db.SupportSQLiteQuery
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.favourites.domain.model.Cover
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
|
||||
@Dao
|
||||
@@ -71,12 +72,12 @@ abstract class FavouritesDao {
|
||||
)
|
||||
abstract suspend fun findAllManga(categoryId: Int): List<MangaEntity>
|
||||
|
||||
suspend fun findCovers(categoryId: Long, order: SortOrder): List<String> {
|
||||
suspend fun findCovers(categoryId: Long, order: SortOrder): List<Cover> {
|
||||
val orderBy = getOrderBy(order)
|
||||
|
||||
@Language("RoomSql")
|
||||
val query = SimpleSQLiteQuery(
|
||||
"SELECT m.cover_url FROM favourites AS f LEFT JOIN manga AS m ON f.manga_id = m.manga_id " +
|
||||
"SELECT m.cover_url AS url, m.source AS source FROM favourites AS f LEFT JOIN manga AS m ON f.manga_id = m.manga_id " +
|
||||
"WHERE f.category_id = ? AND deleted_at = 0 ORDER BY $orderBy",
|
||||
arrayOf<Any>(categoryId),
|
||||
)
|
||||
@@ -145,7 +146,7 @@ abstract class FavouritesDao {
|
||||
protected abstract fun observeAllImpl(query: SupportSQLiteQuery): Flow<List<FavouriteManga>>
|
||||
|
||||
@RawQuery
|
||||
protected abstract suspend fun findCoversImpl(query: SupportSQLiteQuery): List<String>
|
||||
protected abstract suspend fun findCoversImpl(query: SupportSQLiteQuery): List<Cover>
|
||||
|
||||
@Query("UPDATE favourites SET deleted_at = :deletedAt WHERE manga_id = :mangaId")
|
||||
protected abstract suspend fun setDeletedAt(mangaId: Long, deletedAt: Long)
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.koitharu.kotatsu.favourites.data.FavouriteEntity
|
||||
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
|
||||
import org.koitharu.kotatsu.favourites.data.toManga
|
||||
import org.koitharu.kotatsu.favourites.data.toMangaList
|
||||
import org.koitharu.kotatsu.favourites.domain.model.Cover
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
|
||||
@@ -66,11 +67,11 @@ class FavouritesRepository @Inject constructor(
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
fun observeCategoriesWithCovers(): Flow<Map<FavouriteCategory, List<String>>> {
|
||||
fun observeCategoriesWithCovers(): Flow<Map<FavouriteCategory, List<Cover>>> {
|
||||
return db.favouriteCategoriesDao.observeAll()
|
||||
.map {
|
||||
db.withTransaction {
|
||||
val res = LinkedHashMap<FavouriteCategory, List<String>>()
|
||||
val res = LinkedHashMap<FavouriteCategory, List<Cover>>()
|
||||
for (entity in it) {
|
||||
val cat = entity.toFavouriteCategory()
|
||||
res[cat] = db.favouritesDao.findCovers(
|
||||
|
||||
@@ -18,11 +18,12 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.databinding.ItemCategoryBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.utils.ext.animatorDurationScale
|
||||
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.getAnimationDuration
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.utils.ext.source
|
||||
|
||||
fun categoryAD(
|
||||
coil: ImageLoader,
|
||||
@@ -53,10 +54,7 @@ fun categoryAD(
|
||||
ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153))
|
||||
val fallback = ColorDrawable(Color.TRANSPARENT)
|
||||
val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3)
|
||||
val crossFadeDuration = (
|
||||
context.resources.getInteger(R.integer.config_defaultAnimTime) *
|
||||
context.animatorDurationScale
|
||||
).toInt()
|
||||
val crossFadeDuration = context.getAnimationDuration(R.integer.config_defaultAnimTime).toInt()
|
||||
itemView.setOnClickListener(eventListener)
|
||||
itemView.setOnLongClickListener(eventListener)
|
||||
itemView.setOnTouchListener(eventListener)
|
||||
@@ -77,9 +75,11 @@ fun categoryAD(
|
||||
)
|
||||
}
|
||||
repeat(coverViews.size) { i ->
|
||||
coverViews[i].newImageRequest(lifecycleOwner, item.covers.getOrNull(i))?.run {
|
||||
val cover = item.covers.getOrNull(i)
|
||||
coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run {
|
||||
placeholder(R.drawable.ic_placeholder)
|
||||
fallback(fallback)
|
||||
source(cover?.mangaSource)
|
||||
crossfade(crossFadeDuration * (i + 1))
|
||||
error(R.drawable.ic_error_placeholder)
|
||||
allowRgb565(true)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.adapter
|
||||
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.favourites.domain.model.Cover
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
|
||||
class CategoryListModel(
|
||||
val mangaCount: Int,
|
||||
val covers: List<String>,
|
||||
val covers: List<Cover>,
|
||||
val category: FavouriteCategory,
|
||||
val isReorderMode: Boolean,
|
||||
) : ListModel {
|
||||
@@ -21,9 +22,7 @@ class CategoryListModel(
|
||||
if (covers != other.covers) return false
|
||||
if (category.id != other.category.id) return false
|
||||
if (category.title != other.category.title) return false
|
||||
if (category.order != other.category.order) return false
|
||||
|
||||
return true
|
||||
return category.order == other.category.order
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
@@ -35,4 +34,4 @@ class CategoryListModel(
|
||||
result = 31 * result + category.order.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,9 +54,10 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
|
||||
suspend fun put(url: String, source: Source): File = withContext(Dispatchers.IO) {
|
||||
val file = File(cacheDir.get().parentFile, url.longHashCode().toString())
|
||||
try {
|
||||
file.sink(append = false).buffer().use {
|
||||
val bytes = file.sink(append = false).buffer().use {
|
||||
it.writeAllCancellable(source)
|
||||
}
|
||||
check(bytes != 0L) { "No data has been written" }
|
||||
lruCache.get().put(url, file)
|
||||
} finally {
|
||||
file.delete()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.reader.domain
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
@@ -8,6 +9,7 @@ import androidx.collection.LongSparseArray
|
||||
import androidx.collection.set
|
||||
import dagger.hilt.android.ActivityRetainedLifecycle
|
||||
import dagger.hilt.android.lifecycle.RetainedLifecycle
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -30,8 +32,10 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import org.koitharu.kotatsu.utils.FileSize
|
||||
import org.koitharu.kotatsu.utils.RetainedLifecycleCoroutineScope
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.ramAvailable
|
||||
import org.koitharu.kotatsu.utils.ext.withProgress
|
||||
import org.koitharu.kotatsu.utils.progress.ProgressDeferred
|
||||
import java.io.File
|
||||
@@ -44,9 +48,11 @@ import kotlin.coroutines.CoroutineContext
|
||||
|
||||
private const val PROGRESS_UNDEFINED = -1f
|
||||
private const val PREFETCH_LIMIT_DEFAULT = 10
|
||||
private const val PREFETCH_MIN_RAM_MB = 80L
|
||||
|
||||
@ActivityRetainedScoped
|
||||
class PageLoader @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
lifecycle: ActivityRetainedLifecycle,
|
||||
private val okHttp: OkHttpClient,
|
||||
private val cache: PagesCache,
|
||||
@@ -75,7 +81,7 @@ class PageLoader @Inject constructor(
|
||||
}
|
||||
|
||||
fun isPrefetchApplicable(): Boolean {
|
||||
return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled()
|
||||
return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled() && !isLowRam()
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@@ -116,6 +122,9 @@ class PageLoader @Inject constructor(
|
||||
|
||||
suspend fun convertInPlace(file: File) {
|
||||
convertLock.withLock {
|
||||
if (context.ramAvailable < file.length() * 2) {
|
||||
return@withLock
|
||||
}
|
||||
runInterruptible(Dispatchers.Default) {
|
||||
val image = BitmapFactory.decodeFile(file.absolutePath)
|
||||
try {
|
||||
@@ -212,6 +221,10 @@ class PageLoader @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun isLowRam(): Boolean {
|
||||
return context.ramAvailable <= FileSize.MEGABYTES.convert(PREFETCH_MIN_RAM_MB, FileSize.BYTES)
|
||||
}
|
||||
|
||||
private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
|
||||
CoroutineExceptionHandler {
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.ActivityManager
|
||||
import android.app.ActivityManager.MemoryInfo
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Context
|
||||
import android.content.Context.ACTIVITY_SERVICE
|
||||
@@ -138,6 +139,13 @@ fun isLowRamDevice(context: Context): Boolean {
|
||||
return context.activityManager?.isLowRamDevice ?: false
|
||||
}
|
||||
|
||||
val Context.ramAvailable: Long
|
||||
get() {
|
||||
val result = MemoryInfo()
|
||||
activityManager?.getMemoryInfo(result)
|
||||
return result.availMem
|
||||
}
|
||||
|
||||
fun scaleUpActivityOptionsOf(view: View): ActivityOptions = ActivityOptions.makeScaleUpAnimation(
|
||||
view,
|
||||
0,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.koitharu.kotatsu.favourites.domain.model
|
||||
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
|
||||
class Cover(
|
||||
val url: String,
|
||||
val source: String,
|
||||
) {
|
||||
|
||||
val mangaSource: MangaSource?
|
||||
get() = if (source.isEmpty()) null else MangaSource.values().find { it.name == source }
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Cover
|
||||
|
||||
if (url != other.url) return false
|
||||
return source == other.source
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = url.hashCode()
|
||||
result = 31 * result + source.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Cover(url='$url', source=$source)"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user