Merge branch 'master' into devel

This commit is contained in:
Koitharu
2025-05-03 09:26:21 +03:00
13 changed files with 158 additions and 147 deletions

View File

@@ -18,8 +18,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 35
versionCode = 1009
versionName = '8.1.3'
versionCode = 1010
versionName = '8.1.4'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {

View File

@@ -17,9 +17,9 @@ abstract class BookmarksDao {
@Transaction
@Query(
"SELECT * FROM manga JOIN bookmarks ON bookmarks.manga_id = manga.manga_id ORDER BY percent",
"SELECT * FROM manga JOIN bookmarks ON bookmarks.manga_id = manga.manga_id ORDER BY percent LIMIT :limit OFFSET :offset",
)
abstract suspend fun findAll(): Map<MangaWithTags, List<BookmarkEntity>>
abstract suspend fun findAll(offset: Int, limit: Int): Map<MangaWithTags, List<BookmarkEntity>>
@Query("SELECT * FROM bookmarks WHERE manga_id = :mangaId AND chapter_id = :chapterId AND page = :page ORDER BY percent")
abstract fun observe(mangaId: Long, chapterId: Long, page: Int): Flow<BookmarkEntity?>

View File

@@ -28,7 +28,7 @@ class BackupRepository @Inject constructor(
var offset = 0
val entry = BackupEntry(BackupEntry.Name.HISTORY, JSONArray())
while (true) {
val history = db.getHistoryDao().findAll(offset, PAGE_SIZE)
val history = db.getHistoryDao().findAll(offset = offset, limit = PAGE_SIZE)
if (history.isEmpty()) {
break
}
@@ -59,7 +59,7 @@ class BackupRepository @Inject constructor(
var offset = 0
val entry = BackupEntry(BackupEntry.Name.FAVOURITES, JSONArray())
while (true) {
val favourites = db.getFavouritesDao().findAllRaw(offset, PAGE_SIZE)
val favourites = db.getFavouritesDao().findAllRaw(offset = offset, limit = PAGE_SIZE)
if (favourites.isEmpty()) {
break
}
@@ -78,19 +78,26 @@ class BackupRepository @Inject constructor(
}
suspend fun dumpBookmarks(): BackupEntry {
var offset = 0
val entry = BackupEntry(BackupEntry.Name.BOOKMARKS, JSONArray())
val all = db.getBookmarksDao().findAll()
for ((m, b) in all) {
val json = JSONObject()
val manga = JsonSerializer(m.manga).toJson()
json.put("manga", manga)
val tags = JSONArray()
m.tags.forEach { tags.put(JsonSerializer(it).toJson()) }
json.put("tags", tags)
val bookmarks = JSONArray()
b.forEach { bookmarks.put(JsonSerializer(it).toJson()) }
json.put("bookmarks", bookmarks)
entry.data.put(json)
while (true) {
val bookmarks = db.getBookmarksDao().findAll(offset = offset, limit = PAGE_SIZE)
if (bookmarks.isEmpty()) {
break
}
offset += bookmarks.size
for ((m, b) in bookmarks) {
val json = JSONObject()
val manga = JsonSerializer(m.manga).toJson()
json.put("manga", manga)
val tags = JSONArray()
m.tags.forEach { tags.put(JsonSerializer(it).toJson()) }
json.put("tags", tags)
val bookmarks = JSONArray()
b.forEach { bookmarks.put(JsonSerializer(it).toJson()) }
json.put("bookmarks", bookmarks)
entry.data.put(json)
}
}
return entry
}

View File

@@ -151,6 +151,8 @@ fun Manga.chaptersCount(): Int {
return max
}
fun Manga.isNsfw(): Boolean = contentRating == ContentRating.ADULT || source.isNsfw()
fun MangaListFilter.getSummary() = buildSpannedString {
if (!query.isNullOrEmpty()) {
append(query)

View File

@@ -100,7 +100,11 @@ abstract class ChaptersPagesViewModel(
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
val bookmarks = mangaDetails.flatMapLatest {
if (it != null) bookmarksRepository.observeBookmarks(it.toManga()) else flowOf(emptyList())
if (it != null) {
bookmarksRepository.observeBookmarks(it.toManga()).withErrorHandling()
} else {
flowOf(emptyList())
}
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())
val chapters = combine(

View File

@@ -25,6 +25,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ErrorReporterReceiver
import org.koitharu.kotatsu.core.LocalizedAppContext
import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
import org.koitharu.kotatsu.core.util.ext.isReportable
@@ -140,10 +141,10 @@ class DownloadNotificationFactory @AssistedInject constructor(
builder.setSubText(null)
builder.setShowWhen(false)
builder.setVisibility(
if (state != null && state.manga.isNsfw) {
NotificationCompat.VISIBILITY_PRIVATE
if (state != null && state.manga.isNsfw()) {
NotificationCompat.VISIBILITY_SECRET
} else {
NotificationCompat.VISIBILITY_PUBLIC
NotificationCompat.VISIBILITY_PRIVATE
},
)
when {

View File

@@ -352,7 +352,7 @@ class SuggestionsWorker @AssistedInject constructor(
)
setAutoCancel(true)
setCategory(NotificationCompat.CATEGORY_RECOMMENDATION)
setVisibility(if (manga.isNsfw) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PUBLIC)
setVisibility(if (manga.isNsfw()) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PRIVATE)
setShortcutId(manga.id.toString())
priority = NotificationCompat.PRIORITY_DEFAULT

View File

@@ -27,17 +27,17 @@ abstract class TracksDao : MangaQueryBuilder.ConditionCallback {
@Query("SELECT * FROM tracks WHERE manga_id = :mangaId")
abstract suspend fun find(mangaId: Long): TrackEntity?
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
abstract suspend fun findNewChapters(mangaId: Long): Int?
@Query("SELECT IFNULL(chapters_new,0) FROM tracks WHERE manga_id = :mangaId")
abstract suspend fun findNewChapters(mangaId: Long): Int
@Query("SELECT COUNT(*) FROM tracks")
abstract suspend fun getTracksCount(): Int
@Query("SELECT chapters_new FROM tracks")
abstract fun observeNewChapters(): Flow<List<Int>>
@Query("SELECT COUNT(*) FROM tracks WHERE chapters_new > 0")
abstract fun observeUpdateMangaCount(): Flow<Int>
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
abstract fun observeNewChapters(mangaId: Long): Flow<Int?>
@Query("SELECT IFNULL(chapters_new, 0) FROM tracks WHERE manga_id = :mangaId")
abstract fun observeNewChapters(mangaId: Long): Flow<Int>
@Transaction
@Query("SELECT * FROM tracks WHERE chapters_new > 0 ORDER BY last_chapter_date DESC")

View File

@@ -5,7 +5,6 @@ import androidx.room.withTransaction
import dagger.Reusable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toManga
@@ -39,16 +38,16 @@ class TrackingRepository @Inject constructor(
private var isGcCalled = AtomicBoolean(false)
suspend fun getNewChaptersCount(mangaId: Long): Int {
return db.getTracksDao().findNewChapters(mangaId) ?: 0
return db.getTracksDao().findNewChapters(mangaId)
}
fun observeNewChaptersCount(mangaId: Long): Flow<Int> {
return db.getTracksDao().observeNewChapters(mangaId).map { it ?: 0 }
return db.getTracksDao().observeNewChapters(mangaId)
}
@Deprecated("")
fun observeUpdatedMangaCount(): Flow<Int> {
return db.getTracksDao().observeNewChapters().map { list -> list.count { it > 0 } }
return db.getTracksDao().observeUpdateMangaCount()
.onStart { gcIfNotCalled() }
}

View File

@@ -21,6 +21,7 @@ class TrackerDebugViewModel @Inject constructor(
val content = db.getTracksDao().observeAll()
.map { it.toUiList() }
.withErrorHandling()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
private fun List<TrackWithManga>.toUiList(): List<TrackDebugItem> = map {

View File

@@ -7,7 +7,7 @@ import android.content.Context
import android.os.Build
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC
import androidx.core.app.NotificationCompat.VISIBILITY_PRIVATE
import androidx.core.app.NotificationCompat.VISIBILITY_SECRET
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
@@ -17,12 +17,14 @@ import coil3.request.ImageRequest
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.LocalizedAppContext
import org.koitharu.kotatsu.core.model.getLocalizedTitle
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import javax.inject.Inject
@@ -51,7 +53,7 @@ class TrackerNotificationHelper @Inject constructor(
if (newChapters.isEmpty() || !applicationContext.checkNotificationPermission(CHANNEL_ID)) {
return null
}
if (manga.isNsfw && (settings.isTrackerNsfwDisabled || settings.isNsfwContentDisabled)) {
if (manga.isNsfw() && (settings.isTrackerNsfwDisabled || settings.isNsfwContentDisabled)) {
return null
}
val id = manga.url.hashCode()
@@ -92,7 +94,7 @@ class TrackerNotificationHelper @Inject constructor(
false,
),
)
setVisibility(if (manga.isNsfw) VISIBILITY_SECRET else VISIBILITY_PUBLIC)
setVisibility(if (manga.isNsfw()) VISIBILITY_SECRET else VISIBILITY_PRIVATE)
setShortcutId(manga.id.toString())
applyCommonSettings(this)
}
@@ -127,6 +129,13 @@ class TrackerNotificationHelper @Inject constructor(
setNumber(newChaptersCount)
setGroup(GROUP_NEW_CHAPTERS)
setGroupSummary(true)
setVisibility(
if (notifications.any { it.manga.isNsfw() }) {
VISIBILITY_SECRET
} else {
VISIBILITY_PRIVATE
},
)
val intent = AppRouter.mangaUpdatesIntent(applicationContext)
setContentIntent(
PendingIntentCompat.getActivity(

View File

@@ -1,119 +1,107 @@
yaoi
yuri
trap
traps
guro
furry
loli
incest
tentacles
shemale
scat
яой
юри
трап
копро
гуро
тентакли
футанари
инцест
boys' love
girls' love
amputation
amputee
anal birth
anal torture
bdsm
futanari
ntr
coprophagia
unbirth
rape
mother
beast
beastiality
bestiality
birth
blackmail
blood
body horror
bondage
boys' love
brother
bukkake
cannibalism
cbt
choking
coprophagia
degradation
diapers
drugs
egg laying
electrical play
electro
electro play
enema
extreme
father
sister
femdom
force
full censorship
furry
futanari
gang rape
gangbang
gangbang rape
gender bender
girls' love
guro
human pet
humiliation
hypno
incest
inflation
insect
inseki
knife play
loli
lolicon
machine
mind break
mindbreak
molestation
mosaic
mother
mutilation
necrophila
necrophilia
netorase
nipple torture
non-consensual
ntr
orgasm denial
parasite
piercing
prolapse
prostitution
public use
puke
puppy play
rape
ryona
scar
scat
shemale
shota
shotacon
mother
father
brother
rape
blackmail
lolicon
toddlercon
birth
mind break
ryona
beastiality
urination
sister
slave
human pet
amputee
amputation
gender bender
slavery
snuff
tentacles
toddlercon
torture
trans
transgender
full censorship
mosaic
gang rape
furry
inseki
necrophila
prostitution
torture
vore
vaginal birth
parasite
snuff
cannibalism
anal birth
netorase
guro
bestiality
mutilation
vomit
inflation
necrophilia
insect
enema
diapers
beast
parasite
body horror
cbt
piercing
blood
non-consensual
machine
egg laying
femdom
humiliation
public use
bukkake
gangbang
trap
traps
unbirth
urination
incest
lolicon
drugs
slavery
degradation
bondage
watersports
choking
orgasm denial
beastiality
electrical play
hypno
force
molestation
anal torture
prolapse
electro
knife play
scar
degradation
puke
nipple torture
extreme
vaginal birth
violent
degradation
gangbang rape
mindbreak
puppy play
electro play
vomit
vore
watersports
yaoi
yuri
гуро
инцест
копро
тентакли
трап
футанари
юри
яой

View File

@@ -31,7 +31,7 @@ material = "1.13.0-alpha13"
moshi = "1.15.2"
okhttp = "4.12.0"
okio = "3.11.0"
parsers = "e874837efb"
parsers = "b165a0d611"
preference = "1.2.1"
recyclerview = "1.4.0"
room = "2.7.1"