Compare commits

...

11 Commits

Author SHA1 Message Date
Koitharu
24f1546019 Fix pagination 2023-10-26 12:45:32 +03:00
ngocanhtve
1b0fed5c56 Translated using Weblate (Vietnamese)
Currently translated at 84.1% (419 of 498 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/vi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-10-26 12:29:29 +03:00
Koitharu
3d32bd9d58 Fix warnings 2023-10-25 15:42:00 +03:00
Koitharu
590120433c Update dependencies 2023-10-25 15:42:00 +03:00
Koitharu
4bd7656681 Fix loading footer in lists 2023-10-25 15:41:59 +03:00
Koitharu
2c7438e64d Add error reporting to import local manga 2023-10-25 15:41:59 +03:00
InfinityDouki56
665bebaa7b Translated using Weblate (Filipino)
Currently translated at 88.9% (443 of 498 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-10-25 12:05:52 +03:00
return_null
6ed5994726 Translated using Weblate (Chinese (Simplified))
Currently translated at 98.5% (491 of 498 strings)

Co-authored-by: return_null <demolang@dismail.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2023-10-25 12:05:52 +03:00
Dpper
311ed865b7 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (498 of 498 strings)

Co-authored-by: Dpper <ruslan20020401@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-10-25 12:05:52 +03:00
Bai
b59fb678fe Translated using Weblate (Turkish)
Currently translated at 100.0% (498 of 498 strings)

Co-authored-by: Bai <batuhanakkurt000@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2023-10-25 12:05:52 +03:00
Koitharu
ed9ebdcc55 Handle kotatsu scheme links 2023-10-23 17:20:44 +03:00
58 changed files with 384 additions and 332 deletions

View File

@@ -16,11 +16,12 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 34
versionCode = 589
versionName = '6.2.2'
versionCode = 591
versionName = '6.2.4'
generatedDensities = []
testInstrumentationRunner "org.koitharu.kotatsu.HiltTestRunner"
ksp {
// arg("room.generateKotlin", "true") TODO: enable later
arg("room.schemaLocation", "$projectDir/schemas")
}
androidResources {
@@ -81,7 +82,7 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:0054d06e6e') {
implementation('com.github.KotatsuApp:kotatsu-parsers:af1aca8725') {
exclude group: 'org.json', module: 'json'
}
@@ -98,7 +99,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-process:2.6.2'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
@@ -114,9 +115,9 @@ dependencies {
exclude group: 'com.google.j2objc', module: 'j2objc-annotations'
}
implementation 'androidx.room:room-runtime:2.5.2'
implementation 'androidx.room:room-ktx:2.5.2'
ksp 'androidx.room:room-compiler:2.5.2'
implementation 'androidx.room:room-runtime:2.6.0'
implementation 'androidx.room:room-ktx:2.6.0'
ksp 'androidx.room:room-compiler:2.6.0'
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.11.0'
@@ -136,8 +137,8 @@ dependencies {
implementation 'com.github.solkin:disk-lru-cache:1.4'
implementation 'io.noties.markwon:core:4.6.2'
implementation 'ch.acra:acra-http:5.11.2'
implementation 'ch.acra:acra-dialog:5.11.2'
implementation 'ch.acra:acra-http:5.11.3'
implementation 'ch.acra:acra-dialog:5.11.3'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
@@ -152,7 +153,7 @@ dependencies {
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
androidTestImplementation 'androidx.room:room-testing:2.5.2'
androidTestImplementation 'androidx.room:room-testing:2.6.0'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.48.1'

View File

@@ -82,7 +82,7 @@ class AppBackupAgentTest {
assertEquals(history, historyRepository.getOne(SampleData.manga))
assertEquals(listOf(SampleData.manga), favouritesRepository.getManga(category.id))
val allTags = database.tagsDao.findTags(SampleData.tag.source.name).toMangaTags()
val allTags = database.getTagsDao().findTags(SampleData.tag.source.name).toMangaTags()
assertTrue(SampleData.tag in allTags)
}

View File

@@ -83,6 +83,16 @@
<data android:host="kotatsu.app" />
<data android:path="/manga" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="kotatsu" />
<data android:host="manga" />
<data android:host="kotatsu.app" />
</intent-filter>
</activity>
<activity
android:name="org.koitharu.kotatsu.reader.ui.ReaderActivity"
@@ -327,6 +337,13 @@
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
<receiver
android:name="org.koitharu.kotatsu.core.ErrorReporterReceiver"
android:exported="false">
<intent-filter>
<action android:name="${applicationId}.action.REPORT_ERROR" />
</intent-filter>
</receiver>
<meta-data
android:name="android.webkit.WebView.EnableSafeBrowsing"

View File

@@ -25,15 +25,15 @@ class BookmarksRepository @Inject constructor(
) {
fun observeBookmark(manga: Manga, chapterId: Long, page: Int): Flow<Bookmark?> {
return db.bookmarksDao.observe(manga.id, chapterId, page).map { it?.toBookmark(manga) }
return db.getBookmarksDao().observe(manga.id, chapterId, page).map { it?.toBookmark(manga) }
}
fun observeBookmarks(manga: Manga): Flow<List<Bookmark>> {
return db.bookmarksDao.observe(manga.id).mapItems { it.toBookmark(manga) }
return db.getBookmarksDao().observe(manga.id).mapItems { it.toBookmark(manga) }
}
fun observeBookmarks(): Flow<Map<Manga, List<Bookmark>>> {
return db.bookmarksDao.observe().map { map ->
return db.getBookmarksDao().observe().map { map ->
val res = LinkedHashMap<Manga, List<Bookmark>>(map.size)
for ((k, v) in map) {
val manga = k.toManga()
@@ -46,9 +46,9 @@ class BookmarksRepository @Inject constructor(
suspend fun addBookmark(bookmark: Bookmark) {
db.withTransaction {
val tags = bookmark.manga.tags.toEntities()
db.tagsDao.upsert(tags)
db.mangaDao.upsert(bookmark.manga.toEntity(), tags)
db.bookmarksDao.insert(bookmark.toEntity())
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(bookmark.manga.toEntity(), tags)
db.getBookmarksDao().insert(bookmark.toEntity())
}
}
@@ -56,11 +56,11 @@ class BookmarksRepository @Inject constructor(
val entity = bookmark.toEntity().copy(
imageUrl = imageUrl,
)
db.bookmarksDao.upsert(listOf(entity))
db.getBookmarksDao().upsert(listOf(entity))
}
suspend fun removeBookmark(mangaId: Long, chapterId: Long, page: Int) {
check(db.bookmarksDao.delete(mangaId, chapterId, page) != 0) {
check(db.getBookmarksDao().delete(mangaId, chapterId, page) != 0) {
"Bookmark not found"
}
}
@@ -72,7 +72,7 @@ class BookmarksRepository @Inject constructor(
suspend fun removeBookmarks(ids: Set<Long>): ReversibleHandle {
val entities = ArrayList<BookmarkEntity>(ids.size)
db.withTransaction {
val dao = db.bookmarksDao
val dao = db.getBookmarksDao()
for (pageId in ids) {
val e = dao.find(pageId)
if (e != null) {
@@ -92,7 +92,7 @@ class BookmarksRepository @Inject constructor(
db.withTransaction {
for (e in entities) {
try {
db.bookmarksDao.insert(e)
db.getBookmarksDao().insert(e)
} catch (e: SQLException) {
e.printStackTraceDebug()
}

View File

@@ -34,8 +34,8 @@ class BookmarksActivity :
val fm = supportFragmentManager
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
val fragment = BookmarksFragment.newInstance()
replace(R.id.container, fragment)
setReorderingAllowed(true)
replace(R.id.container, BookmarksFragment::class.java, null)
}
}
}

View File

@@ -0,0 +1,33 @@
package org.koitharu.kotatsu.core
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.app.PendingIntentCompat
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
import org.koitharu.kotatsu.core.util.ext.report
class ErrorReporterReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val e = intent?.getSerializableExtraCompat<Throwable>(EXTRA_ERROR) ?: return
e.report()
}
companion object {
private const val EXTRA_ERROR = "err"
private const val ACTION_REPORT = "${BuildConfig.APPLICATION_ID}.action.REPORT_ERROR"
fun getPendingIntent(context: Context, e: Throwable): PendingIntent {
val intent = Intent(context, ErrorReporterReceiver::class.java)
intent.setAction(ACTION_REPORT)
intent.setData(Uri.parse("err://${e.hashCode()}"))
intent.putExtra(EXTRA_ERROR, e)
return checkNotNull(PendingIntentCompat.getBroadcast(context, 0, intent, 0, false))
}
}
}

View File

@@ -22,7 +22,7 @@ class BackupRepository @Inject constructor(
var offset = 0
val entry = BackupEntry(BackupEntry.HISTORY, JSONArray())
while (true) {
val history = db.historyDao.findAll(offset, PAGE_SIZE)
val history = db.getHistoryDao().findAll(offset, PAGE_SIZE)
if (history.isEmpty()) {
break
}
@@ -42,7 +42,7 @@ class BackupRepository @Inject constructor(
suspend fun dumpCategories(): BackupEntry {
val entry = BackupEntry(BackupEntry.CATEGORIES, JSONArray())
val categories = db.favouriteCategoriesDao.findAll()
val categories = db.getFavouriteCategoriesDao().findAll()
for (item in categories) {
entry.data.put(JsonSerializer(item).toJson())
}
@@ -53,7 +53,7 @@ class BackupRepository @Inject constructor(
var offset = 0
val entry = BackupEntry(BackupEntry.FAVOURITES, JSONArray())
while (true) {
val favourites = db.favouritesDao.findAll(offset, PAGE_SIZE)
val favourites = db.getFavouritesDao().findAll(offset, PAGE_SIZE)
if (favourites.isEmpty()) {
break
}
@@ -73,7 +73,7 @@ class BackupRepository @Inject constructor(
suspend fun dumpBookmarks(): BackupEntry {
val entry = BackupEntry(BackupEntry.BOOKMARKS, JSONArray())
val all = db.bookmarksDao.findAll()
val all = db.getBookmarksDao().findAll()
for ((m, b) in all) {
val json = JSONObject()
val manga = JsonSerializer(m.manga).toJson()
@@ -122,9 +122,9 @@ class BackupRepository @Inject constructor(
val history = JsonDeserializer(item).toHistoryEntity()
result += runCatchingCancellable {
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga, tags)
db.historyDao.upsert(history)
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga, tags)
db.getHistoryDao().upsert(history)
}
}
}
@@ -136,7 +136,7 @@ class BackupRepository @Inject constructor(
for (item in entry.data.JSONIterator()) {
val category = JsonDeserializer(item).toFavouriteCategoryEntity()
result += runCatchingCancellable {
db.favouriteCategoriesDao.upsert(category)
db.getFavouriteCategoriesDao().upsert(category)
}
}
return result
@@ -153,9 +153,9 @@ class BackupRepository @Inject constructor(
val favourite = JsonDeserializer(item).toFavouriteEntity()
result += runCatchingCancellable {
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga, tags)
db.favouritesDao.upsert(favourite)
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga, tags)
db.getFavouritesDao().upsert(favourite)
}
}
}
@@ -175,9 +175,9 @@ class BackupRepository @Inject constructor(
}
result += runCatchingCancellable {
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga, tags)
db.bookmarksDao.upsert(bookmarks)
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga, tags)
db.getBookmarksDao().upsert(bookmarks)
}
}
}

View File

@@ -66,29 +66,29 @@ const val DATABASE_VERSION = 17
)
abstract class MangaDatabase : RoomDatabase() {
abstract val historyDao: HistoryDao
abstract fun getHistoryDao(): HistoryDao
abstract val tagsDao: TagsDao
abstract fun getTagsDao(): TagsDao
abstract val mangaDao: MangaDao
abstract fun getMangaDao(): MangaDao
abstract val favouritesDao: FavouritesDao
abstract fun getFavouritesDao(): FavouritesDao
abstract val preferencesDao: PreferencesDao
abstract fun getPreferencesDao(): PreferencesDao
abstract val favouriteCategoriesDao: FavouriteCategoriesDao
abstract fun getFavouriteCategoriesDao(): FavouriteCategoriesDao
abstract val tracksDao: TracksDao
abstract fun getTracksDao(): TracksDao
abstract val trackLogsDao: TrackLogsDao
abstract fun getTrackLogsDao(): TrackLogsDao
abstract val suggestionDao: SuggestionDao
abstract fun getSuggestionDao(): SuggestionDao
abstract val bookmarksDao: BookmarksDao
abstract fun getBookmarksDao(): BookmarksDao
abstract val scrobblingDao: ScrobblingDao
abstract fun getScrobblingDao(): ScrobblingDao
abstract val sourcesDao: MangaSourcesDao
abstract fun getSourcesDao(): MangaSourcesDao
}
fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(

View File

@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration10To11 : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `bookmarks` (
`manga_id` INTEGER NOT NULL,
@@ -20,7 +20,7 @@ class Migration10To11 : Migration(10, 11) {
FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_bookmarks_manga_id` ON `bookmarks` (`manga_id`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_bookmarks_page_id` ON `bookmarks` (`page_id`)")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_bookmarks_manga_id` ON `bookmarks` (`manga_id`)")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_bookmarks_page_id` ON `bookmarks` (`page_id`)")
}
}
}

View File

@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration11To12 : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `scrobblings` (
`scrobbler` INTEGER NOT NULL,
@@ -21,7 +21,7 @@ class Migration11To12 : Migration(11, 12) {
)
""".trimIndent()
)
database.execSQL("ALTER TABLE history ADD COLUMN `percent` REAL NOT NULL DEFAULT -1")
database.execSQL("ALTER TABLE bookmarks ADD COLUMN `percent` REAL NOT NULL DEFAULT -1")
db.execSQL("ALTER TABLE history ADD COLUMN `percent` REAL NOT NULL DEFAULT -1")
db.execSQL("ALTER TABLE bookmarks ADD COLUMN `percent` REAL NOT NULL DEFAULT -1")
}
}

View File

@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration12To13 : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE favourite_categories ADD COLUMN `show_in_lib` INTEGER NOT NULL DEFAULT 1")
database.execSQL("ALTER TABLE favourites ADD COLUMN `sort_key` INTEGER NOT NULL DEFAULT 0")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE favourite_categories ADD COLUMN `show_in_lib` INTEGER NOT NULL DEFAULT 1")
db.execSQL("ALTER TABLE favourites ADD COLUMN `sort_key` INTEGER NOT NULL DEFAULT 0")
}
}
}

View File

@@ -5,11 +5,11 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration13To14 : Migration(13, 14) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE favourite_categories ADD COLUMN `deleted_at` INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE favourites ADD COLUMN `deleted_at` INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE history ADD COLUMN `deleted_at` INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE preferences ADD COLUMN `cf_brightness` REAL NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE preferences ADD COLUMN `cf_contrast` REAL NOT NULL DEFAULT 0")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE favourite_categories ADD COLUMN `deleted_at` INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE favourites ADD COLUMN `deleted_at` INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE history ADD COLUMN `deleted_at` INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE preferences ADD COLUMN `cf_brightness` REAL NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE preferences ADD COLUMN `cf_contrast` REAL NOT NULL DEFAULT 0")
}
}

View File

@@ -5,5 +5,5 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration14To15 : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) = Unit
override fun migrate(db: SupportSQLiteDatabase) = Unit
}

View File

@@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration15To16 : Migration(15, 16) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE preferences ADD COLUMN `cf_invert` INTEGER NOT NULL DEFAULT 0")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE preferences ADD COLUMN `cf_invert` INTEGER NOT NULL DEFAULT 0")
}
}

View File

@@ -10,9 +10,9 @@ class Migration16To17(context: Context) : Migration(16, 17) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE `sources` (`source` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `sort_key` INTEGER NOT NULL, PRIMARY KEY(`source`))")
database.execSQL("CREATE INDEX `index_sources_sort_key` ON `sources` (`sort_key`)")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE `sources` (`source` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `sort_key` INTEGER NOT NULL, PRIMARY KEY(`source`))")
db.execSQL("CREATE INDEX `index_sources_sort_key` ON `sources` (`sort_key`)")
val hiddenSources = prefs.getStringSet("sources_hidden", null).orEmpty()
val order = prefs.getString("sources_order_2", null)?.split('|').orEmpty()
val sources = MangaSource.entries
@@ -30,7 +30,7 @@ class Migration16To17(context: Context) : Migration(16, 17) {
continue
}
}
database.execSQL(
db.execSQL(
"INSERT INTO `sources` (`source`, `enabled`, `sort_key`) VALUES (?, ?, ?)",
arrayOf(name, (!isHidden).toInt(), sortKey),
)

View File

@@ -7,48 +7,48 @@ class Migration1To2 : Migration(1, 2) {
/**
* Adding foreign keys
*/
override fun migrate(database: SupportSQLiteDatabase) {
override fun migrate(db: SupportSQLiteDatabase) {
/* manga_tags */
database.execSQL(
db.execSQL(
"CREATE TABLE IF NOT EXISTS manga_tags_tmp (manga_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, " +
"PRIMARY KEY(manga_id, tag_id), " +
"FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE, " +
"FOREIGN KEY(tag_id) REFERENCES tags(tag_id) ON UPDATE NO ACTION ON DELETE CASCADE )"
)
database.execSQL("CREATE INDEX IF NOT EXISTS index_manga_tags_manga_id ON manga_tags_tmp (manga_id)")
database.execSQL("CREATE INDEX IF NOT EXISTS index_manga_tags_tag_id ON manga_tags_tmp (tag_id)")
database.execSQL("INSERT INTO manga_tags_tmp (manga_id, tag_id) SELECT manga_id, tag_id FROM manga_tags")
database.execSQL("DROP TABLE manga_tags")
database.execSQL("ALTER TABLE manga_tags_tmp RENAME TO manga_tags")
db.execSQL("CREATE INDEX IF NOT EXISTS index_manga_tags_manga_id ON manga_tags_tmp (manga_id)")
db.execSQL("CREATE INDEX IF NOT EXISTS index_manga_tags_tag_id ON manga_tags_tmp (tag_id)")
db.execSQL("INSERT INTO manga_tags_tmp (manga_id, tag_id) SELECT manga_id, tag_id FROM manga_tags")
db.execSQL("DROP TABLE manga_tags")
db.execSQL("ALTER TABLE manga_tags_tmp RENAME TO manga_tags")
/* favourites */
database.execSQL(
db.execSQL(
"CREATE TABLE IF NOT EXISTS favourites_tmp (manga_id INTEGER NOT NULL, category_id INTEGER NOT NULL, created_at INTEGER NOT NULL, " +
"PRIMARY KEY(manga_id, category_id), " +
"FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE , " +
"FOREIGN KEY(category_id) REFERENCES favourite_categories(category_id) ON UPDATE NO ACTION ON DELETE CASCADE )"
)
database.execSQL("CREATE INDEX IF NOT EXISTS index_favourites_manga_id ON favourites_tmp (manga_id)")
database.execSQL("CREATE INDEX IF NOT EXISTS index_favourites_category_id ON favourites_tmp (category_id)")
database.execSQL("INSERT INTO favourites_tmp (manga_id, category_id, created_at) SELECT manga_id, category_id, created_at FROM favourites")
database.execSQL("DROP TABLE favourites")
database.execSQL("ALTER TABLE favourites_tmp RENAME TO favourites")
db.execSQL("CREATE INDEX IF NOT EXISTS index_favourites_manga_id ON favourites_tmp (manga_id)")
db.execSQL("CREATE INDEX IF NOT EXISTS index_favourites_category_id ON favourites_tmp (category_id)")
db.execSQL("INSERT INTO favourites_tmp (manga_id, category_id, created_at) SELECT manga_id, category_id, created_at FROM favourites")
db.execSQL("DROP TABLE favourites")
db.execSQL("ALTER TABLE favourites_tmp RENAME TO favourites")
/* history */
database.execSQL(
db.execSQL(
"CREATE TABLE IF NOT EXISTS history_tmp (manga_id INTEGER NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, chapter_id INTEGER NOT NULL, page INTEGER NOT NULL, " +
"PRIMARY KEY(manga_id), " +
"FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )"
)
database.execSQL("INSERT INTO history_tmp (manga_id, created_at, updated_at, chapter_id, page) SELECT manga_id, created_at, updated_at, chapter_id, page FROM history")
database.execSQL("DROP TABLE history")
database.execSQL("ALTER TABLE history_tmp RENAME TO history")
db.execSQL("INSERT INTO history_tmp (manga_id, created_at, updated_at, chapter_id, page) SELECT manga_id, created_at, updated_at, chapter_id, page FROM history")
db.execSQL("DROP TABLE history")
db.execSQL("ALTER TABLE history_tmp RENAME TO history")
/* preferences */
database.execSQL(
db.execSQL(
"CREATE TABLE IF NOT EXISTS preferences_tmp (manga_id INTEGER NOT NULL, mode INTEGER NOT NULL," +
" PRIMARY KEY(manga_id), " +
"FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )"
)
database.execSQL("INSERT INTO preferences_tmp (manga_id, mode) SELECT manga_id, mode FROM preferences")
database.execSQL("DROP TABLE preferences")
database.execSQL("ALTER TABLE preferences_tmp RENAME TO preferences")
db.execSQL("INSERT INTO preferences_tmp (manga_id, mode) SELECT manga_id, mode FROM preferences")
db.execSQL("DROP TABLE preferences")
db.execSQL("ALTER TABLE preferences_tmp RENAME TO preferences")
}
}
}

View File

@@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration2To3 : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE history ADD COLUMN scroll REAL NOT NULL DEFAULT 0")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE history ADD COLUMN scroll REAL NOT NULL DEFAULT 0")
}
}
}

View File

@@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration3To4 : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS tracks (manga_id INTEGER NOT NULL, chapters_total INTEGER NOT NULL, last_chapter_id INTEGER NOT NULL, chapters_new INTEGER NOT NULL, last_check INTEGER NOT NULL, last_notified_id INTEGER NOT NULL, PRIMARY KEY(manga_id), FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS tracks (manga_id INTEGER NOT NULL, chapters_total INTEGER NOT NULL, last_chapter_id INTEGER NOT NULL, chapters_new INTEGER NOT NULL, last_check INTEGER NOT NULL, last_notified_id INTEGER NOT NULL, PRIMARY KEY(manga_id), FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )")
}
}
}

View File

@@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration4To5 : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE favourite_categories ADD COLUMN sort_key INTEGER NOT NULL DEFAULT 0")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE favourite_categories ADD COLUMN sort_key INTEGER NOT NULL DEFAULT 0")
}
}
}

View File

@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration5To6 : Migration(5, 6) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS track_logs (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, manga_id INTEGER NOT NULL, chapters TEXT NOT NULL, created_at INTEGER NOT NULL, FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE)")
database.execSQL("CREATE INDEX IF NOT EXISTS index_track_logs_manga_id ON track_logs (manga_id)")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS track_logs (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, manga_id INTEGER NOT NULL, chapters TEXT NOT NULL, created_at INTEGER NOT NULL, FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE)")
db.execSQL("CREATE INDEX IF NOT EXISTS index_track_logs_manga_id ON track_logs (manga_id)")
}
}
}

View File

@@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration6To7 : Migration(6, 7) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE manga ADD COLUMN public_url TEXT NOT NULL DEFAULT ''")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE manga ADD COLUMN public_url TEXT NOT NULL DEFAULT ''")
}
}
}

View File

@@ -5,9 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration7To8 : Migration(7, 8) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE manga ADD COLUMN nsfw INTEGER NOT NULL DEFAULT 0")
database.execSQL("CREATE TABLE IF NOT EXISTS suggestions (manga_id INTEGER NOT NULL, relevance REAL NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY(manga_id), FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )")
database.execSQL("CREATE INDEX IF NOT EXISTS index_suggestions_manga_id ON suggestions (manga_id)")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE manga ADD COLUMN nsfw INTEGER NOT NULL DEFAULT 0")
db.execSQL("CREATE TABLE IF NOT EXISTS suggestions (manga_id INTEGER NOT NULL, relevance REAL NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY(manga_id), FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )")
db.execSQL("CREATE INDEX IF NOT EXISTS index_suggestions_manga_id ON suggestions (manga_id)")
}
}
}

View File

@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
class Migration8To9 : Migration(8, 9) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE favourite_categories ADD COLUMN `order` TEXT NOT NULL DEFAULT ${SortOrder.NEWEST.name}")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE favourite_categories ADD COLUMN `order` TEXT NOT NULL DEFAULT ${SortOrder.NEWEST.name}")
}
}
}

View File

@@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration9To10 : Migration(9, 10) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE favourite_categories ADD COLUMN `track` INTEGER NOT NULL DEFAULT 1")
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE favourite_categories ADD COLUMN `track` INTEGER NOT NULL DEFAULT 1")
}
}
}

View File

@@ -28,16 +28,16 @@ class MangaDataRepository @Inject constructor(
suspend fun saveReaderMode(manga: Manga, mode: ReaderMode) {
db.withTransaction {
storeManga(manga)
val entity = db.preferencesDao.find(manga.id) ?: newEntity(manga.id)
db.preferencesDao.upsert(entity.copy(mode = mode.id))
val entity = db.getPreferencesDao().find(manga.id) ?: newEntity(manga.id)
db.getPreferencesDao().upsert(entity.copy(mode = mode.id))
}
}
suspend fun saveColorFilter(manga: Manga, colorFilter: ReaderColorFilter?) {
db.withTransaction {
storeManga(manga)
val entity = db.preferencesDao.find(manga.id) ?: newEntity(manga.id)
db.preferencesDao.upsert(
val entity = db.getPreferencesDao().find(manga.id) ?: newEntity(manga.id)
db.getPreferencesDao().upsert(
entity.copy(
cfBrightness = colorFilter?.brightness ?: 0f,
cfContrast = colorFilter?.contrast ?: 0f,
@@ -48,25 +48,25 @@ class MangaDataRepository @Inject constructor(
}
suspend fun getReaderMode(mangaId: Long): ReaderMode? {
return db.preferencesDao.find(mangaId)?.let { ReaderMode.valueOf(it.mode) }
return db.getPreferencesDao().find(mangaId)?.let { ReaderMode.valueOf(it.mode) }
}
suspend fun getColorFilter(mangaId: Long): ReaderColorFilter? {
return db.preferencesDao.find(mangaId)?.getColorFilterOrNull()
return db.getPreferencesDao().find(mangaId)?.getColorFilterOrNull()
}
fun observeColorFilter(mangaId: Long): Flow<ReaderColorFilter?> {
return db.preferencesDao.observe(mangaId)
return db.getPreferencesDao().observe(mangaId)
.map { it?.getColorFilterOrNull() }
.distinctUntilChanged()
}
suspend fun findMangaById(mangaId: Long): Manga? {
return db.mangaDao.find(mangaId)?.toManga()
return db.getMangaDao().find(mangaId)?.toManga()
}
suspend fun findMangaByPublicUrl(publicUrl: String): Manga? {
return db.mangaDao.findByPublicUrl(publicUrl)?.toManga()
return db.getMangaDao().findByPublicUrl(publicUrl)?.toManga()
}
suspend fun resolveIntent(intent: MangaIntent): Manga? = when {
@@ -79,13 +79,13 @@ class MangaDataRepository @Inject constructor(
suspend fun storeManga(manga: Manga) {
val tags = manga.tags.toEntities()
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga.toEntity(), tags)
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga.toEntity(), tags)
}
}
suspend fun findTags(source: MangaSource): Set<MangaTag> {
return db.tagsDao.findTags(source.name).toMangaTags()
return db.getTagsDao().findTags(source.name).toMangaTags()
}
private fun MangaPrefsEntity.getColorFilterOrNull(): ReaderColorFilter? {

View File

@@ -23,14 +23,14 @@ class MangaLinkResolver @Inject constructor(
) {
suspend fun resolve(uri: Uri): Manga {
return if (uri.host == "kotatsu.app") {
return if (uri.scheme == "kotatsu" || uri.host == "kotatsu.app") {
resolveAppLink(uri)
} else {
resolveExternalLink(uri)
} ?: throw NotFoundException("Manga not found", uri.toString())
} ?: throw NotFoundException("Cannot resolve link", uri.toString())
}
suspend fun resolveAppLink(uri: Uri): Manga? {
private suspend fun resolveAppLink(uri: Uri): Manga? {
require(uri.pathSegments.singleOrNull() == "manga") { "Invalid url" }
val sourceName = requireNotNull(uri.getQueryParameter("source")) { "Source is not specified" }
val source = MangaSource(sourceName)
@@ -42,7 +42,7 @@ class MangaLinkResolver @Inject constructor(
)
}
suspend fun resolveExternalLink(uri: Uri): Manga? {
private suspend fun resolveExternalLink(uri: Uri): Manga? {
dataRepository.findMangaByPublicUrl(uri.toString())?.let {
return it
}

View File

@@ -97,7 +97,8 @@ abstract class BaseActivity<B : ViewBinding> :
}
override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) {
onBackPressed()
onBackPressedDispatcher.onBackPressed()
// TODO: navigateUpTo
true
} else super.onOptionsItemSelected(item)

View File

@@ -16,7 +16,7 @@ class ProgressUpdateUseCase @Inject constructor(
) {
suspend operator fun invoke(manga: Manga): Float {
val history = database.historyDao.find(manga.id) ?: return PROGRESS_NONE
val history = database.getHistoryDao().find(manga.id) ?: return PROGRESS_NONE
val seed = if (manga.isLocal) {
localMangaRepository.getRemoteManga(manga) ?: manga
} else {
@@ -43,7 +43,7 @@ class ProgressUpdateUseCase @Inject constructor(
val ppc = 1f / chaptersCount
val result = ppc * chapterIndex + ppc * pagePercent
if (result != history.percent) {
database.historyDao.update(
database.getHistoryDao().update(
history.copy(
chapterId = chapter.id,
percent = result,

View File

@@ -30,7 +30,7 @@ class MangaSourcesRepository @Inject constructor(
) {
private val dao: MangaSourcesDao
get() = db.sourcesDao
get() = db.getSourcesDao()
private val remoteSources = EnumSet.allOf(MangaSource::class.java).apply {
remove(MangaSource.LOCAL)

View File

@@ -31,27 +31,27 @@ class FavouritesRepository @Inject constructor(
) {
suspend fun getAllManga(): List<Manga> {
val entities = db.favouritesDao.findAll()
val entities = db.getFavouritesDao().findAll()
return entities.toMangaList()
}
suspend fun getLastManga(limit: Int): List<Manga> {
val entities = db.favouritesDao.findLast(limit)
val entities = db.getFavouritesDao().findLast(limit)
return entities.toMangaList()
}
fun observeAll(order: ListSortOrder): Flow<List<Manga>> {
return db.favouritesDao.observeAll(order)
return db.getFavouritesDao().observeAll(order)
.mapItems { it.toManga() }
}
suspend fun getManga(categoryId: Long): List<Manga> {
val entities = db.favouritesDao.findAll(categoryId)
val entities = db.getFavouritesDao().findAll(categoryId)
return entities.toMangaList()
}
fun observeAll(categoryId: Long, order: ListSortOrder): Flow<List<Manga>> {
return db.favouritesDao.observeAll(categoryId, order)
return db.getFavouritesDao().observeAll(categoryId, order)
.mapItems { it.toManga() }
}
@@ -61,25 +61,25 @@ class FavouritesRepository @Inject constructor(
}
fun observeCategories(): Flow<List<FavouriteCategory>> {
return db.favouriteCategoriesDao.observeAll().mapItems {
return db.getFavouriteCategoriesDao().observeAll().mapItems {
it.toFavouriteCategory()
}.distinctUntilChanged()
}
fun observeCategoriesForLibrary(): Flow<List<FavouriteCategory>> {
return db.favouriteCategoriesDao.observeAllForLibrary().mapItems {
return db.getFavouriteCategoriesDao().observeAllForLibrary().mapItems {
it.toFavouriteCategory()
}.distinctUntilChanged()
}
fun observeCategoriesWithCovers(): Flow<Map<FavouriteCategory, List<Cover>>> {
return db.favouriteCategoriesDao.observeAll()
return db.getFavouriteCategoriesDao().observeAll()
.map {
db.withTransaction {
val res = LinkedHashMap<FavouriteCategory, List<Cover>>()
for (entity in it) {
val cat = entity.toFavouriteCategory()
res[cat] = db.favouritesDao.findCovers(
res[cat] = db.getFavouritesDao().findCovers(
categoryId = cat.id,
order = cat.order,
)
@@ -90,16 +90,16 @@ class FavouritesRepository @Inject constructor(
}
fun observeCategory(id: Long): Flow<FavouriteCategory?> {
return db.favouriteCategoriesDao.observe(id)
return db.getFavouriteCategoriesDao().observe(id)
.map { it?.toFavouriteCategory() }
}
fun observeCategoriesIds(mangaId: Long): Flow<Set<Long>> {
return db.favouritesDao.observeIds(mangaId).map { it.toSet() }
return db.getFavouritesDao().observeIds(mangaId).map { it.toSet() }
}
suspend fun getCategory(id: Long): FavouriteCategory {
return db.favouriteCategoriesDao.find(id.toInt()).toFavouriteCategory()
return db.getFavouriteCategoriesDao().find(id.toInt()).toFavouriteCategory()
}
suspend fun createCategory(
@@ -111,14 +111,14 @@ class FavouritesRepository @Inject constructor(
val entity = FavouriteCategoryEntity(
title = title,
createdAt = System.currentTimeMillis(),
sortKey = db.favouriteCategoriesDao.getNextSortKey(),
sortKey = db.getFavouriteCategoriesDao().getNextSortKey(),
categoryId = 0,
order = sortOrder.name,
track = isTrackerEnabled,
deletedAt = 0L,
isVisibleInLibrary = isVisibleOnShelf,
)
val id = db.favouriteCategoriesDao.insert(entity)
val id = db.getFavouriteCategoriesDao().insert(entity)
val category = entity.toFavouriteCategory(id)
channels.createChannel(category)
return category
@@ -131,22 +131,22 @@ class FavouritesRepository @Inject constructor(
isTrackerEnabled: Boolean,
isVisibleOnShelf: Boolean,
) {
db.favouriteCategoriesDao.update(id, title, sortOrder.name, isTrackerEnabled, isVisibleOnShelf)
db.getFavouriteCategoriesDao().update(id, title, sortOrder.name, isTrackerEnabled, isVisibleOnShelf)
}
suspend fun updateCategory(id: Long, isVisibleInLibrary: Boolean) {
db.favouriteCategoriesDao.updateLibVisibility(id, isVisibleInLibrary)
db.getFavouriteCategoriesDao().updateLibVisibility(id, isVisibleInLibrary)
}
suspend fun updateCategoryTracking(id: Long, isTrackingEnabled: Boolean) {
db.favouriteCategoriesDao.updateTracking(id, isTrackingEnabled)
db.getFavouriteCategoriesDao().updateTracking(id, isTrackingEnabled)
}
suspend fun removeCategories(ids: Collection<Long>) {
db.withTransaction {
for (id in ids) {
db.favouritesDao.deleteAll(id)
db.favouriteCategoriesDao.delete(id)
db.getFavouritesDao().deleteAll(id)
db.getFavouriteCategoriesDao().delete(id)
}
}
// run after transaction success
@@ -156,11 +156,11 @@ class FavouritesRepository @Inject constructor(
}
suspend fun setCategoryOrder(id: Long, order: ListSortOrder) {
db.favouriteCategoriesDao.updateOrder(id, order.name)
db.getFavouriteCategoriesDao().updateOrder(id, order.name)
}
suspend fun reorderCategories(orderedIds: List<Long>) {
val dao = db.favouriteCategoriesDao
val dao = db.getFavouriteCategoriesDao()
db.withTransaction {
for ((i, id) in orderedIds.withIndex()) {
dao.updateSortKey(id, i)
@@ -172,8 +172,8 @@ class FavouritesRepository @Inject constructor(
db.withTransaction {
for (manga in mangas) {
val tags = manga.tags.toEntities()
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga.toEntity(), tags)
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga.toEntity(), tags)
val entity = FavouriteEntity(
mangaId = manga.id,
categoryId = categoryId,
@@ -181,7 +181,7 @@ class FavouritesRepository @Inject constructor(
sortKey = 0,
deletedAt = 0L,
)
db.favouritesDao.insert(entity)
db.getFavouritesDao().insert(entity)
}
}
}
@@ -189,7 +189,7 @@ class FavouritesRepository @Inject constructor(
suspend fun removeFromFavourites(ids: Collection<Long>): ReversibleHandle {
db.withTransaction {
for (id in ids) {
db.favouritesDao.delete(mangaId = id)
db.getFavouritesDao().delete(mangaId = id)
}
}
return ReversibleHandle { recoverToFavourites(ids) }
@@ -198,14 +198,14 @@ class FavouritesRepository @Inject constructor(
suspend fun removeFromCategory(categoryId: Long, ids: Collection<Long>): ReversibleHandle {
db.withTransaction {
for (id in ids) {
db.favouritesDao.delete(categoryId = categoryId, mangaId = id)
db.getFavouritesDao().delete(categoryId = categoryId, mangaId = id)
}
}
return ReversibleHandle { recoverToCategory(categoryId, ids) }
}
private fun observeOrder(categoryId: Long): Flow<ListSortOrder> {
return db.favouriteCategoriesDao.observe(categoryId)
return db.getFavouriteCategoriesDao().observe(categoryId)
.filterNotNull()
.map { x -> ListSortOrder(x.order, ListSortOrder.NEWEST) }
.distinctUntilChanged()
@@ -214,7 +214,7 @@ class FavouritesRepository @Inject constructor(
private suspend fun recoverToFavourites(ids: Collection<Long>) {
db.withTransaction {
for (id in ids) {
db.favouritesDao.recover(mangaId = id)
db.getFavouritesDao().recover(mangaId = id)
}
}
}
@@ -222,7 +222,7 @@ class FavouritesRepository @Inject constructor(
private suspend fun recoverToCategory(categoryId: Long, ids: Collection<Long>) {
db.withTransaction {
for (id in ids) {
db.favouritesDao.recover(mangaId = id, categoryId = categoryId)
db.getFavouritesDao().recover(mangaId = id, categoryId = categoryId)
}
}
}

View File

@@ -23,7 +23,7 @@ abstract class HistoryDao {
@Transaction
@Query("SELECT * FROM history WHERE deleted_at = 0 AND manga_id IN (:ids)")
abstract suspend fun findAll(ids: Collection<Long>): List<HistoryEntity?>
abstract suspend fun findAll(ids: Collection<Long>): List<HistoryEntity>
@Transaction
@Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC")

View File

@@ -38,36 +38,36 @@ class HistoryRepository @Inject constructor(
) {
suspend fun getList(offset: Int, limit: Int): List<Manga> {
val entities = db.historyDao.findAll(offset, limit)
val entities = db.getHistoryDao().findAll(offset, limit)
return entities.map { it.manga.toManga(it.tags.toMangaTags()) }
}
suspend fun getLastOrNull(): Manga? {
val entity = db.historyDao.findAll(0, 1).firstOrNull() ?: return null
val entity = db.getHistoryDao().findAll(0, 1).firstOrNull() ?: return null
return entity.manga.toManga(entity.tags.toMangaTags())
}
fun observeLast(): Flow<Manga?> {
return db.historyDao.observeAll(1).map {
return db.getHistoryDao().observeAll(1).map {
val first = it.firstOrNull()
first?.manga?.toManga(first.tags.toMangaTags())
}
}
fun observeAll(): Flow<List<Manga>> {
return db.historyDao.observeAll().mapItems {
return db.getHistoryDao().observeAll().mapItems {
it.manga.toManga(it.tags.toMangaTags())
}
}
fun observeAll(limit: Int): Flow<List<Manga>> {
return db.historyDao.observeAll(limit).mapItems {
return db.getHistoryDao().observeAll(limit).mapItems {
it.manga.toManga(it.tags.toMangaTags())
}
}
fun observeAllWithHistory(order: ListSortOrder): Flow<List<MangaWithHistory>> {
return db.historyDao.observeAll(order).mapItems {
return db.getHistoryDao().observeAll(order).mapItems {
MangaWithHistory(
it.manga.toManga(it.tags.toMangaTags()),
it.history.toMangaHistory(),
@@ -76,13 +76,13 @@ class HistoryRepository @Inject constructor(
}
fun observeOne(id: Long): Flow<MangaHistory?> {
return db.historyDao.observe(id).map {
return db.getHistoryDao().observe(id).map {
it?.toMangaHistory()
}
}
fun observeHasItems(): Flow<Boolean> {
return db.historyDao.observeCount()
return db.getHistoryDao().observeCount()
.map { it > 0 }
.distinctUntilChanged()
}
@@ -93,12 +93,12 @@ class HistoryRepository @Inject constructor(
}
val tags = manga.tags.toEntities()
db.withTransaction {
val existing = db.mangaDao.find(manga.id)?.manga
val existing = db.getMangaDao().find(manga.id)?.manga
if (existing == null || existing.source == manga.source.name) {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga.toEntity(), tags)
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga.toEntity(), tags)
}
db.historyDao.upsert(
db.getHistoryDao().upsert(
HistoryEntity(
mangaId = manga.id,
createdAt = System.currentTimeMillis(),
@@ -119,29 +119,29 @@ class HistoryRepository @Inject constructor(
}
suspend fun getOne(manga: Manga): MangaHistory? {
return db.historyDao.find(manga.id)?.recoverIfNeeded(manga)?.toMangaHistory()
return db.getHistoryDao().find(manga.id)?.recoverIfNeeded(manga)?.toMangaHistory()
}
suspend fun getProgress(mangaId: Long): Float {
return db.historyDao.findProgress(mangaId) ?: PROGRESS_NONE
return db.getHistoryDao().findProgress(mangaId) ?: PROGRESS_NONE
}
suspend fun clear() {
db.historyDao.clear()
db.getHistoryDao().clear()
}
suspend fun delete(manga: Manga) {
db.historyDao.delete(manga.id)
db.getHistoryDao().delete(manga.id)
}
suspend fun deleteAfter(minDate: Long) {
db.historyDao.deleteAfter(minDate)
db.getHistoryDao().deleteAfter(minDate)
}
suspend fun delete(ids: Collection<Long>): ReversibleHandle {
db.withTransaction {
for (id in ids) {
db.historyDao.delete(id)
db.getHistoryDao().delete(id)
}
}
return ReversibleHandle {
@@ -154,13 +154,13 @@ class HistoryRepository @Inject constructor(
* Useful for replacing saved manga on deleting it with remote source
*/
suspend fun deleteOrSwap(manga: Manga, alternative: Manga?) {
if (alternative == null || db.mangaDao.update(alternative.toEntity()) <= 0) {
db.historyDao.delete(manga.id)
if (alternative == null || db.getMangaDao().update(alternative.toEntity()) <= 0) {
db.getHistoryDao().delete(manga.id)
}
}
suspend fun getPopularTags(limit: Int): List<MangaTag> {
return db.historyDao.findPopularTags(limit).map { x -> x.toMangaTag() }
return db.getHistoryDao().findPopularTags(limit).map { x -> x.toMangaTag() }
}
fun shouldSkip(manga: Manga): Boolean {
@@ -178,7 +178,7 @@ class HistoryRepository @Inject constructor(
private suspend fun recover(ids: Collection<Long>) {
db.withTransaction {
for (id in ids) {
db.historyDao.recover(id)
db.getHistoryDao().recover(id)
}
}
}
@@ -192,7 +192,7 @@ class HistoryRepository @Inject constructor(
(chapters.size * percent).toInt(),
)?.id ?: return this
val newEntity = copy(chapterId = newChapterId)
db.historyDao.update(newEntity)
db.getHistoryDao().update(newEntity)
return newEntity
}
}

View File

@@ -24,6 +24,7 @@ import coil.request.ImageRequest
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ErrorReporterReceiver
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
@@ -91,6 +92,7 @@ class ImportWorker @AssistedInject constructor(
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setDefaults(0)
.setSilent(true)
.setAutoCancel(true)
result.onSuccess { manga ->
notification.setLargeIcon(
coil.execute(
@@ -110,10 +112,9 @@ class ImportWorker @AssistedInject constructor(
PendingIntent.FLAG_UPDATE_CURRENT,
false,
),
).setAutoCancel(true)
.setVisibility(
if (manga.isNsfw) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PUBLIC,
)
).setVisibility(
if (manga.isNsfw) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PUBLIC,
)
notification.setContentTitle(applicationContext.getString(R.string.import_completed))
.setContentText(applicationContext.getString(R.string.import_completed_hint))
.setSmallIcon(R.drawable.ic_stat_done)
@@ -123,6 +124,11 @@ class ImportWorker @AssistedInject constructor(
notification.setContentTitle(applicationContext.getString(R.string.error_occurred))
.setContentText(error.getDisplayMessage(applicationContext.resources))
.setSmallIcon(android.R.drawable.stat_notify_error)
.addAction(
R.drawable.ic_alert_outline,
applicationContext.getString(R.string.report),
ErrorReporterReceiver.getPendingIntent(applicationContext, error),
)
}
return notification.build()
}

View File

@@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -95,7 +96,6 @@ open class RemoteListViewModel @Inject constructor(
.onEach { filterState ->
loadingJob?.cancelAndJoin()
mangaList.value = null
hasNextPage.value = false
loadList(filterState, false)
}.catch { error ->
listError.value = error
@@ -134,12 +134,18 @@ open class RemoteListViewModel @Inject constructor(
sortOrder = filterState.sortOrder,
tags = filterState.tags,
)
if (!append) {
mangaList.value = list
} else if (list.isNotEmpty()) {
mangaList.value = mangaList.value?.plus(list) ?: list
val oldList = mangaList.getAndUpdate { oldList ->
if (!append || oldList.isNullOrEmpty()) {
list
} else {
oldList + list
}
}.orEmpty()
hasNextPage.value = if (append) {
list.isNotEmpty()
} else {
list.size > oldList.size || hasNextPage.value
}
hasNextPage.value = list.isNotEmpty() // TODO check if new ids added
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
@@ -148,6 +154,7 @@ open class RemoteListViewModel @Inject constructor(
if (!mangaList.value.isNullOrEmpty()) {
errorEvent.call(e)
}
hasNextPage.value = false
}
}.also { loadingJob = it }
}

View File

@@ -106,7 +106,7 @@ class AniListRepository @Inject constructor(
}
override suspend fun unregister(mangaId: Long) {
return db.scrobblingDao.delete(ScrobblerService.ANILIST.id, mangaId)
return db.getScrobblingDao().delete(ScrobblerService.ANILIST.id, mangaId)
}
override fun logout() {
@@ -223,7 +223,7 @@ class AniListRepository @Inject constructor(
comment = json.getString("notes"),
rating = scoreFormat.normalize(json.getDouble("score").toFloat()),
)
db.scrobblingDao.upsert(entity)
db.getScrobblingDao().upsert(entity)
}
private fun ScrobblerManga(json: JSONObject): ScrobblerManga {

View File

@@ -29,7 +29,7 @@ class AniListScrobbler @Inject constructor(
status: ScrobblingStatus?,
comment: String?,
) {
val entity = db.scrobblingDao.find(scrobblerService.id, mangaId)
val entity = db.getScrobblingDao().find(scrobblerService.id, mangaId)
requireNotNull(entity) { "Scrobbling info for manga $mangaId not found" }
repository.updateRate(
rateId = entity.id,

View File

@@ -67,24 +67,24 @@ abstract class Scrobbler(
}
suspend fun scrobble(mangaId: Long, chapter: MangaChapter) {
val entity = db.scrobblingDao.find(scrobblerService.id, mangaId) ?: return
val entity = db.getScrobblingDao().find(scrobblerService.id, mangaId) ?: return
repository.updateRate(entity.id, entity.mangaId, chapter)
}
suspend fun getScrobblingInfoOrNull(mangaId: Long): ScrobblingInfo? {
val entity = db.scrobblingDao.find(scrobblerService.id, mangaId) ?: return null
val entity = db.getScrobblingDao().find(scrobblerService.id, mangaId) ?: return null
return entity.toScrobblingInfo()
}
abstract suspend fun updateScrobblingInfo(mangaId: Long, rating: Float, status: ScrobblingStatus?, comment: String?)
fun observeScrobblingInfo(mangaId: Long): Flow<ScrobblingInfo?> {
return db.scrobblingDao.observe(scrobblerService.id, mangaId)
return db.getScrobblingDao().observe(scrobblerService.id, mangaId)
.map { it?.toScrobblingInfo() }
}
fun observeAllScrobblingInfo(): Flow<List<ScrobblingInfo>> {
return db.scrobblingDao.observe(scrobblerService.id)
return db.getScrobblingDao().observe(scrobblerService.id)
.map { entities ->
coroutineScope {
entities.map {

View File

@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
@@ -18,6 +19,7 @@ import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.core.util.ext.requireValue
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -27,7 +29,6 @@ import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.common.ui.selector.model.ScrobblerHint
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import javax.inject.Inject
@HiltViewModel
@@ -53,7 +54,7 @@ class ScrobblingSelectorViewModel @Inject constructor(
get() = availableScrobblers[selectedScrobblerIndex.requireValue()]
val content: StateFlow<List<ListModel>> = combine(
scrobblerMangaList,
scrobblerMangaList.map { it.distinctBy { x -> x.id } },
listError,
hasNextPage,
) { list, error, isHasNextPage ->

View File

@@ -85,7 +85,7 @@ class MALRepository @Inject constructor(
}
override suspend fun unregister(mangaId: Long) {
return db.scrobblingDao.delete(ScrobblerService.MAL.id, mangaId)
return db.getScrobblingDao().delete(ScrobblerService.MAL.id, mangaId)
}
override suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
@@ -98,6 +98,7 @@ class MALRepository @Inject constructor(
.build()
val request = Request.Builder().url(url).get().build()
val response = okHttp.newCall(request).await().parseJson()
check(response.has("data")) { "Invalid response: \"$response\"" }
val data = response.getJSONArray("data")
return data.mapJSONNotNull { jsonToManga(it) }
}
@@ -175,7 +176,7 @@ class MALRepository @Inject constructor(
comment = json.getString("comments"),
rating = (json.getDouble("score").toFloat() / 10f).coerceIn(0f, 1f),
)
db.scrobblingDao.upsert(entity)
db.getScrobblingDao().upsert(entity)
}
override fun logout() {

View File

@@ -30,7 +30,7 @@ class MALScrobbler @Inject constructor(
status: ScrobblingStatus?,
comment: String?,
) {
val entity = db.scrobblingDao.find(scrobblerService.id, mangaId)
val entity = db.getScrobblingDao().find(scrobblerService.id, mangaId)
requireNotNull(entity) { "Scrobbling info for manga $mangaId not found" }
repository.updateRate(
rateId = entity.id,

View File

@@ -84,7 +84,7 @@ class ShikimoriRepository @Inject constructor(
}
override suspend fun unregister(mangaId: Long) {
return db.scrobblingDao.delete(ScrobblerService.SHIKIMORI.id, mangaId)
return db.getScrobblingDao().delete(ScrobblerService.SHIKIMORI.id, mangaId)
}
override fun logout() {
@@ -192,7 +192,7 @@ class ShikimoriRepository @Inject constructor(
comment = json.getString("text"),
rating = (json.getDouble("score").toFloat() / 10f).coerceIn(0f, 1f),
)
db.scrobblingDao.upsert(entity)
db.getScrobblingDao().upsert(entity)
}
private fun ScrobblerManga(json: JSONObject) = ScrobblerManga(

View File

@@ -31,7 +31,7 @@ class ShikimoriScrobbler @Inject constructor(
status: ScrobblingStatus?,
comment: String?,
) {
val entity = db.scrobblingDao.find(scrobblerService.id, mangaId)
val entity = db.getScrobblingDao().find(scrobblerService.id, mangaId)
requireNotNull(entity) { "Scrobbling info for manga $mangaId not found" }
repository.updateRate(
rateId = entity.id,

View File

@@ -40,9 +40,9 @@ class MangaSearchRepository @Inject constructor(
}
val skipNsfw = settings.isNsfwContentDisabled
return if (source != null) {
db.mangaDao.searchByTitle("%$query%", source.name, limit)
db.getMangaDao().searchByTitle("%$query%", source.name, limit)
} else {
db.mangaDao.searchByTitle("%$query%", limit)
db.getMangaDao().searchByTitle("%$query%", limit)
}.let {
if (skipNsfw) it.filterNot { x -> x.manga.isNsfw } else it
}
@@ -83,7 +83,7 @@ class MangaSearchRepository @Inject constructor(
if (query.isEmpty()) {
return emptyList()
}
val titles = db.suggestionDao.getTitles("$query%")
val titles = db.getSuggestionDao().getTitles("$query%")
if (titles.isEmpty()) {
return emptyList()
}
@@ -92,19 +92,20 @@ class MangaSearchRepository @Inject constructor(
suspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List<MangaTag> {
return when {
query.isNotEmpty() && source != null -> db.tagsDao.findTags(source.name, "%$query%", limit)
query.isNotEmpty() -> db.tagsDao.findTags("%$query%", limit)
source != null -> db.tagsDao.findPopularTags(source.name, limit)
else -> db.tagsDao.findPopularTags(limit)
query.isNotEmpty() && source != null -> db.getTagsDao()
.findTags(source.name, "%$query%", limit)
query.isNotEmpty() -> db.getTagsDao().findTags("%$query%", limit)
source != null -> db.getTagsDao().findPopularTags(source.name, limit)
else -> db.getTagsDao().findPopularTags(limit)
}.toMangaTagsList()
}
suspend fun getTagsSuggestion(tags: Set<MangaTag>): List<MangaTag> {
val ids = tags.mapToSet { it.toEntity().id }
return if (ids.size == 1) {
db.tagsDao.findRelatedTags(ids.first())
db.getTagsDao().findRelatedTags(ids.first())
} else {
db.tagsDao.findRelatedTags(ids)
db.getTagsDao().findRelatedTags(ids)
}.mapNotNull { x ->
if (x.id in ids) null else x.toMangaTag()
}

View File

@@ -12,7 +12,7 @@ fun searchSuggestionQueryHintAD(
{ inflater, parent -> ItemSearchSuggestionQueryHintBinding.inflate(inflater, parent, false) },
) {
val viewClickListener = View.OnClickListener { v ->
val viewClickListener = View.OnClickListener { _ ->
listener.onQueryClick(item.query, true)
}

View File

@@ -11,7 +11,7 @@ class TagsAutoCompleteProvider @Inject constructor(
if (query.isEmpty()) {
return emptyList()
}
val tags = db.tagsDao.findTags(query = "$query%", limit = 6)
val tags = db.getTagsDao().findTags(query = "$query%", limit = 6)
val set = HashSet<String>()
val result = ArrayList<String>(tags.size)
for (tag in tags) {

View File

@@ -17,39 +17,39 @@ class SuggestionRepository @Inject constructor(
) {
fun observeAll(): Flow<List<Manga>> {
return db.suggestionDao.observeAll().mapItems {
return db.getSuggestionDao().observeAll().mapItems {
it.manga.toManga(it.tags.toMangaTags())
}
}
fun observeAll(limit: Int): Flow<List<Manga>> {
return db.suggestionDao.observeAll(limit).mapItems {
return db.getSuggestionDao().observeAll(limit).mapItems {
it.manga.toManga(it.tags.toMangaTags())
}
}
suspend fun getRandom(): Manga? {
return db.suggestionDao.getRandom()?.let {
return db.getSuggestionDao().getRandom()?.let {
it.manga.toManga(it.tags.toMangaTags())
}
}
suspend fun clear() {
db.suggestionDao.deleteAll()
db.getSuggestionDao().deleteAll()
}
suspend fun isEmpty(): Boolean {
return db.suggestionDao.count() == 0
return db.getSuggestionDao().count() == 0
}
suspend fun replace(suggestions: Iterable<MangaSuggestion>) {
db.withTransaction {
db.suggestionDao.deleteAll()
db.getSuggestionDao().deleteAll()
suggestions.forEach { (manga, relevance) ->
val tags = manga.tags.toEntities()
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga.toEntity(), tags)
db.suggestionDao.upsert(
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga.toEntity(), tags)
db.getSuggestionDao().upsert(
SuggestionEntity(
mangaId = manga.id,
relevance = relevance,

View File

@@ -29,8 +29,7 @@ class SuggestionsActivity :
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
setReorderingAllowed(true)
val fragment = SuggestionsFragment.newInstance()
replace(R.id.container, fragment)
replace(R.id.container, SuggestionsFragment::class.java, null)
}
}
}

View File

@@ -120,11 +120,11 @@ class SyncController @Inject constructor(
private suspend fun MangaDatabase.gc(favourites: Boolean, history: Boolean) = withTransaction {
val deletedAt = System.currentTimeMillis() - defaultGcPeriod
if (history) {
historyDao.gc(deletedAt)
getHistoryDao().gc(deletedAt)
}
if (favourites) {
favouritesDao.gc(deletedAt)
favouriteCategoriesDao.gc(deletedAt)
getFavouritesDao().gc(deletedAt)
getFavouriteCategoriesDao().gc(deletedAt)
}
}

View File

@@ -20,11 +20,11 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity
class TrackEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "manga_id") val mangaId: Long,
@get:Deprecated(message = "Should not be used", level = DeprecationLevel.ERROR)
@get:Deprecated(message = "Should not be used", level = DeprecationLevel.WARNING)
@ColumnInfo(name = "chapters_total") val totalChapters: Int,
@ColumnInfo(name = "last_chapter_id") val lastChapterId: Long,
@ColumnInfo(name = "chapters_new") val newChapters: Int,
@ColumnInfo(name = "last_check") val lastCheck: Long,
@get:Deprecated(message = "Should not be used", level = DeprecationLevel.ERROR)
@get:Deprecated(message = "Should not be used", level = DeprecationLevel.WARNING)
@ColumnInfo(name = "last_notified_id") val lastNotifiedChapterId: Long
)

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.tracker.data
import androidx.room.Dao
import androidx.room.MapInfo
import androidx.room.MapColumn
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
@@ -23,9 +23,8 @@ abstract class TracksDao {
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
abstract suspend fun findNewChapters(mangaId: Long): Int?
@MapInfo(keyColumn = "manga_id", valueColumn = "chapters_new")
@Query("SELECT manga_id, chapters_new FROM tracks")
abstract fun observeNewChaptersMap(): Flow<Map<Long, Int>>
abstract fun observeNewChaptersMap(): Flow<Map<@MapColumn(columnName = "manga_id") Long, @MapColumn(columnName = "chapters_new") Int>>
@Query("SELECT chapters_new FROM tracks")
abstract fun observeNewChapters(): Flow<List<Int>>

View File

@@ -41,23 +41,23 @@ class TrackingRepository @Inject constructor(
private var isGcCalled = AtomicBoolean(false)
suspend fun getNewChaptersCount(mangaId: Long): Int {
return db.tracksDao.findNewChapters(mangaId) ?: 0
return db.getTracksDao().findNewChapters(mangaId) ?: 0
}
fun observeNewChaptersCount(mangaId: Long): Flow<Int> {
return db.tracksDao.observeNewChapters(mangaId).map { it ?: 0 }
return db.getTracksDao().observeNewChapters(mangaId).map { it ?: 0 }
}
fun observeUpdatedMangaCount(): Flow<Int> {
return db.tracksDao.observeNewChapters().map { list -> list.count { it > 0 } }
return db.getTracksDao().observeNewChapters().map { list -> list.count { it > 0 } }
.onStart { gcIfNotCalled() }
}
fun observeUpdatedManga(limit: Int = 0): Flow<List<Manga>> {
return if (limit == 0) {
db.tracksDao.observeUpdatedManga()
db.getTracksDao().observeUpdatedManga()
} else {
db.tracksDao.observeUpdatedManga(limit)
db.getTracksDao().observeUpdatedManga(limit)
}.mapItems { it.toManga() }
.distinctUntilChanged()
.onStart { gcIfNotCalled() }
@@ -65,7 +65,7 @@ class TrackingRepository @Inject constructor(
suspend fun getTracks(mangaList: Collection<Manga>): List<MangaTracking> {
val ids = mangaList.mapToSet { it.id }
val tracks = db.tracksDao.findAll(ids).groupBy { it.mangaId }
val tracks = db.getTracksDao().findAll(ids).groupBy { it.mangaId }
val idSet = HashSet<Long>()
val result = ArrayList<MangaTracking>(mangaList.size)
for (item in mangaList) {
@@ -89,7 +89,7 @@ class TrackingRepository @Inject constructor(
@VisibleForTesting
suspend fun getTrack(manga: Manga): MangaTracking {
val track = db.tracksDao.find(manga.id)
val track = db.getTracksDao().find(manga.id)
return MangaTracking(
manga = manga,
lastChapterId = track?.lastChapterId ?: NO_ID,
@@ -99,14 +99,14 @@ class TrackingRepository @Inject constructor(
@VisibleForTesting
suspend fun deleteTrack(mangaId: Long) {
db.tracksDao.delete(mangaId)
db.getTracksDao().delete(mangaId)
}
fun observeTrackingLog(limit: Flow<Int>): Flow<List<TrackingLogItem>> {
return limit.flatMapLatest { limitValue ->
combine(
db.tracksDao.observeNewChaptersMap(),
db.trackLogsDao.observeAll(limitValue),
db.getTracksDao().observeNewChaptersMap(),
db.getTrackLogsDao().observeAll(limitValue),
) { counters, entities ->
val countersMap = counters.toMutableMap()
entities.map { x -> x.toTrackingLogItem(countersMap) }
@@ -116,21 +116,21 @@ class TrackingRepository @Inject constructor(
}
}
suspend fun getLogsCount() = db.trackLogsDao.count()
suspend fun getLogsCount() = db.getTrackLogsDao().count()
suspend fun clearLogs() = db.trackLogsDao.clear()
suspend fun clearLogs() = db.getTrackLogsDao().clear()
suspend fun clearCounters() = db.tracksDao.clearCounters()
suspend fun clearCounters() = db.getTracksDao().clearCounters()
suspend fun gc() {
db.tracksDao.gc()
db.trackLogsDao.gc()
db.getTracksDao().gc()
db.getTrackLogsDao().gc()
}
suspend fun saveUpdates(updates: MangaUpdates.Success) {
db.withTransaction {
val track = getOrCreateTrack(updates.manga.id).mergeWith(updates)
db.tracksDao.upsert(track)
db.getTracksDao().upsert(track)
if (updates.isValid && updates.newChapters.isNotEmpty()) {
updatePercent(updates)
val logEntity = TrackLogEntity(
@@ -138,7 +138,7 @@ class TrackingRepository @Inject constructor(
chapters = updates.newChapters.joinToString("\n") { x -> x.name },
createdAt = System.currentTimeMillis(),
)
db.trackLogsDao.insert(logEntity)
db.getTrackLogsDao().insert(logEntity)
}
}
}
@@ -146,10 +146,10 @@ class TrackingRepository @Inject constructor(
suspend fun clearUpdates(ids: Collection<Long>) {
when {
ids.isEmpty() -> return
ids.size == 1 -> db.tracksDao.clearCounter(ids.single())
ids.size == 1 -> db.getTracksDao().clearCounter(ids.single())
else -> db.withTransaction {
for (id in ids) {
db.tracksDao.clearCounter(id)
db.getTracksDao().clearCounter(id)
}
}
}
@@ -174,11 +174,11 @@ class TrackingRepository @Inject constructor(
lastCheck = System.currentTimeMillis(),
lastNotifiedChapterId = lastChapterId,
)
db.tracksDao.upsert(entity)
db.getTracksDao().upsert(entity)
}
suspend fun getCategoriesCount(): IntArray {
val categories = db.favouriteCategoriesDao.findAll()
val categories = db.getFavouriteCategoriesDao().findAll()
return intArrayOf(
categories.count { it.track },
categories.size,
@@ -186,19 +186,19 @@ class TrackingRepository @Inject constructor(
}
suspend fun getAllFavouritesManga(): Map<FavouriteCategory, List<Manga>> {
val categories = db.favouriteCategoriesDao.findAll()
val categories = db.getFavouriteCategoriesDao().findAll()
return categories.associateTo(LinkedHashMap(categories.size)) { categoryEntity ->
categoryEntity.toFavouriteCategory() to
db.favouritesDao.findAllManga(categoryEntity.categoryId).toMangaList()
db.getFavouritesDao().findAllManga(categoryEntity.categoryId).toMangaList()
}
}
suspend fun getAllHistoryManga(): List<Manga> {
return db.historyDao.findAllManga().toMangaList()
return db.getHistoryDao().findAllManga().toMangaList()
}
private suspend fun getOrCreateTrack(mangaId: Long): TrackEntity {
return db.tracksDao.find(mangaId) ?: TrackEntity(
return db.getTracksDao().find(mangaId) ?: TrackEntity(
mangaId = mangaId,
totalChapters = 0,
lastChapterId = 0L,
@@ -209,7 +209,7 @@ class TrackingRepository @Inject constructor(
}
private suspend fun updatePercent(updates: MangaUpdates.Success) {
val history = db.historyDao.find(updates.manga.id) ?: return
val history = db.getHistoryDao().find(updates.manga.id) ?: return
val chapters = updates.manga.chapters
if (chapters.isNullOrEmpty()) {
return
@@ -220,7 +220,7 @@ class TrackingRepository @Inject constructor(
}
val position = (chapters.size - updates.newChapters.size) * history.percent
val newPercent = position / chapters.size.toFloat()
db.historyDao.update(history.copy(percent = newPercent))
db.getHistoryDao().update(history.copy(percent = newPercent))
}
private fun TrackEntity.mergeWith(updates: MangaUpdates.Success): TrackEntity {

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_import"
android:orderInCategory="50"
android:title="@string/_import"
app:showAsAction="never" />
<item
android:id="@+id/action_categories"
android:orderInCategory="50"
android:title="@string/settings"
app:showAsAction="never" />
<item
android:id="@+id/action_grid_size"
android:orderInCategory="50"
android:title="@string/grid_size"
app:showAsAction="never" />
<item
android:id="@+id/action_clear_history"
android:orderInCategory="50"
android:title="@string/clear_history"
app:showAsAction="never" />
</menu>

View File

@@ -493,4 +493,5 @@
<string name="categories">Mga Kategorya</string>
<string name="list_options">Opsyon sa Listahan</string>
<string name="by_relevance">Kaugnayan</string>
<string name="online_variant">Online na baryante</string>
</resources>

View File

@@ -490,4 +490,8 @@
<string name="download_option_all_chapters">Çevirisi %s olan tüm bölümler</string>
<string name="color_black">Siyah</string>
<string name="this_month">Bu ay</string>
<string name="categories">Kategoriler</string>
<string name="list_options">Seçenekleri listele</string>
<string name="online_variant">Çevrimiçi varyant</string>
<string name="by_relevance">Alaka düzeyi</string>
</resources>

View File

@@ -58,7 +58,7 @@
<string name="clear_pages_cache">Очистити кеш сторінок</string>
<string name="text_file_sizes">Б|кБ|МБ|ГБ|ТБ</string>
<string name="standard">Стандартний</string>
<string name="webtoon">Манхва</string>
<string name="webtoon">Вебтун</string>
<string name="read_mode">Режим читання</string>
<string name="grid_size">Розмір сітки</string>
<string name="search_on_s">Пошук по %s</string>
@@ -479,19 +479,19 @@
<string name="directories">Каталоги</string>
<string name="main_screen_sections">Розділи головного екрана</string>
<string name="to_top">Вгору</string>
<string name="zoom_in">Збільшити</string>
<string name="zoom_in">Наблизити</string>
<string name="reader_zoom_buttons_summary">Чи показувати елементи керування масштабуванням у нижньому правому куті</string>
<string name="reader_zoom_buttons">Показати кнопки масштабування</string>
<string name="zoom_out">Зменшити</string>
<string name="zoom_out">Віддалити</string>
<string name="keep_screen_on">Тримати екран увімкненим</string>
<string name="keep_screen_on_summary">Не вимикати екран під час читання манги</string>
<string name="state_abandoned">Кинута</string>
<string name="categories">Категорії</string>
<string name="list_options">Параметри списку</string>
<string name="suggest_new_sources">Пропонуйте нові джерела після оновлення застосунка</string>
<string name="suggest_new_sources">Пропонувати нові джерела після оновлення застосунку</string>
<string name="enhanced_colors_summary">Зменшує смуги, але може вплинути на продуктивність</string>
<string name="by_relevance">Актуальність</string>
<string name="enhanced_colors">32-бітний колірний режим</string>
<string name="suggest_new_sources_summary">Пропонуйте джерела манги, додані в останньому оновленні застосунка</string>
<string name="suggest_new_sources_summary">Пропонує включити нові джерела манґи після оновлення застосунку</string>
<string name="online_variant">Онлайн варіант</string>
</resources>

View File

@@ -1,18 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="new_chapters">
<item quantity="other">%1$d chương mới</item>
</plurals>
<plurals name="chapters">
<item quantity="other">%1$d chương</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="other">%1$d phút trước</item>
</plurals>
<plurals name="hours_ago">
<item quantity="other">%1$d giờ trước</item>
</plurals>
<plurals name="days_ago">
<item quantity="other">%1$d ngày trước</item>
</plurals>
</resources>
<plurals name="new_chapters">
<item quantity="other">%1$d chương mới</item>
</plurals>
<plurals name="chapters">
<item quantity="other">%1$d chương</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="other">%1$d phút trước</item>
</plurals>
<plurals name="hours_ago">
<item quantity="other">%1$d giờ trước</item>
</plurals>
<plurals name="days_ago">
<item quantity="other">%1$d ngày trước</item>
</plurals>
<plurals name="items">
<item quantity="other">%1$d mục</item>
</plurals>
<plurals name="months_ago">
<item quantity="other">%1$d tháng trước</item>
</plurals>
</resources>

View File

@@ -407,7 +407,7 @@
<string name="local_manga_processing">Xử lý các truyện được lưu</string>
<string name="text_delete_local_manga_batch">Xoá vĩnh viễn những thứ được chọn khỏi thiết bị\?</string>
<string name="percent_string_pattern">%1$s%%</string>
<string name="disable_battery_optimization_summary">Giúp kiểm tra chương mới dưới nền dễ dàng hơn</string>
<string name="disable_battery_optimization_summary">Giúp kiểm tra chương mới trong khi chạy dưới nền dễ dàng hơn</string>
<string name="show_on_shelf">Hiển thị trên kệ sách</string>
<string name="mirror_switching">Tự động chọn máy chủ dự phòng</string>
<string name="mirror_switching_summary">Tự động chuyển tên miền sang máy chủ dự phòng (nếu có) nếu gặp lỗi trên tên miền chính</string>

View File

@@ -68,7 +68,7 @@
<string name="downloads">下载</string>
<string name="by_name">名称</string>
<string name="popular">热门</string>
<string name="updated">更新</string>
<string name="updated">最近更新</string>
<string name="sort_order">排序</string>
<string name="filter">筛选</string>
<string name="theme">主题</string>
@@ -483,4 +483,9 @@
<string name="zoom_out">缩小</string>
<string name="keep_screen_on">保持亮屏</string>
<string name="keep_screen_on_summary">阅读漫画时不关闭屏幕</string>
<string name="list_options">列表选项</string>
<string name="suggest_new_sources">推荐新漫画源</string>
<string name="enhanced_colors_summary">减少色带,但可能会影响性能</string>
<string name="enhanced_colors">32位色彩模式</string>
<string name="suggest_new_sources_summary">应用更新后快速启用新增的漫画源</string>
</resources>