Merge branch 'master' into devel

master
Koitharu 1 year ago
commit 842ecaaff6
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

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

@ -17,9 +17,9 @@ abstract class BookmarksDao {
@Transaction @Transaction
@Query( @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") @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?> abstract fun observe(mangaId: Long, chapterId: Long, page: Int): Flow<BookmarkEntity?>

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

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

@ -100,7 +100,11 @@ abstract class ChaptersPagesViewModel(
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
val bookmarks = mangaDetails.flatMapLatest { 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()) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())
val chapters = combine( val chapters = combine(

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

@ -352,7 +352,7 @@ class SuggestionsWorker @AssistedInject constructor(
) )
setAutoCancel(true) setAutoCancel(true)
setCategory(NotificationCompat.CATEGORY_RECOMMENDATION) 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()) setShortcutId(manga.id.toString())
priority = NotificationCompat.PRIORITY_DEFAULT priority = NotificationCompat.PRIORITY_DEFAULT

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

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

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

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

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

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

Loading…
Cancel
Save