Linting fixes

This commit is contained in:
arkon 2020-04-25 14:24:45 -04:00
parent 4da760d614
commit 3f63b320c4
272 changed files with 4167 additions and 3602 deletions

View file

@ -22,7 +22,6 @@ import uy.kohesive.injekt.api.get
class AppModule(val app: Application) : InjektModule { class AppModule(val app: Application) : InjektModule {
override fun InjektRegistrar.registerInjectables() { override fun InjektRegistrar.registerInjectables() {
addSingleton(app) addSingleton(app)
addSingletonFactory { PreferencesHelper(app) } addSingletonFactory { PreferencesHelper(app) }

View file

@ -33,7 +33,8 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
if (interval > 0) { if (interval > 0) {
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>( val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
interval.toLong(), TimeUnit.HOURS, interval.toLong(), TimeUnit.HOURS,
10, TimeUnit.MINUTES) 10, TimeUnit.MINUTES
)
.addTag(TAG) .addTag(TAG)
.build() .build()

View file

@ -85,7 +85,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
private fun initParser(): Gson = when (version) { private fun initParser(): Gson = when (version) {
1 -> GsonBuilder().create() 1 -> GsonBuilder().create()
2 -> GsonBuilder() 2 ->
GsonBuilder()
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build()) .registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build()) .registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build()) .registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
@ -442,8 +443,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking() val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
// Return if fetch is needed // Return if fetch is needed
if (dbChapters.isEmpty() || dbChapters.size < chapters.size) if (dbChapters.isEmpty() || dbChapters.size < chapters.size) {
return false return false
}
for (chapter in chapters) { for (chapter in chapters) {
val pos = dbChapters.indexOf(chapter) val pos = dbChapters.indexOf(chapter)

View file

@ -143,7 +143,8 @@ class BackupRestoreService : Service() {
startForeground(Notifications.ID_RESTORE, notifier.showRestoreProgress().build()) startForeground(Notifications.ID_RESTORE, notifier.showRestoreProgress().build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock") PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock"
)
wakeLock.acquire() wakeLock.acquire()
} }
@ -184,7 +185,8 @@ class BackupRestoreService : Service() {
subscription = Observable.using( subscription = Observable.using(
{ db.lowLevel().beginTransaction() }, { db.lowLevel().beginTransaction() },
{ getRestoreObservable(uri).doOnNext { db.lowLevel().setTransactionSuccessful() } }, { getRestoreObservable(uri).doOnNext { db.lowLevel().setTransactionSuccessful() } },
{ executor.execute { db.lowLevel().endTransaction() } }) { executor.execute { db.lowLevel().endTransaction() } }
)
.doAfterTerminate { stopSelf(startId) } .doAfterTerminate { stopSelf(startId) }
.subscribeOn(Schedulers.from(executor)) .subscribeOn(Schedulers.from(executor))
.subscribe() .subscribe()
@ -231,14 +233,22 @@ class BackupRestoreService : Service() {
.concatMap { .concatMap {
val obj = it.asJsonObject val obj = it.asJsonObject
val manga = backupManager.parser.fromJson<MangaImpl>(obj.get(MANGA)) val manga = backupManager.parser.fromJson<MangaImpl>(obj.get(MANGA))
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(obj.get(CHAPTERS) val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
?: JsonArray()) obj.get(CHAPTERS)
val categories = backupManager.parser.fromJson<List<String>>(obj.get(CATEGORIES) ?: JsonArray()
?: JsonArray()) )
val history = backupManager.parser.fromJson<List<DHistory>>(obj.get(HISTORY) val categories = backupManager.parser.fromJson<List<String>>(
?: JsonArray()) obj.get(CATEGORIES)
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(obj.get(TRACK) ?: JsonArray()
?: JsonArray()) )
val history = backupManager.parser.fromJson<List<DHistory>>(
obj.get(HISTORY)
?: JsonArray()
)
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(
obj.get(TRACK)
?: JsonArray()
)
val observable = getMangaRestoreObservable(manga, chapters, categories, history, tracks) val observable = getMangaRestoreObservable(manga, chapters, categories, history, tracks)
if (observable != null) { if (observable != null) {
@ -379,7 +389,6 @@ class BackupRestoreService : Service() {
history: List<DHistory>, history: List<DHistory>,
tracks: List<Track> tracks: List<Track>
): Observable<Manga> { ): Observable<Manga> {
return Observable.just(backupManga) return Observable.just(backupManga)
.flatMap { manga -> .flatMap { manga ->
if (!backupManager.restoreChaptersForManga(manga, chapters)) { if (!backupManager.restoreChaptersForManga(manga, chapters)) {

View file

@ -46,10 +46,12 @@ class ChapterCache(private val context: Context) {
private val gson: Gson by injectLazy() private val gson: Gson by injectLazy()
/** Cache class used for cache management. */ /** Cache class used for cache management. */
private val diskCache = DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY), private val diskCache = DiskLruCache.open(
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
PARAMETER_APP_VERSION, PARAMETER_APP_VERSION,
PARAMETER_VALUE_COUNT, PARAMETER_VALUE_COUNT,
PARAMETER_CACHE_SIZE) PARAMETER_CACHE_SIZE
)
/** /**
* Returns directory of cache. * Returns directory of cache.
@ -77,8 +79,9 @@ class ChapterCache(private val context: Context) {
*/ */
fun removeFileFromCache(file: String): Boolean { fun removeFileFromCache(file: String): Boolean {
// Make sure we don't delete the journal file (keeps track of cache). // Make sure we don't delete the journal file (keeps track of cache).
if (file == "journal" || file.startsWith("journal.")) if (file == "journal" || file.startsWith("journal.")) {
return false return false
}
return try { return try {
// Remove the extension from the file to get the key of the cache // Remove the extension from the file to get the key of the cache

View file

@ -56,8 +56,9 @@ class CoverCache(private val context: Context) {
*/ */
fun deleteFromCache(thumbnailUrl: String?): Boolean { fun deleteFromCache(thumbnailUrl: String?): Boolean {
// Check if url is empty. // Check if url is empty.
if (thumbnailUrl.isNullOrEmpty()) if (thumbnailUrl.isNullOrEmpty()) {
return false return false
}
// Remove file. // Remove file.
val file = getCoverFile(thumbnailUrl) val file = getCoverFile(thumbnailUrl)

View file

@ -44,8 +44,10 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
db.execSQL(ChapterTable.sourceOrderUpdateQuery) db.execSQL(ChapterTable.sourceOrderUpdateQuery)
// Fix kissmanga covers after supporting cloudflare // Fix kissmanga covers after supporting cloudflare
db.execSQL("""UPDATE mangas SET thumbnail_url = db.execSQL(
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""") """UPDATE mangas SET thumbnail_url =
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4"""
)
} }
if (oldVersion < 3) { if (oldVersion < 3) {
// Initialize history tables // Initialize history tables

View file

@ -11,18 +11,22 @@ interface CategoryQueries : DbProvider {
fun getCategories() = db.get() fun getCategories() = db.get()
.listOfObjects(Category::class.java) .listOfObjects(Category::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(CategoryTable.TABLE) .table(CategoryTable.TABLE)
.orderBy(CategoryTable.COL_ORDER) .orderBy(CategoryTable.COL_ORDER)
.build()) .build()
)
.prepare() .prepare()
fun getCategoriesForManga(manga: Manga) = db.get() fun getCategoriesForManga(manga: Manga) = db.get()
.listOfObjects(Category::class.java) .listOfObjects(Category::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getCategoriesForMangaQuery()) .query(getCategoriesForMangaQuery())
.args(manga.id) .args(manga.id)
.build()) .build()
)
.prepare() .prepare()
fun insertCategory(category: Category) = db.put().`object`(category).prepare() fun insertCategory(category: Category) = db.put().`object`(category).prepare()

View file

@ -17,48 +17,58 @@ interface ChapterQueries : DbProvider {
fun getChapters(manga: Manga) = db.get() fun getChapters(manga: Manga) = db.get()
.listOfObjects(Chapter::class.java) .listOfObjects(Chapter::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(ChapterTable.TABLE) .table(ChapterTable.TABLE)
.where("${ChapterTable.COL_MANGA_ID} = ?") .where("${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(manga.id) .whereArgs(manga.id)
.build()) .build()
)
.prepare() .prepare()
fun getRecentChapters(date: Date) = db.get() fun getRecentChapters(date: Date) = db.get()
.listOfObjects(MangaChapter::class.java) .listOfObjects(MangaChapter::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getRecentsQuery()) .query(getRecentsQuery())
.args(date.time) .args(date.time)
.observesTables(ChapterTable.TABLE) .observesTables(ChapterTable.TABLE)
.build()) .build()
)
.withGetResolver(MangaChapterGetResolver.INSTANCE) .withGetResolver(MangaChapterGetResolver.INSTANCE)
.prepare() .prepare()
fun getChapter(id: Long) = db.get() fun getChapter(id: Long) = db.get()
.`object`(Chapter::class.java) .`object`(Chapter::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(ChapterTable.TABLE) .table(ChapterTable.TABLE)
.where("${ChapterTable.COL_ID} = ?") .where("${ChapterTable.COL_ID} = ?")
.whereArgs(id) .whereArgs(id)
.build()) .build()
)
.prepare() .prepare()
fun getChapter(url: String) = db.get() fun getChapter(url: String) = db.get()
.`object`(Chapter::class.java) .`object`(Chapter::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(ChapterTable.TABLE) .table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?") .where("${ChapterTable.COL_URL} = ?")
.whereArgs(url) .whereArgs(url)
.build()) .build()
)
.prepare() .prepare()
fun getChapter(url: String, mangaId: Long) = db.get() fun getChapter(url: String, mangaId: Long) = db.get()
.`object`(Chapter::class.java) .`object`(Chapter::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(ChapterTable.TABLE) .table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?") .where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(url, mangaId) .whereArgs(url, mangaId)
.build()) .build()
)
.prepare() .prepare()
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare() fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()

View file

@ -24,30 +24,36 @@ interface HistoryQueries : DbProvider {
*/ */
fun getRecentManga(date: Date) = db.get() fun getRecentManga(date: Date) = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getRecentMangasQuery()) .query(getRecentMangasQuery())
.args(date.time) .args(date.time)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build()) .build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare() .prepare()
fun getHistoryByMangaId(mangaId: Long) = db.get() fun getHistoryByMangaId(mangaId: Long) = db.get()
.listOfObjects(History::class.java) .listOfObjects(History::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getHistoryByMangaId()) .query(getHistoryByMangaId())
.args(mangaId) .args(mangaId)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
fun getHistoryByChapterUrl(chapterUrl: String) = db.get() fun getHistoryByChapterUrl(chapterUrl: String) = db.get()
.`object`(History::class.java) .`object`(History::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getHistoryByChapterUrl()) .query(getHistoryByChapterUrl())
.args(chapterUrl) .args(chapterUrl)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
/** /**
@ -71,16 +77,20 @@ interface HistoryQueries : DbProvider {
.prepare() .prepare()
fun deleteHistory() = db.delete() fun deleteHistory() = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
DeleteQuery.builder()
.table(HistoryTable.TABLE) .table(HistoryTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
fun deleteHistoryNoLastRead() = db.delete() fun deleteHistoryNoLastRead() = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
DeleteQuery.builder()
.table(HistoryTable.TABLE) .table(HistoryTable.TABLE)
.where("${HistoryTable.COL_LAST_READ} = ?") .where("${HistoryTable.COL_LAST_READ} = ?")
.whereArgs(0) .whereArgs(0)
.build()) .build()
)
.prepare() .prepare()
} }

View file

@ -15,11 +15,13 @@ interface MangaCategoryQueries : DbProvider {
fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare() fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare()
fun deleteOldMangasCategories(mangas: List<Manga>) = db.delete() fun deleteOldMangasCategories(mangas: List<Manga>) = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
DeleteQuery.builder()
.table(MangaCategoryTable.TABLE) .table(MangaCategoryTable.TABLE)
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})") .where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
.whereArgs(*mangas.map { it.id }.toTypedArray()) .whereArgs(*mangas.map { it.id }.toTypedArray())
.build()) .build()
)
.prepare() .prepare()
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) { fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {

View file

@ -21,46 +21,56 @@ interface MangaQueries : DbProvider {
fun getMangas() = db.get() fun getMangas() = db.get()
.listOfObjects(Manga::class.java) .listOfObjects(Manga::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
fun getLibraryMangas() = db.get() fun getLibraryMangas() = db.get()
.listOfObjects(LibraryManga::class.java) .listOfObjects(LibraryManga::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(libraryQuery) .query(libraryQuery)
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE) .observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
.build()) .build()
)
.withGetResolver(LibraryMangaGetResolver.INSTANCE) .withGetResolver(LibraryMangaGetResolver.INSTANCE)
.prepare() .prepare()
fun getFavoriteMangas() = db.get() fun getFavoriteMangas() = db.get()
.listOfObjects(Manga::class.java) .listOfObjects(Manga::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?") .where("${MangaTable.COL_FAVORITE} = ?")
.whereArgs(1) .whereArgs(1)
.orderBy(MangaTable.COL_TITLE) .orderBy(MangaTable.COL_TITLE)
.build()) .build()
)
.prepare() .prepare()
fun getManga(url: String, sourceId: Long) = db.get() fun getManga(url: String, sourceId: Long) = db.get()
.`object`(Manga::class.java) .`object`(Manga::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?") .where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
.whereArgs(url, sourceId) .whereArgs(url, sourceId)
.build()) .build()
)
.prepare() .prepare()
fun getManga(id: Long) = db.get() fun getManga(id: Long) = db.get()
.`object`(Manga::class.java) .`object`(Manga::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?") .where("${MangaTable.COL_ID} = ?")
.whereArgs(id) .whereArgs(id)
.build()) .build()
)
.prepare() .prepare()
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare() fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
@ -97,40 +107,50 @@ interface MangaQueries : DbProvider {
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare() fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
fun deleteMangasNotInLibrary() = db.delete() fun deleteMangasNotInLibrary() = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
DeleteQuery.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?") .where("${MangaTable.COL_FAVORITE} = ?")
.whereArgs(0) .whereArgs(0)
.build()) .build()
)
.prepare() .prepare()
fun deleteMangas() = db.delete() fun deleteMangas() = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
DeleteQuery.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
fun getLastReadManga() = db.get() fun getLastReadManga() = db.get()
.listOfObjects(Manga::class.java) .listOfObjects(Manga::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getLastReadMangaQuery()) .query(getLastReadMangaQuery())
.observesTables(MangaTable.TABLE) .observesTables(MangaTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
fun getTotalChapterManga() = db.get() fun getTotalChapterManga() = db.get()
.listOfObjects(Manga::class.java) .listOfObjects(Manga::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getTotalChapterMangaQuery()) .query(getTotalChapterMangaQuery())
.observesTables(MangaTable.TABLE) .observesTables(MangaTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
fun getLatestChapterManga() = db.get() fun getLatestChapterManga() = db.get()
.listOfObjects(Manga::class.java) .listOfObjects(Manga::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getLatestChapterMangaQuery()) .query(getLatestChapterMangaQuery())
.observesTables(MangaTable.TABLE) .observesTables(MangaTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
} }

View file

@ -9,7 +9,8 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
/** /**
* Query to get the manga from the library, with their categories and unread count. * Query to get the manga from the library, with their categories and unread count.
*/ */
val libraryQuery = """ val libraryQuery =
"""
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY} SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
FROM ( FROM (
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD} SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}
@ -33,7 +34,8 @@ val libraryQuery = """
/** /**
* Query to get the recent chapters of manga from the library up to a date. * Query to get the recent chapters of manga from the library up to a date.
*/ */
fun getRecentsQuery() = """ fun getRecentsQuery() =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE} SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Chapter.COL_DATE_UPLOAD} > ? WHERE ${Manga.COL_FAVORITE} = 1 AND ${Chapter.COL_DATE_UPLOAD} > ?
@ -47,7 +49,8 @@ fun getRecentsQuery() = """
* and are read after the given time period * and are read after the given time period
* @return return limit is 25 * @return return limit is 25
*/ */
fun getRecentMangasQuery() = """ fun getRecentMangasQuery() =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -65,7 +68,8 @@ fun getRecentMangasQuery() = """
LIMIT 25 LIMIT 25
""" """
fun getHistoryByMangaId() = """ fun getHistoryByMangaId() =
"""
SELECT ${History.TABLE}.* SELECT ${History.TABLE}.*
FROM ${History.TABLE} FROM ${History.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -73,7 +77,8 @@ fun getHistoryByMangaId() = """
WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID} WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
""" """
fun getHistoryByChapterUrl() = """ fun getHistoryByChapterUrl() =
"""
SELECT ${History.TABLE}.* SELECT ${History.TABLE}.*
FROM ${History.TABLE} FROM ${History.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -81,7 +86,8 @@ fun getHistoryByChapterUrl() = """
WHERE ${Chapter.TABLE}.${Chapter.COL_URL} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID} WHERE ${Chapter.TABLE}.${Chapter.COL_URL} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
""" """
fun getLastReadMangaQuery() = """ fun getLastReadMangaQuery() =
"""
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -93,7 +99,8 @@ fun getLastReadMangaQuery() = """
ORDER BY max DESC ORDER BY max DESC
""" """
fun getTotalChapterMangaQuery() = """ fun getTotalChapterMangaQuery() =
"""
SELECT ${Manga.TABLE}.* SELECT ${Manga.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -102,7 +109,8 @@ fun getTotalChapterMangaQuery() = """
ORDER by COUNT(*) ORDER by COUNT(*)
""" """
fun getLatestChapterMangaQuery() = """ fun getLatestChapterMangaQuery() =
"""
SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD}) AS max SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD}) AS max
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -114,7 +122,8 @@ fun getLatestChapterMangaQuery() = """
/** /**
* Query to get the categories for a manga. * Query to get the categories for a manga.
*/ */
fun getCategoriesForMangaQuery() = """ fun getCategoriesForMangaQuery() =
"""
SELECT ${Category.TABLE}.* FROM ${Category.TABLE} SELECT ${Category.TABLE}.* FROM ${Category.TABLE}
JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} = JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} =
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID} ${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}

View file

@ -12,11 +12,13 @@ interface TrackQueries : DbProvider {
fun getTracks(manga: Manga) = db.get() fun getTracks(manga: Manga) = db.get()
.listOfObjects(Track::class.java) .listOfObjects(Track::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(TrackTable.TABLE) .table(TrackTable.TABLE)
.where("${TrackTable.COL_MANGA_ID} = ?") .where("${TrackTable.COL_MANGA_ID} = ?")
.whereArgs(manga.id) .whereArgs(manga.id)
.build()) .build()
)
.prepare() .prepare()
fun insertTrack(track: Track) = db.put().`object`(track).prepare() fun insertTrack(track: Track) = db.put().`object`(track).prepare()
@ -24,10 +26,12 @@ interface TrackQueries : DbProvider {
fun insertTracks(tracks: List<Track>) = db.put().objects(tracks).prepare() fun insertTracks(tracks: List<Track>) = db.put().objects(tracks).prepare()
fun deleteTrackForManga(manga: Manga, sync: TrackService) = db.delete() fun deleteTrackForManga(manga: Manga, sync: TrackService) = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
DeleteQuery.builder()
.table(TrackTable.TABLE) .table(TrackTable.TABLE)
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?") .where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
.whereArgs(manga.id, sync.id) .whereArgs(manga.id, sync.id)
.build()) .build()
)
.prepare() .prepare()
} }

View file

@ -19,11 +19,13 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn { override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(history) val updateQuery = mapToUpdateQuery(history)
val cursor = db.lowLevel().query(Query.builder() val cursor = db.lowLevel().query(
Query.builder()
.table(updateQuery.table()) .table(updateQuery.table())
.where(updateQuery.where()) .where(updateQuery.where())
.whereArgs(updateQuery.whereArgs()) .whereArgs(updateQuery.whereArgs())
.build()) .build()
)
val putResult: PutResult val putResult: PutResult

View file

@ -13,7 +13,8 @@ object CategoryTable {
const val COL_FLAGS = "flags" const val COL_FLAGS = "flags"
val createTableQuery: String val createTableQuery: String
get() = """CREATE TABLE $TABLE( get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_NAME TEXT NOT NULL, $COL_NAME TEXT NOT NULL,
$COL_ORDER INTEGER NOT NULL, $COL_ORDER INTEGER NOT NULL,

View file

@ -29,7 +29,8 @@ object ChapterTable {
const val COL_SOURCE_ORDER = "source_order" const val COL_SOURCE_ORDER = "source_order"
val createTableQuery: String val createTableQuery: String
get() = """CREATE TABLE $TABLE( get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_MANGA_ID INTEGER NOT NULL, $COL_MANGA_ID INTEGER NOT NULL,
$COL_URL TEXT NOT NULL, $COL_URL TEXT NOT NULL,

View file

@ -31,7 +31,8 @@ object HistoryTable {
* query to create history table * query to create history table
*/ */
val createTableQuery: String val createTableQuery: String
get() = """CREATE TABLE $TABLE( get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_CHAPTER_ID INTEGER NOT NULL UNIQUE, $COL_CHAPTER_ID INTEGER NOT NULL UNIQUE,
$COL_LAST_READ LONG, $COL_LAST_READ LONG,

View file

@ -11,7 +11,8 @@ object MangaCategoryTable {
const val COL_CATEGORY_ID = "category_id" const val COL_CATEGORY_ID = "category_id"
val createTableQuery: String val createTableQuery: String
get() = """CREATE TABLE $TABLE( get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_MANGA_ID INTEGER NOT NULL, $COL_MANGA_ID INTEGER NOT NULL,
$COL_CATEGORY_ID INTEGER NOT NULL, $COL_CATEGORY_ID INTEGER NOT NULL,

View file

@ -39,7 +39,8 @@ object MangaTable {
const val COL_CATEGORY = "category" const val COL_CATEGORY = "category"
val createTableQuery: String val createTableQuery: String
get() = """CREATE TABLE $TABLE( get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_SOURCE INTEGER NOT NULL, $COL_SOURCE INTEGER NOT NULL,
$COL_URL TEXT NOT NULL, $COL_URL TEXT NOT NULL,

View file

@ -31,7 +31,8 @@ object TrackTable {
const val COL_FINISH_DATE = "finish_date" const val COL_FINISH_DATE = "finish_date"
val createTableQuery: String val createTableQuery: String
get() = """CREATE TABLE $TABLE( get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_MANGA_ID INTEGER NOT NULL, $COL_MANGA_ID INTEGER NOT NULL,
$COL_SYNC_ID INTEGER NOT NULL, $COL_SYNC_ID INTEGER NOT NULL,

View file

@ -87,9 +87,11 @@ internal class DownloadNotifier(private val context: Context) {
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
isDownloading = true isDownloading = true
// Pause action // Pause action
addAction(R.drawable.ic_pause_24dp, addAction(
R.drawable.ic_pause_24dp,
context.getString(R.string.action_pause), context.getString(R.string.action_pause),
NotificationReceiver.pauseDownloadsPendingBroadcast(context)) NotificationReceiver.pauseDownloadsPendingBroadcast(context)
)
} }
val downloadingProgressText = context.getString(R.string.chapter_downloading_progress) val downloadingProgressText = context.getString(R.string.chapter_downloading_progress)
@ -126,13 +128,17 @@ internal class DownloadNotifier(private val context: Context) {
// Open download manager when clicked // Open download manager when clicked
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
// Resume action // Resume action
addAction(R.drawable.ic_play_arrow_24dp, addAction(
R.drawable.ic_play_arrow_24dp,
context.getString(R.string.action_resume), context.getString(R.string.action_resume),
NotificationReceiver.resumeDownloadsPendingBroadcast(context)) NotificationReceiver.resumeDownloadsPendingBroadcast(context)
)
// Clear action // Clear action
addAction(R.drawable.ic_close_24dp, addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel_all), context.getString(R.string.action_cancel_all),
NotificationReceiver.clearDownloadsPendingBroadcast(context)) NotificationReceiver.clearDownloadsPendingBroadcast(context)
)
} }
// Show notification. // Show notification.
@ -173,8 +179,10 @@ internal class DownloadNotifier(private val context: Context) {
fun onError(error: String? = null, chapter: String? = null) { fun onError(error: String? = null, chapter: String? = null) {
// Create notification // Create notification
with(notificationBuilder) { with(notificationBuilder) {
setContentTitle(chapter setContentTitle(
?: context.getString(R.string.download_notifier_downloader_title)) chapter
?: context.getString(R.string.download_notifier_downloader_title)
)
setContentText(error ?: context.getString(R.string.download_notifier_unknown_error)) setContentText(error ?: context.getString(R.string.download_notifier_unknown_error))
setSmallIcon(android.R.drawable.stat_sys_warning) setSmallIcon(android.R.drawable.stat_sys_warning)
clearActions() clearActions()

View file

@ -125,12 +125,15 @@ class DownloadService : Service() {
subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext) subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ state -> .subscribe(
{ state ->
onNetworkStateChanged(state) onNetworkStateChanged(state)
}, { },
{
toast(R.string.download_queue_error) toast(R.string.download_queue_error)
stopSelf() stopSelf()
}) }
)
} }
/** /**
@ -162,12 +165,13 @@ class DownloadService : Service() {
*/ */
private fun listenDownloaderState() { private fun listenDownloaderState() {
subscriptions += downloadManager.runningRelay.subscribe { running -> subscriptions += downloadManager.runningRelay.subscribe { running ->
if (running) if (running) {
wakeLock.acquireIfNeeded() wakeLock.acquireIfNeeded()
else } else {
wakeLock.releaseIfNeeded() wakeLock.releaseIfNeeded()
} }
} }
}
/** /**
* Releases the wake lock if it's held. * Releases the wake lock if it's held.

View file

@ -100,11 +100,13 @@ class Downloader(
* @return true if the downloader is started, false otherwise. * @return true if the downloader is started, false otherwise.
*/ */
fun start(): Boolean { fun start(): Boolean {
if (isRunning || queue.isEmpty()) if (isRunning || queue.isEmpty()) {
return false return false
}
if (!subscriptions.hasSubscriptions()) if (!subscriptions.hasSubscriptions()) {
initializeSubscriptions() initializeSubscriptions()
}
val pending = queue.filter { it.status != Download.DOWNLOADED } val pending = queue.filter { it.status != Download.DOWNLOADED }
pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE } pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE }
@ -177,13 +179,16 @@ class Downloader(
.concatMap { downloadChapter(it).subscribeOn(Schedulers.io()) } .concatMap { downloadChapter(it).subscribeOn(Schedulers.io()) }
.onBackpressureBuffer() .onBackpressureBuffer()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ .subscribe(
{
completeDownload(it) completeDownload(it)
}, { error -> },
{ error ->
DownloadService.stop(context) DownloadService.stop(context)
Timber.e(error) Timber.e(error)
notifier.onError(error.message) notifier.onError(error.message)
}) }
)
} }
/** /**
@ -304,8 +309,9 @@ class Downloader(
*/ */
private fun getOrDownloadImage(page: Page, download: Download, tmpDir: UniFile): Observable<Page> { private fun getOrDownloadImage(page: Page, download: Download, tmpDir: UniFile): Observable<Page> {
// If the image URL is empty, do nothing // If the image URL is empty, do nothing
if (page.imageUrl == null) if (page.imageUrl == null) {
return Observable.just(page) return Observable.just(page)
}
val filename = String.format("%03d", page.number) val filename = String.format("%03d", page.number)
val tmpFile = tmpDir.findFile("$filename.tmp") val tmpFile = tmpDir.findFile("$filename.tmp")
@ -317,10 +323,11 @@ class Downloader(
val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") } val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") }
// If the image is already downloaded, do nothing. Otherwise download from network // If the image is already downloaded, do nothing. Otherwise download from network
val pageObservable = if (imageFile != null) val pageObservable = if (imageFile != null) {
Observable.just(imageFile) Observable.just(imageFile)
else } else {
downloadImage(page, download.source, tmpDir, filename) downloadImage(page, download.source, tmpDir, filename)
}
return pageObservable return pageObservable
// When the image is ready, set image path, progress (just in case) and status // When the image is ready, set image path, progress (just in case) and status
@ -400,7 +407,6 @@ class Downloader(
tmpDir: UniFile, tmpDir: UniFile,
dirname: String dirname: String
) { ) {
// Ensure that the chapter folder has all the images. // Ensure that the chapter folder has all the images.
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") } val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") }

View file

@ -25,7 +25,9 @@ class LibraryMangaUrlFetcher(
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) { override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
if (!file.exists()) { if (!file.exists()) {
networkFetcher.loadData(priority, object : DataFetcher.DataCallback<InputStream> { networkFetcher.loadData(
priority,
object : DataFetcher.DataCallback<InputStream> {
override fun onDataReady(data: InputStream?) { override fun onDataReady(data: InputStream?) {
if (data != null) { if (data != null) {
val tmpFile = File(file.path + ".tmp") val tmpFile = File(file.path + ".tmp")
@ -54,7 +56,8 @@ class LibraryMangaUrlFetcher(
override fun onLoadFailed(e: Exception) { override fun onLoadFailed(e: Exception) {
callback.onLoadFailed(e) callback.onLoadFailed(e)
} }
}) }
)
} else { } else {
loadFromFile(callback) loadFromFile(callback)
} }

View file

@ -27,8 +27,10 @@ class TachiGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) { override fun applyOptions(context: Context, builder: GlideBuilder) {
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024)) builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024))
builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565)) builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
builder.setDefaultTransitionOptions(Drawable::class.java, builder.setDefaultTransitionOptions(
DrawableTransitionOptions.withCrossFade()) Drawable::class.java,
DrawableTransitionOptions.withCrossFade()
)
} }
override fun registerComponents(context: Context, glide: Glide, registry: Registry) { override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
@ -36,7 +38,10 @@ class TachiGlideModule : AppGlideModule() {
registry.replace(GlideUrl::class.java, InputStream::class.java, networkFactory) registry.replace(GlideUrl::class.java, InputStream::class.java, networkFactory)
registry.append(MangaThumbnail::class.java, InputStream::class.java, MangaThumbnailModelLoader.Factory()) registry.append(MangaThumbnail::class.java, InputStream::class.java, MangaThumbnailModelLoader.Factory())
registry.append(InputStream::class.java, InputStream::class.java, PassthroughModelLoader registry.append(
.Factory()) InputStream::class.java, InputStream::class.java,
PassthroughModelLoader
.Factory()
)
} }
} }

View file

@ -30,10 +30,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
if (interval > 0) { if (interval > 0) {
val restrictions = preferences.libraryUpdateRestriction()!! val restrictions = preferences.libraryUpdateRestriction()!!
val acRestriction = "ac" in restrictions val acRestriction = "ac" in restrictions
val wifiRestriction = if ("wifi" in restrictions) val wifiRestriction = if ("wifi" in restrictions) {
NetworkType.UNMETERED NetworkType.UNMETERED
else } else {
NetworkType.CONNECTED NetworkType.CONNECTED
}
val constraints = Constraints.Builder() val constraints = Constraints.Builder()
.setRequiredNetworkType(wifiRestriction) .setRequiredNetworkType(wifiRestriction)
@ -42,7 +43,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>( val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
interval.toLong(), TimeUnit.HOURS, interval.toLong(), TimeUnit.HOURS,
10, TimeUnit.MINUTES) 10, TimeUnit.MINUTES
)
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()

View file

@ -9,7 +9,8 @@ object LibraryUpdateRanker {
val rankingScheme = listOf( val rankingScheme = listOf(
(this::lexicographicRanking)(), (this::lexicographicRanking)(),
(this::latestFirstRanking)()) (this::latestFirstRanking)()
)
/** /**
* Provides a total ordering over all the Mangas. * Provides a total ordering over all the Mangas.

View file

@ -184,7 +184,8 @@ class LibraryUpdateService(
super.onCreate() super.onCreate()
startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotificationBuilder.build()) startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotificationBuilder.build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock") PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock"
)
wakeLock.acquire() wakeLock.acquire()
} }
@ -238,13 +239,17 @@ class LibraryUpdateService(
} }
} }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe({ .subscribe(
}, { {
},
{
Timber.e(it) Timber.e(it)
stopSelf(startId) stopSelf(startId)
}, { },
{
stopSelf(startId) stopSelf(startId)
}) }
)
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
@ -259,17 +264,18 @@ class LibraryUpdateService(
fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> { fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> {
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1) val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
var listToUpdate = if (categoryId != -1) var listToUpdate = if (categoryId != -1) {
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId } db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
else { } else {
val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt) val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt)
if (categoriesToUpdate.isNotEmpty()) if (categoriesToUpdate.isNotEmpty()) {
db.getLibraryMangas().executeAsBlocking() db.getLibraryMangas().executeAsBlocking()
.filter { it.category in categoriesToUpdate } .filter { it.category in categoriesToUpdate }
.distinctBy { it.id } .distinctBy { it.id }
else } else {
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id } db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
} }
}
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) { if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED } listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
} }
@ -315,9 +321,11 @@ class LibraryUpdateService(
// Filter out mangas without new chapters (or failed). // Filter out mangas without new chapters (or failed).
.filter { pair -> pair.first.isNotEmpty() } .filter { pair -> pair.first.isNotEmpty() }
.doOnNext { .doOnNext {
if (downloadNew && (categoriesToDownload.isEmpty() || if (downloadNew && (
manga.category in categoriesToDownload)) { categoriesToDownload.isEmpty() ||
manga.category in categoriesToDownload
)
) {
downloadChapters(manga, it.first) downloadChapters(manga, it.first)
hasDownloads = true hasDownloads = true
} }
@ -453,15 +461,19 @@ class LibraryUpdateService(
* @param total the total progress. * @param total the total progress.
*/ */
private fun showProgressNotification(manga: Manga, current: Int, total: Int) { private fun showProgressNotification(manga: Manga, current: Int, total: Int) {
val title = if (preferences.hideNotificationContent()) val title = if (preferences.hideNotificationContent()) {
getString(R.string.notification_check_updates) getString(R.string.notification_check_updates)
else } else {
manga.title manga.title
}
notificationManager.notify(Notifications.ID_LIBRARY_PROGRESS, progressNotificationBuilder notificationManager.notify(
Notifications.ID_LIBRARY_PROGRESS,
progressNotificationBuilder
.setContentTitle(title) .setContentTitle(title)
.setProgress(total, current, false) .setProgress(total, current, false)
.build()) .build()
)
} }
/** /**
@ -476,7 +488,9 @@ class LibraryUpdateService(
NotificationManagerCompat.from(this).apply { NotificationManagerCompat.from(this).apply {
// Parent group notification // Parent group notification
notify(Notifications.ID_NEW_CHAPTERS, notification(Notifications.CHANNEL_NEW_CHAPTERS) { notify(
Notifications.ID_NEW_CHAPTERS,
notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setContentTitle(getString(R.string.notification_new_chapters)) setContentTitle(getString(R.string.notification_new_chapters))
if (updates.size == 1 && !preferences.hideNotificationContent()) { if (updates.size == 1 && !preferences.hideNotificationContent()) {
setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN)) setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
@ -484,9 +498,13 @@ class LibraryUpdateService(
setContentText(resources.getQuantityString(R.plurals.notification_new_chapters_summary, updates.size, updates.size)) setContentText(resources.getQuantityString(R.plurals.notification_new_chapters_summary, updates.size, updates.size))
if (!preferences.hideNotificationContent()) { if (!preferences.hideNotificationContent()) {
setStyle(NotificationCompat.BigTextStyle().bigText(updates.joinToString("\n") { setStyle(
NotificationCompat.BigTextStyle().bigText(
updates.joinToString("\n") {
it.first.title.chop(NOTIF_TITLE_MAX_LEN) it.first.title.chop(NOTIF_TITLE_MAX_LEN)
})) }
)
)
} }
} }
@ -500,7 +518,8 @@ class LibraryUpdateService(
setContentIntent(getNotificationIntent()) setContentIntent(getNotificationIntent())
setAutoCancel(true) setAutoCancel(true)
}) }
)
// Per-manga notification // Per-manga notification
if (!preferences.hideNotificationContent()) { if (!preferences.hideNotificationContent()) {
@ -536,13 +555,21 @@ class LibraryUpdateService(
setAutoCancel(true) setAutoCancel(true)
// Mark chapters as read action // Mark chapters as read action
addAction(R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read), addAction(
NotificationReceiver.markAsReadPendingBroadcast(this@LibraryUpdateService, R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read),
manga, chapters, Notifications.ID_NEW_CHAPTERS)) NotificationReceiver.markAsReadPendingBroadcast(
this@LibraryUpdateService,
manga, chapters, Notifications.ID_NEW_CHAPTERS
)
)
// View chapters action // View chapters action
addAction(R.drawable.ic_book_24dp, getString(R.string.action_view_chapters), addAction(
NotificationReceiver.openChapterPendingActivity(this@LibraryUpdateService, R.drawable.ic_book_24dp, getString(R.string.action_view_chapters),
manga, Notifications.ID_NEW_CHAPTERS)) NotificationReceiver.openChapterPendingActivity(
this@LibraryUpdateService,
manga, Notifications.ID_NEW_CHAPTERS
)
)
} }
} }
@ -570,8 +597,11 @@ class LibraryUpdateService(
} }
private fun getNewChaptersDescription(chapters: Array<Chapter>): String { private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
val formatter = DecimalFormat("#.###", DecimalFormatSymbols() val formatter = DecimalFormat(
.apply { decimalSeparator = '.' }) "#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' }
)
val displayableChapterNumbers = chapters val displayableChapterNumbers = chapters
.filter { it.isRecognizedNumber } .filter { it.isRecognizedNumber }

View file

@ -54,22 +54,35 @@ class NotificationReceiver : BroadcastReceiver() {
// Clear the download queue // Clear the download queue
ACTION_CLEAR_DOWNLOADS -> downloadManager.clearQueue(true) ACTION_CLEAR_DOWNLOADS -> downloadManager.clearQueue(true)
// Launch share activity and dismiss notification // Launch share activity and dismiss notification
ACTION_SHARE_IMAGE -> shareImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION), ACTION_SHARE_IMAGE ->
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) shareImage(
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
// Delete image from path and dismiss notification // Delete image from path and dismiss notification
ACTION_DELETE_IMAGE -> deleteImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION), ACTION_DELETE_IMAGE ->
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) deleteImage(
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
// Share backup file // Share backup file
ACTION_SHARE_BACKUP -> shareBackup(context, intent.getParcelableExtra(EXTRA_URI), ACTION_SHARE_BACKUP ->
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) shareBackup(
ACTION_CANCEL_RESTORE -> cancelRestore(context, context, intent.getParcelableExtra(EXTRA_URI),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
ACTION_CANCEL_RESTORE -> cancelRestore(
context,
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
// Cancel library update and dismiss notification // Cancel library update and dismiss notification
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context, Notifications.ID_LIBRARY_PROGRESS) ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context, Notifications.ID_LIBRARY_PROGRESS)
// Open reader activity // Open reader activity
ACTION_OPEN_CHAPTER -> { ACTION_OPEN_CHAPTER -> {
openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1), openChapter(
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)) context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
)
} }
// Mark updated manga chapters as read // Mark updated manga chapters as read
ACTION_MARK_AS_READ -> { ACTION_MARK_AS_READ -> {

View file

@ -61,22 +61,34 @@ object Notifications {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val channels = listOf( val channels = listOf(
NotificationChannel(CHANNEL_COMMON, context.getString(R.string.channel_common), NotificationChannel(
NotificationManager.IMPORTANCE_LOW), CHANNEL_COMMON, context.getString(R.string.channel_common),
NotificationChannel(CHANNEL_LIBRARY, context.getString(R.string.channel_library), NotificationManager.IMPORTANCE_LOW
NotificationManager.IMPORTANCE_LOW).apply { ),
NotificationChannel(
CHANNEL_LIBRARY, context.getString(R.string.channel_library),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel(CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader), NotificationChannel(
NotificationManager.IMPORTANCE_LOW).apply { CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel(CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters), NotificationChannel(
NotificationManager.IMPORTANCE_DEFAULT), CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters),
NotificationChannel(CHANNEL_UPDATES_TO_EXTS, context.getString(R.string.channel_ext_updates), NotificationManager.IMPORTANCE_DEFAULT
NotificationManager.IMPORTANCE_DEFAULT), ),
NotificationChannel(CHANNEL_BACKUP_RESTORE, context.getString(R.string.channel_backup_restore), NotificationChannel(
NotificationManager.IMPORTANCE_HIGH).apply { CHANNEL_UPDATES_TO_EXTS, context.getString(R.string.channel_ext_updates),
NotificationManager.IMPORTANCE_DEFAULT
),
NotificationChannel(
CHANNEL_BACKUP_RESTORE, context.getString(R.string.channel_backup_restore),
NotificationManager.IMPORTANCE_HIGH
).apply {
setShowBadge(false) setShowBadge(false)
} }
) )

View file

@ -45,12 +45,20 @@ class PreferencesHelper(val context: Context) {
private val flowPrefs = FlowSharedPreferences(prefs) private val flowPrefs = FlowSharedPreferences(prefs)
private val defaultDownloadsDir = Uri.fromFile( private val defaultDownloadsDir = Uri.fromFile(
File(Environment.getExternalStorageDirectory().absolutePath + File.separator + File(
context.getString(R.string.app_name), "downloads")) Environment.getExternalStorageDirectory().absolutePath + File.separator +
context.getString(R.string.app_name),
"downloads"
)
)
private val defaultBackupDir = Uri.fromFile( private val defaultBackupDir = Uri.fromFile(
File(Environment.getExternalStorageDirectory().absolutePath + File.separator + File(
context.getString(R.string.app_name), "backup")) Environment.getExternalStorageDirectory().absolutePath + File.separator +
context.getString(R.string.app_name),
"backup"
)
)
fun startScreen() = prefs.getInt(Keys.startScreen, 1) fun startScreen() = prefs.getInt(Keys.startScreen, 1)

View file

@ -25,7 +25,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder().addInterceptor(interceptor).build()
fun addLibManga(track: Track): Observable<Track> { fun addLibManga(track: Track): Observable<Track> {
val query = """ val query =
"""
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) { |mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) { |SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id | id
@ -62,7 +63,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
fun updateLibManga(track: Track): Observable<Track> { fun updateLibManga(track: Track): Observable<Track> {
val query = """ val query =
"""
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) { |mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) { |SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|id |id
@ -94,7 +96,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
fun search(search: String): Observable<List<TrackSearch>> { fun search(search: String): Observable<List<TrackSearch>> {
val query = """ val query =
"""
|query Search(${'$'}query: String) { |query Search(${'$'}query: String) {
|Page (perPage: 50) { |Page (perPage: 50) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) { |media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
@ -147,7 +150,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
fun findLibManga(track: Track, userid: Int): Observable<Track?> { fun findLibManga(track: Track, userid: Int): Observable<Track?> {
val query = """ val query =
"""
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) { |query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|Page { |Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) { |mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
@ -216,7 +220,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
fun getCurrentUser(): Observable<Pair<Int, String>> { fun getCurrentUser(): Observable<Pair<Int, String>> {
val query = """ val query =
"""
|query User { |query User {
|Viewer { |Viewer {
|id |id
@ -251,17 +256,24 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private fun jsonToALManga(struct: JsonObject): ALManga { private fun jsonToALManga(struct: JsonObject): ALManga {
val date = try { val date = try {
val date = Calendar.getInstance() val date = Calendar.getInstance()
date.set(struct["startDate"]["year"].nullInt ?: 0, (struct["startDate"]["month"].nullInt date.set(
?: 0) - 1, struct["startDate"]["year"].nullInt ?: 0,
struct["startDate"]["day"].nullInt ?: 0) (
struct["startDate"]["month"].nullInt
?: 0
) - 1,
struct["startDate"]["day"].nullInt ?: 0
)
date.timeInMillis date.timeInMillis
} catch (_: Exception) { } catch (_: Exception) {
0L 0L
} }
return ALManga(struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString, return ALManga(
struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString,
struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].asString, struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].asString,
date, struct["chapters"].nullInt ?: 0) date, struct["chapters"].nullInt ?: 0
)
} }
private fun jsonToALUserManga(struct: JsonObject): ALUserManga { private fun jsonToALUserManga(struct: JsonObject): ALUserManga {

View file

@ -73,7 +73,8 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
fun search(search: String): Observable<List<TrackSearch>> { fun search(search: String): Observable<List<TrackSearch>> {
val url = Uri.parse( val url = Uri.parse(
"$apiUrl/search/subject/${URLEncoder.encode(search, Charsets.UTF_8.name())}").buildUpon() "$apiUrl/search/subject/${URLEncoder.encode(search, Charsets.UTF_8.name())}"
).buildUpon()
.appendQueryParameter("max_results", "20") .appendQueryParameter("max_results", "20")
.build() .build()
val request = Request.Builder() val request = Request.Builder()
@ -109,9 +110,15 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
return Track.create(TrackManager.BANGUMI).apply { return Track.create(TrackManager.BANGUMI).apply {
title = mangas["name"].asString title = mangas["name"].asString
media_id = mangas["id"].asInt media_id = mangas["id"].asInt
score = if (mangas["rating"] != null) score = if (mangas["rating"] != null) {
(if (mangas["rating"].isJsonObject) mangas["rating"].obj["score"].asFloat else 0f) if (mangas["rating"].isJsonObject) {
else 0f mangas["rating"].obj["score"].asFloat
} else {
0f
}
} else {
0f
}
status = Bangumi.DEFAULT_STATUS status = Bangumi.DEFAULT_STATUS
tracking_url = mangas["url"].asString tracking_url = mangas["url"].asString
} }
@ -163,7 +170,8 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
} }
} }
private fun accessTokenRequest(code: String) = POST(oauthUrl, private fun accessTokenRequest(code: String) = POST(
oauthUrl,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "authorization_code") .add("grant_type", "authorization_code")
.add("client_id", clientId) .add("client_id", clientId)
@ -196,13 +204,15 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
.appendQueryParameter("redirect_uri", redirectUrl) .appendQueryParameter("redirect_uri", redirectUrl)
.build() .build()
fun refreshTokenRequest(token: String) = POST(oauthUrl, fun refreshTokenRequest(token: String) = POST(
oauthUrl,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.add("client_id", clientId) .add("client_id", clientId)
.add("client_secret", clientSecret) .add("client_secret", clientSecret)
.add("refresh_token", token) .add("refresh_token", token)
.add("redirect_uri", redirectUrl) .add("redirect_uri", redirectUrl)
.build()) .build()
)
} }
} }

View file

@ -37,8 +37,10 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
val authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder() val authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder()
.header("User-Agent", "Tachiyomi") .header("User-Agent", "Tachiyomi")
.url(originalRequest.url.newBuilder() .url(
.addQueryParameter("access_token", currAuth.access_token).build()) originalRequest.url.newBuilder()
.addQueryParameter("access_token", currAuth.access_token).build()
)
.build() else originalRequest.newBuilder() .build() else originalRequest.newBuilder()
.post(addTocken(currAuth.access_token, originalRequest.body as FormBody)) .post(addTocken(currAuth.access_token, originalRequest.body as FormBody))
.header("User-Agent", "Tachiyomi") .header("User-Agent", "Tachiyomi")
@ -54,7 +56,8 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
System.currentTimeMillis() / 1000, System.currentTimeMillis() / 1000,
oauth.expires_in, oauth.expires_in,
oauth.refresh_token, oauth.refresh_token,
this.oauth?.user_id) this.oauth?.user_id
)
bangumi.saveToken(oauth) bangumi.saveToken(oauth)
} }

View file

@ -242,12 +242,14 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
return baseMangaUrl + remoteId return baseMangaUrl + remoteId
} }
fun refreshTokenRequest(token: String) = POST("${loginUrl}oauth/token", fun refreshTokenRequest(token: String) = POST(
"${loginUrl}oauth/token",
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.add("client_id", clientId) .add("client_id", clientId)
.add("client_secret", clientSecret) .add("client_secret", clientSecret)
.add("refresh_token", token) .add("refresh_token", token)
.build()) .build()
)
} }
} }

View file

@ -152,9 +152,10 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
var ckCount = 0 var ckCount = 0
val url = BASE_URL.toHttpUrlOrNull()!! val url = BASE_URL.toHttpUrlOrNull()!!
for (ck in networkService.cookieManager.get(url)) { for (ck in networkService.cookieManager.get(url)) {
if (ck.name == USER_SESSION_COOKIE || ck.name == LOGGED_IN_COOKIE) if (ck.name == USER_SESSION_COOKIE || ck.name == LOGGED_IN_COOKIE) {
ckCount++ ckCount++
} }
}
return ckCount == 2 return ckCount == 2
} }

View file

@ -46,10 +46,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
client.newCall(GET(searchUrl(query))) client.newCall(GET(searchUrl(query)))
.asObservable() .asObservable()
.flatMap { response -> .flatMap { response ->
Observable.from(Jsoup.parse(response.consumeBody()) Observable.from(
Jsoup.parse(response.consumeBody())
.select("div.js-categories-seasonal.js-block-list.list") .select("div.js-categories-seasonal.js-block-list.list")
.select("table").select("tbody") .select("table").select("tbody")
.select("tr").drop(1)) .select("tr").drop(1)
)
} }
.filter { row -> .filter { row ->
row.select(TD)[2].text() != "Novel" row.select(TD)[2].text() != "Novel"
@ -349,8 +351,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
private fun Element.searchDateXml(field: String): Long { private fun Element.searchDateXml(field: String): Long {
val text = selectText(field, "0000-00-00")!! val text = selectText(field, "0000-00-00")!!
// MAL sets the data to 0000-00-00 when date is invalid or missing // MAL sets the data to 0000-00-00 when date is invalid or missing
if (text == "0000-00-00") if (text == "0000-00-00") {
return 0L return 0L
}
return SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(text)?.time ?: 0L return SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(text)?.time ?: 0L
} }
@ -359,8 +362,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
val month = select(id + "_month > option[selected]").`val`().toIntOrNull() val month = select(id + "_month > option[selected]").`val`().toIntOrNull()
val day = select(id + "_day > option[selected]").`val`().toIntOrNull() val day = select(id + "_day > option[selected]").`val`().toIntOrNull()
val year = select(id + "_year > option[selected]").`val`().toIntOrNull() val year = select(id + "_year > option[selected]").`val`().toIntOrNull()
if (year == null || month == null || day == null) if (year == null || month == null || day == null) {
return 0L return 0L
}
return GregorianCalendar(year, month - 1, day).timeInMillis return GregorianCalendar(year, month - 1, day).timeInMillis
} }
@ -472,8 +476,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
fun copyPersonalFrom(track: Track) { fun copyPersonalFrom(track: Track) {
num_read_chapters = track.last_chapter_read.toString() num_read_chapters = track.last_chapter_read.toString()
val numScore = track.score.toInt() val numScore = track.score.toInt()
if (numScore in 1..9) if (numScore in 1..9) {
score = numScore.toString() score = numScore.toString()
}
status = track.status.toString() status = track.status.toString()
if (track.started_reading_date == 0L) { if (track.started_reading_date == 0L) {
start_date_month = "" start_date_month = ""

View file

@ -159,7 +159,8 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
} }
} }
private fun accessTokenRequest(code: String) = POST(oauthUrl, private fun accessTokenRequest(code: String) = POST(
oauthUrl,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "authorization_code") .add("grant_type", "authorization_code")
.add("client_id", clientId) .add("client_id", clientId)
@ -192,12 +193,14 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
.appendQueryParameter("response_type", "code") .appendQueryParameter("response_type", "code")
.build() .build()
fun refreshTokenRequest(token: String) = POST(oauthUrl, fun refreshTokenRequest(token: String) = POST(
oauthUrl,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.add("client_id", clientId) .add("client_id", clientId)
.add("client_secret", clientSecret) .add("client_secret", clientSecret)
.add("refresh_token", token) .add("refresh_token", token)
.build()) .build()
)
} }
} }

View file

@ -37,9 +37,11 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
setContentText(context.getString(R.string.update_check_notification_update_available)) setContentText(context.getString(R.string.update_check_notification_update_available))
setSmallIcon(android.R.drawable.stat_sys_download_done) setSmallIcon(android.R.drawable.stat_sys_download_done)
// Download action // Download action
addAction(android.R.drawable.stat_sys_download_done, addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download), context.getString(R.string.action_download),
PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)) PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
)
} }
} }
Result.success() Result.success()
@ -64,7 +66,8 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
val request = PeriodicWorkRequestBuilder<UpdaterJob>( val request = PeriodicWorkRequestBuilder<UpdaterJob>(
3, TimeUnit.DAYS, 3, TimeUnit.DAYS,
3, TimeUnit.HOURS) 3, TimeUnit.HOURS
)
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()

View file

@ -69,13 +69,17 @@ internal class UpdaterNotifier(private val context: Context) {
setProgress(0, 0, false) setProgress(0, 0, false)
// Install action // Install action
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri)) setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
addAction(R.drawable.ic_system_update_alt_white_24dp, addAction(
R.drawable.ic_system_update_alt_white_24dp,
context.getString(R.string.action_install), context.getString(R.string.action_install),
NotificationHandler.installApkPendingActivity(context, uri)) NotificationHandler.installApkPendingActivity(context, uri)
)
// Cancel action // Cancel action
addAction(R.drawable.ic_close_24dp, addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel), context.getString(R.string.action_cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)) NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
)
} }
notificationBuilder.show() notificationBuilder.show()
} }
@ -92,13 +96,17 @@ internal class UpdaterNotifier(private val context: Context) {
setOnlyAlertOnce(false) setOnlyAlertOnce(false)
setProgress(0, 0, false) setProgress(0, 0, false)
// Retry action // Retry action
addAction(R.drawable.ic_refresh_24dp, addAction(
R.drawable.ic_refresh_24dp,
context.getString(R.string.action_retry), context.getString(R.string.action_retry),
UpdaterService.downloadApkPendingService(context, url)) UpdaterService.downloadApkPendingService(context, url)
)
// Cancel action // Cancel action
addAction(R.drawable.ic_close_24dp, addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel), context.getString(R.string.action_cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)) NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
)
} }
notificationBuilder.show(Notifications.ID_UPDATER) notificationBuilder.show(Notifications.ID_UPDATER)
} }

View file

@ -40,7 +40,8 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
private fun createUpdateNotification(names: List<String>) { private fun createUpdateNotification(names: List<String>) {
NotificationManagerCompat.from(context).apply { NotificationManagerCompat.from(context).apply {
notify(Notifications.ID_UPDATES_TO_EXTS, notify(
Notifications.ID_UPDATES_TO_EXTS,
context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) { context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) {
setContentTitle( setContentTitle(
context.resources.getQuantityString( context.resources.getQuantityString(
@ -55,7 +56,8 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
setSmallIcon(R.drawable.ic_extension_24dp) setSmallIcon(R.drawable.ic_extension_24dp)
setContentIntent(NotificationReceiver.openExtensionsPendingActivity(context)) setContentIntent(NotificationReceiver.openExtensionsPendingActivity(context))
setAutoCancel(true) setAutoCancel(true)
}) }
)
} }
} }
@ -72,7 +74,8 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>( val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
12, TimeUnit.HOURS, 12, TimeUnit.HOURS,
1, TimeUnit.HOURS) 1, TimeUnit.HOURS
)
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()

View file

@ -107,8 +107,10 @@ internal object ExtensionLoader {
// Validate lib version // Validate lib version
val libVersion = versionName.substringBeforeLast('.').toDouble() val libVersion = versionName.substringBeforeLast('.').toDouble()
if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) { if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
val exception = Exception("Lib version is $libVersion, while only versions " + val exception = Exception(
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed") "Lib version is $libVersion, while only versions " +
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
)
Timber.w(exception) Timber.w(exception)
return LoadResult.Error(exception) return LoadResult.Error(exception)
} }
@ -129,11 +131,12 @@ internal object ExtensionLoader {
.split(";") .split(";")
.map { .map {
val sourceClass = it.trim() val sourceClass = it.trim()
if (sourceClass.startsWith(".")) if (sourceClass.startsWith(".")) {
pkgInfo.packageName + sourceClass pkgInfo.packageName + sourceClass
else } else {
sourceClass sourceClass
} }
}
.flatMap { .flatMap {
try { try {
when (val obj = Class.forName(it, false, classLoader).newInstance()) { when (val obj = Class.forName(it, false, classLoader).newInstance()) {

View file

@ -83,18 +83,20 @@ class LocalSource(private val context: Context) : CatalogueSource {
val state = ((if (filters.isEmpty()) POPULAR_FILTERS else filters)[0] as OrderBy).state val state = ((if (filters.isEmpty()) POPULAR_FILTERS else filters)[0] as OrderBy).state
when (state?.index) { when (state?.index) {
0 -> { 0 -> {
mangaDirs = if (state.ascending) mangaDirs = if (state.ascending) {
mangaDirs.sortedBy { it.name.toLowerCase(Locale.ENGLISH) } mangaDirs.sortedBy { it.name.toLowerCase(Locale.ENGLISH) }
else } else {
mangaDirs.sortedByDescending { it.name.toLowerCase(Locale.ENGLISH) } mangaDirs.sortedByDescending { it.name.toLowerCase(Locale.ENGLISH) }
} }
}
1 -> { 1 -> {
mangaDirs = if (state.ascending) mangaDirs = if (state.ascending) {
mangaDirs.sortedBy(File::lastModified) mangaDirs.sortedBy(File::lastModified)
else } else {
mangaDirs.sortedByDescending(File::lastModified) mangaDirs.sortedByDescending(File::lastModified)
} }
} }
}
val mangas = mangaDirs.map { mangaDir -> val mangas = mangaDirs.map { mangaDir ->
SManga.create().apply { SManga.create().apply {
@ -167,10 +169,12 @@ class LocalSource(private val context: Context) : CatalogueSource {
ChapterRecognition.parseChapterNumber(this, manga) ChapterRecognition.parseChapterNumber(this, manga)
} }
} }
.sortedWith(Comparator { c1, c2 -> .sortedWith(
Comparator { c1, c2 ->
val c = c2.chapter_number.compareTo(c1.chapter_number) val c = c2.chapter_number.compareTo(c1.chapter_number)
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
}) }
)
.toList() .toList()
return Observable.just(chapters) return Observable.just(chapters)

View file

@ -23,26 +23,32 @@ interface SManga : Serializable {
var initialized: Boolean var initialized: Boolean
fun copyFrom(other: SManga) { fun copyFrom(other: SManga) {
if (other.author != null) if (other.author != null) {
author = other.author author = other.author
}
if (other.artist != null) if (other.artist != null) {
artist = other.artist artist = other.artist
}
if (other.description != null) if (other.description != null) {
description = other.description description = other.description
}
if (other.genre != null) if (other.genre != null) {
genre = other.genre genre = other.genre
}
if (other.thumbnail_url != null) if (other.thumbnail_url != null) {
thumbnail_url = other.thumbnail_url thumbnail_url = other.thumbnail_url
}
status = other.status status = other.status
if (!initialized) if (!initialized) {
initialized = other.initialized initialized = other.initialized
} }
}
companion object { companion object {
const val UNKNOWN = 0 const val UNKNOWN = 0

View file

@ -343,10 +343,12 @@ abstract class HttpSource : CatalogueSource {
return try { return try {
val uri = URI(orig) val uri = URI(orig)
var out = uri.path var out = uri.path
if (uri.query != null) if (uri.query != null) {
out += "?" + uri.query out += "?" + uri.query
if (uri.fragment != null) }
if (uri.fragment != null) {
out += "#" + uri.fragment out += "#" + uri.fragment
}
out out
} catch (e: URISyntaxException) { } catch (e: URISyntaxException) {
orig orig

View file

@ -59,7 +59,8 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(when (preferences.themeMode().get()) { setTheme(
when (preferences.themeMode().get()) {
Values.THEME_MODE_SYSTEM -> { Values.THEME_MODE_SYSTEM -> {
if (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) { if (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) {
darkTheme darkTheme
@ -69,7 +70,8 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
} }
Values.THEME_MODE_DARK -> darkTheme Values.THEME_MODE_DARK -> darkTheme
else -> lightTheme else -> lightTheme
}) }
)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -15,7 +15,8 @@ import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.clearFindViewByIdCache import kotlinx.android.synthetic.clearFindViewByIdCache
import timber.log.Timber import timber.log.Timber
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) : RestoreViewOnCreateController(bundle), abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
RestoreViewOnCreateController(bundle),
LayoutContainer { LayoutContainer {
lateinit var binding: VB lateinit var binding: VB

View file

@ -87,10 +87,12 @@ abstract class DialogController : RestoreViewOnCreateController {
*/ */
fun showDialog(router: Router, tag: String?) { fun showDialog(router: Router, tag: String?) {
dismissed = false dismissed = false
router.pushController(RouterTransaction.with(this) router.pushController(
RouterTransaction.with(this)
.pushChangeHandler(SimpleSwapChangeHandler(false)) .pushChangeHandler(SimpleSwapChangeHandler(false))
.popChangeHandler(SimpleSwapChangeHandler(false)) .popChangeHandler(SimpleSwapChangeHandler(false))
.tag(tag)) .tag(tag)
)
} }
/** /**

View file

@ -44,12 +44,10 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
} }
fun <T> Observable<T>.subscribeUntilDetach(): Subscription { fun <T> Observable<T>.subscribeUntilDetach(): Subscription {
return subscribe().also { untilDetachSubscriptions.add(it) } return subscribe().also { untilDetachSubscriptions.add(it) }
} }
fun <T> Observable<T>.subscribeUntilDetach(onNext: (T) -> Unit): Subscription { fun <T> Observable<T>.subscribeUntilDetach(onNext: (T) -> Unit): Subscription {
return subscribe(onNext).also { untilDetachSubscriptions.add(it) } return subscribe(onNext).also { untilDetachSubscriptions.add(it) }
} }
@ -57,7 +55,6 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
onNext: (T) -> Unit, onNext: (T) -> Unit,
onError: (Throwable) -> Unit onError: (Throwable) -> Unit
): Subscription { ): Subscription {
return subscribe(onNext, onError).also { untilDetachSubscriptions.add(it) } return subscribe(onNext, onError).also { untilDetachSubscriptions.add(it) }
} }
@ -66,17 +63,14 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
onError: (Throwable) -> Unit, onError: (Throwable) -> Unit,
onCompleted: () -> Unit onCompleted: () -> Unit
): Subscription { ): Subscription {
return subscribe(onNext, onError, onCompleted).also { untilDetachSubscriptions.add(it) } return subscribe(onNext, onError, onCompleted).also { untilDetachSubscriptions.add(it) }
} }
fun <T> Observable<T>.subscribeUntilDestroy(): Subscription { fun <T> Observable<T>.subscribeUntilDestroy(): Subscription {
return subscribe().also { untilDestroySubscriptions.add(it) } return subscribe().also { untilDestroySubscriptions.add(it) }
} }
fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription { fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription {
return subscribe(onNext).also { untilDestroySubscriptions.add(it) } return subscribe(onNext).also { untilDestroySubscriptions.add(it) }
} }
@ -84,7 +78,6 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
onNext: (T) -> Unit, onNext: (T) -> Unit,
onError: (Throwable) -> Unit onError: (Throwable) -> Unit
): Subscription { ): Subscription {
return subscribe(onNext, onError).also { untilDestroySubscriptions.add(it) } return subscribe(onNext, onError).also { untilDestroySubscriptions.add(it) }
} }
@ -93,7 +86,6 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
onError: (Throwable) -> Unit, onError: (Throwable) -> Unit,
onCompleted: () -> Unit onCompleted: () -> Unit
): Subscription { ): Subscription {
return subscribe(onNext, onError, onCompleted).also { untilDestroySubscriptions.add(it) } return subscribe(onNext, onError, onCompleted).also { untilDestroySubscriptions.add(it) }
} }
} }

View file

@ -25,7 +25,8 @@ import reactivecircus.flowbinding.android.view.clicks
/** /**
* Controller to manage the categories for the users' library. * Controller to manage the categories for the users' library.
*/ */
class CategoryController : NucleusController<CategoriesControllerBinding, CategoryPresenter>(), class CategoryController :
NucleusController<CategoriesControllerBinding, CategoryPresenter>(),
ActionMode.Callback, ActionMode.Callback,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
@ -176,8 +177,10 @@ class CategoryController : NucleusController<CategoriesControllerBinding, Catego
when (item.itemId) { when (item.itemId) {
R.id.action_delete -> { R.id.action_delete -> {
undoHelper = UndoHelper(adapter, this) undoHelper = UndoHelper(adapter, this)
undoHelper?.start(adapter.selectedPositions, view!!, undoHelper?.start(
R.string.snack_categories_deleted, R.string.action_undo, 3000) adapter.selectedPositions, view!!,
R.string.snack_categories_deleted, R.string.action_undo, 3000
)
mode.finish() mode.finish()
} }

View file

@ -49,7 +49,6 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder
position: Int, position: Int,
payloads: List<Any?>? payloads: List<Any?>?
) { ) {
holder.bind(category) holder.bind(category)
} }

View file

@ -23,7 +23,8 @@ import rx.android.schedulers.AndroidSchedulers
* Controller that shows the currently active downloads. * Controller that shows the currently active downloads.
* Uses R.layout.fragment_download_queue. * Uses R.layout.fragment_download_queue.
*/ */
class DownloadController : NucleusController<DownloadControllerBinding, DownloadPresenter>(), class DownloadController :
NucleusController<DownloadControllerBinding, DownloadPresenter>(),
DownloadAdapter.DownloadItemListener { DownloadAdapter.DownloadItemListener {
/** /**
@ -123,8 +124,9 @@ class DownloadController : NucleusController<DownloadControllerBinding, Download
val adapter = adapter ?: return false val adapter = adapter ?: return false
val items = adapter.currentItems.sortedBy { it.download.chapter.date_upload } val items = adapter.currentItems.sortedBy { it.download.chapter.date_upload }
.toMutableList() .toMutableList()
if (item.itemId == R.id.newest) if (item.itemId == R.id.newest) {
items.reverse() items.reverse()
}
adapter.updateDataSet(items) adapter.updateDataSet(items)
val downloads = items.mapNotNull { it.download } val downloads = items.mapNotNull { it.download }
presenter.reorder(downloads) presenter.reorder(downloads)
@ -279,10 +281,11 @@ class DownloadController : NucleusController<DownloadControllerBinding, Download
val items = adapter?.currentItems?.toMutableList() ?: return val items = adapter?.currentItems?.toMutableList() ?: return
val item = items[position] val item = items[position]
items.remove(item) items.remove(item)
if (menuItem.itemId == R.id.move_to_top) if (menuItem.itemId == R.id.move_to_top) {
items.add(0, item) items.add(0, item)
else } else {
items.add(item) items.add(item)
}
val adapter = adapter ?: return val adapter = adapter ?: return
adapter.updateDataSet(items) adapter.updateDataSet(items)

View file

@ -80,13 +80,17 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
} }
private fun showPopupMenu(view: View) { private fun showPopupMenu(view: View) {
view.popupMenu(R.menu.download_single, { view.popupMenu(
R.menu.download_single,
{
findItem(R.id.move_to_top).isVisible = bindingAdapterPosition != 0 findItem(R.id.move_to_top).isVisible = bindingAdapterPosition != 0
findItem(R.id.move_to_bottom).isVisible = findItem(R.id.move_to_bottom).isVisible =
bindingAdapterPosition != adapter.itemCount - 1 bindingAdapterPosition != adapter.itemCount - 1
}, { },
{
adapter.downloadItemListener.onMenuItemClick(bindingAdapterPosition, this) adapter.downloadItemListener.onMenuItemClick(bindingAdapterPosition, this)
true true
}) }
)
} }
} }

View file

@ -32,7 +32,8 @@ import uy.kohesive.injekt.api.get
/** /**
* Controller to manage the catalogues available in the app. * Controller to manage the catalogues available in the app.
*/ */
open class ExtensionController : NucleusController<ExtensionControllerBinding, ExtensionPresenter>(), open class ExtensionController :
NucleusController<ExtensionControllerBinding, ExtensionPresenter>(),
ExtensionAdapter.OnButtonClickListener, ExtensionAdapter.OnButtonClickListener,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
@ -92,9 +93,11 @@ open class ExtensionController : NucleusController<ExtensionControllerBinding, E
when (item.itemId) { when (item.itemId) {
R.id.action_search -> expandActionViewFromInteraction = true R.id.action_search -> expandActionViewFromInteraction = true
R.id.action_settings -> { R.id.action_settings -> {
router.pushController((RouterTransaction.with(ExtensionFilterController())) router.pushController(
(RouterTransaction.with(ExtensionFilterController()))
.popChangeHandler(SettingsExtensionsFadeChangeHandler()) .popChangeHandler(SettingsExtensionsFadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())) .pushChangeHandler(FadeChangeHandler())
)
} }
R.id.action_auto_check -> { R.id.action_auto_check -> {
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
@ -198,7 +201,8 @@ open class ExtensionController : NucleusController<ExtensionControllerBinding, E
adapter?.updateDataSet( adapter?.updateDataSet(
extensions.filter { extensions.filter {
it.extension.name.contains(query, ignoreCase = true) it.extension.name.contains(query, ignoreCase = true)
}) }
)
} else { } else {
adapter?.updateDataSet(extensions) adapter?.updateDataSet(extensions)
} }

View file

@ -46,9 +46,11 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
private var preferenceScreen: PreferenceScreen? = null private var preferenceScreen: PreferenceScreen? = null
constructor(pkgName: String) : this(Bundle().apply { constructor(pkgName: String) : this(
Bundle().apply {
putString(PKGNAME_KEY, pkgName) putString(PKGNAME_KEY, pkgName)
}) }
)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext()) val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
@ -137,7 +139,8 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
source.preferences source.preferences
} else {*/ } else {*/
context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE) context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE)
/*}*/) /*}*/
)
if (source is ConfigurableSource) { if (source is ConfigurableSource) {
if (multiSource) { if (multiSource) {
@ -178,14 +181,19 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
} }
val f = when (preference) { val f = when (preference) {
is EditTextPreference -> EditTextPreferenceDialogController is EditTextPreference ->
EditTextPreferenceDialogController
.newInstance(preference.getKey()) .newInstance(preference.getKey())
is ListPreference -> ListPreferenceDialogController is ListPreference ->
ListPreferenceDialogController
.newInstance(preference.getKey()) .newInstance(preference.getKey())
is MultiSelectListPreference -> MultiSelectListPreferenceDialogController is MultiSelectListPreference ->
MultiSelectListPreferenceDialogController
.newInstance(preference.getKey()) .newInstance(preference.getKey())
else -> throw IllegalArgumentException("Tried to display dialog for unknown " + else -> throw IllegalArgumentException(
"preference type. Did you forget to override onDisplayPreferenceDialog()?") "Tried to display dialog for unknown " +
"preference type. Did you forget to override onDisplayPreferenceDialog()?"
)
} }
f.targetController = this f.targetController = this
f.showDialog(router) f.showDialog(router)

View file

@ -23,7 +23,8 @@ class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecora
val child = parent.getChildAt(i) val child = parent.getChildAt(i)
val holder = parent.getChildViewHolder(child) val holder = parent.getChildViewHolder(child)
if (holder is ExtensionHolder && if (holder is ExtensionHolder &&
parent.getChildViewHolder(parent.getChildAt(i + 1)) is ExtensionHolder) { parent.getChildViewHolder(parent.getChildAt(i + 1)) is ExtensionHolder
) {
val params = child.layoutParams as RecyclerView.LayoutParams val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight val bottom = top + divider.intrinsicHeight

View file

@ -38,7 +38,6 @@ data class ExtensionGroupItem(val name: String, val size: Int, val showSize: Boo
position: Int, position: Int,
payloads: List<Any?>? payloads: List<Any?>?
) { ) {
holder.bind(this) holder.bind(this)
} }

View file

@ -69,13 +69,15 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
val installStep = item.installStep val installStep = item.installStep
if (installStep != null) { if (installStep != null) {
setText(when (installStep) { setText(
when (installStep) {
InstallStep.Pending -> R.string.ext_pending InstallStep.Pending -> R.string.ext_pending
InstallStep.Downloading -> R.string.ext_downloading InstallStep.Downloading -> R.string.ext_downloading
InstallStep.Installing -> R.string.ext_installing InstallStep.Installing -> R.string.ext_installing
InstallStep.Installed -> R.string.ext_installed InstallStep.Installed -> R.string.ext_installed
InstallStep.Error -> R.string.action_retry InstallStep.Error -> R.string.action_retry
}) }
)
if (installStep != InstallStep.Error) { if (installStep != InstallStep.Error) {
isEnabled = false isEnabled = false
isClickable = false isClickable = false

View file

@ -46,7 +46,6 @@ data class ExtensionItem(
position: Int, position: Int,
payloads: List<Any?>? payloads: List<Any?>?
) { ) {
if (payloads == null || payloads.isEmpty()) { if (payloads == null || payloads.isEmpty()) {
holder.bind(this) holder.bind(this)
} else { } else {

View file

@ -10,10 +10,12 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle) class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
where T : Controller, T : ExtensionTrustDialog.Listener { where T : Controller, T : ExtensionTrustDialog.Listener {
constructor(target: T, signatureHash: String, pkgName: String) : this(Bundle().apply { constructor(target: T, signatureHash: String, pkgName: String) : this(
Bundle().apply {
putString(SIGNATURE_KEY, signatureHash) putString(SIGNATURE_KEY, signatureHash)
putString(PKGNAME_KEY, pkgName) putString(PKGNAME_KEY, pkgName)
}) { }
) {
targetController = target targetController = target
} }

View file

@ -255,9 +255,11 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
controller.createActionModeIfNeeded() controller.createActionModeIfNeeded()
when { when {
lastClickPosition == -1 -> setSelection(position) lastClickPosition == -1 -> setSelection(position)
lastClickPosition > position -> for (i in position until lastClickPosition) lastClickPosition > position ->
for (i in position until lastClickPosition)
setSelection(i) setSelection(i)
lastClickPosition < position -> for (i in lastClickPosition + 1..position) lastClickPosition < position ->
for (i in lastClickPosition + 1..position)
setSelection(i) setSelection(i)
else -> setSelection(position) else -> setSelection(position)
} }

View file

@ -230,10 +230,11 @@ class LibraryController(
} }
// Get the current active category. // Get the current active category.
val activeCat = if (adapter.categories.isNotEmpty()) val activeCat = if (adapter.categories.isNotEmpty()) {
binding.libraryPager.currentItem binding.libraryPager.currentItem
else } else {
activeCategory activeCategory
}
// Set the categories // Set the categories
adapter.categories = categories adapter.categories = categories
@ -260,11 +261,12 @@ class LibraryController(
* @return the preference. * @return the preference.
*/ */
private fun getColumnsPreferenceForCurrentOrientation(): Preference<Int> { private fun getColumnsPreferenceForCurrentOrientation(): Preference<Int> {
return if (resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) return if (resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
preferences.portraitColumns() preferences.portraitColumns()
else } else {
preferences.landscapeColumns() preferences.landscapeColumns()
} }
}
/** /**
* Called when a filter is changed. * Called when a filter is changed.
@ -510,8 +512,13 @@ class LibraryController(
if (manga.favorite) { if (manga.favorite) {
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*" intent.type = "image/*"
startActivityForResult(Intent.createChooser(intent, startActivityForResult(
resources?.getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN) Intent.createChooser(
intent,
resources?.getString(R.string.file_select_cover)
),
REQUEST_IMAGE_OPEN
)
} else { } else {
activity?.toast(R.string.notification_first_add_to_library) activity?.toast(R.string.notification_first_add_to_library)
} }

View file

@ -27,11 +27,12 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
var downloadCount = -1 var downloadCount = -1
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return if (libraryAsList.get()) return if (libraryAsList.get()) {
R.layout.source_list_item R.layout.source_list_item
else } else {
R.layout.source_grid_item R.layout.source_grid_item
} }
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder {
val parent = adapter.recyclerView val parent = adapter.recyclerView
@ -40,7 +41,8 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
val coverHeight = parent.itemWidth / 3 * 4 val coverHeight = parent.itemWidth / 3 * 4
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
gradient.layoutParams = FrameLayout.LayoutParams( gradient.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM) MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
)
} }
LibraryGridHolder(view, adapter) LibraryGridHolder(view, adapter)
} else { } else {
@ -76,15 +78,16 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
} }
private fun containsGenre(tag: String, genres: List<String>?): Boolean { private fun containsGenre(tag: String, genres: List<String>?): Boolean {
return if (tag.startsWith("-")) return if (tag.startsWith("-")) {
genres?.find { genres?.find {
it.trim().toLowerCase() == tag.substringAfter("-").toLowerCase() it.trim().toLowerCase() == tag.substringAfter("-").toLowerCase()
} == null } == null
else } else {
genres?.find { genres?.find {
it.trim().toLowerCase() == tag.toLowerCase() it.trim().toLowerCase() == tag.toLowerCase()
} != null } != null
} }
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other is LibraryItem) { if (other is LibraryItem) {

View file

@ -214,10 +214,11 @@ class LibraryPresenter(
} }
} }
val comparator = if (preferences.librarySortingAscending().get()) val comparator = if (preferences.librarySortingAscending().get()) {
Comparator(sortFn) Comparator(sortFn)
else } else {
Collections.reverseOrder(sortFn) Collections.reverseOrder(sortFn)
}
return map.mapValues { entry -> entry.value.sortedWith(comparator) } return map.mapValues { entry -> entry.value.sortedWith(comparator) }
} }
@ -229,10 +230,11 @@ class LibraryPresenter(
*/ */
private fun getLibraryObservable(): Observable<Library> { private fun getLibraryObservable(): Observable<Library> {
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { dbCategories, libraryManga -> return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { dbCategories, libraryManga ->
val categories = if (libraryManga.containsKey(0)) val categories = if (libraryManga.containsKey(0)) {
arrayListOf(Category.createDefault()) + dbCategories arrayListOf(Category.createDefault()) + dbCategories
else } else {
dbCategories dbCategories
}
this.categories = categories this.categories = categories
Library(categories, libraryManga) Library(categories, libraryManga)

View file

@ -129,8 +129,11 @@ class LibrarySettingsSheet(
override fun initModels() { override fun initModels() {
val sorting = preferences.librarySortingMode().get() val sorting = preferences.librarySortingMode().get()
val order = if (preferences.librarySortingAscending().get()) val order = if (preferences.librarySortingAscending().get()) {
Item.MultiSort.SORT_ASC else Item.MultiSort.SORT_DESC Item.MultiSort.SORT_ASC
} else {
Item.MultiSort.SORT_DESC
}
alphabetically.state = alphabetically.state =
if (sorting == LibrarySort.ALPHA) order else Item.MultiSort.SORT_NONE if (sorting == LibrarySort.ALPHA) order else Item.MultiSort.SORT_NONE

View file

@ -29,7 +29,7 @@ import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.ui.recent.history.HistoryController import eu.kanade.tachiyomi.ui.recent.history.HistoryController
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.ui.source.SourceController import eu.kanade.tachiyomi.ui.source.SourceController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast

View file

@ -35,10 +35,12 @@ import uy.kohesive.injekt.api.get
class MangaController : RxController<MangaControllerBinding>, TabbedController { class MangaController : RxController<MangaControllerBinding>, TabbedController {
constructor(manga: Manga?, fromSource: Boolean = false) : super(Bundle().apply { constructor(manga: Manga?, fromSource: Boolean = false) : super(
Bundle().apply {
putLong(MANGA_EXTRA, manga?.id ?: 0) putLong(MANGA_EXTRA, manga?.id ?: 0)
putBoolean(FROM_SOURCE_EXTRA, fromSource) putBoolean(FROM_SOURCE_EXTRA, fromSource)
}) { }
) {
this.manga = manga this.manga = manga
if (manga != null) { if (manga != null) {
source = Injekt.get<SourceManager>().getOrStub(manga.source) source = Injekt.get<SourceManager>().getOrStub(manga.source)
@ -46,7 +48,8 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
} }
constructor(mangaId: Long) : this( constructor(mangaId: Long) : this(
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking()) Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking()
)
@Suppress("unused") @Suppress("unused")
constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA)) constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA))
@ -87,9 +90,10 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
binding.mangaPager.offscreenPageLimit = 3 binding.mangaPager.offscreenPageLimit = 3
binding.mangaPager.adapter = adapter binding.mangaPager.adapter = adapter
if (!fromSource) if (!fromSource) {
binding.mangaPager.currentItem = CHAPTERS_CONTROLLER binding.mangaPager.currentItem = CHAPTERS_CONTROLLER
} }
}
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
super.onDestroyView(view) super.onDestroyView(view)
@ -130,9 +134,11 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
private fun setTrackingIconInternal(visible: Boolean) { private fun setTrackingIconInternal(visible: Boolean) {
val tab = activity?.tabs?.getTabAt(TRACK_CONTROLLER) ?: return val tab = activity?.tabs?.getTabAt(TRACK_CONTROLLER) ?: return
val drawable = if (visible) val drawable = if (visible) {
VectorDrawableCompat.create(resources!!, R.drawable.ic_done_white_18dp, null) VectorDrawableCompat.create(resources!!, R.drawable.ic_done_white_18dp, null)
else null } else {
null
}
tab.icon = drawable tab.icon = drawable
} }
@ -144,7 +150,8 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
private val tabTitles = listOf( private val tabTitles = listOf(
R.string.manga_detail_tab, R.string.manga_detail_tab,
R.string.manga_chapters_tab, R.string.manga_chapters_tab,
R.string.manga_tracking_tab) R.string.manga_tracking_tab
)
.map { resources!!.getString(it) } .map { resources!!.getString(it) }
override fun getCount(): Int { override fun getCount(): Int {

View file

@ -10,7 +10,8 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem<ChapterHolder>(), class ChapterItem(val chapter: Chapter, val manga: Manga) :
AbstractFlexibleItem<ChapterHolder>(),
Chapter by chapter { Chapter by chapter {
private var _status: Int = 0 private var _status: Int = 0

View file

@ -26,8 +26,11 @@ class ChaptersAdapter(
val bookmarkedColor = context.getResourceColor(R.attr.colorAccent) val bookmarkedColor = context.getResourceColor(R.attr.colorAccent)
val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols() val decimalFormat = DecimalFormat(
.apply { decimalSeparator = '.' }) "#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' }
)
val dateFormat: DateFormat = preferences.dateFormat().getOrDefault() val dateFormat: DateFormat = preferences.dateFormat().getOrDefault()

View file

@ -39,7 +39,8 @@ import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.swiperefreshlayout.refreshes import reactivecircus.flowbinding.swiperefreshlayout.refreshes
import timber.log.Timber import timber.log.Timber
class ChaptersController : NucleusController<ChaptersControllerBinding, ChaptersPresenter>(), class ChaptersController :
NucleusController<ChaptersControllerBinding, ChaptersPresenter>(),
ActionMode.Callback, ActionMode.Callback,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
@ -168,11 +169,13 @@ class ChaptersController : NucleusController<ChaptersControllerBinding, Chapters
menuFilterEmpty.isVisible = filterSet menuFilterEmpty.isVisible = filterSet
// Disable unread filter option if read filter is enabled. // Disable unread filter option if read filter is enabled.
if (presenter.onlyRead()) if (presenter.onlyRead()) {
menuFilterUnread.isEnabled = false menuFilterUnread.isEnabled = false
}
// Disable read filter option if unread filter is enabled. // Disable read filter option if unread filter is enabled.
if (presenter.onlyUnread()) if (presenter.onlyUnread()) {
menuFilterRead.isEnabled = false menuFilterRead.isEnabled = false
}
// Display mode submenu // Display mode submenu
if (presenter.manga.displayMode == Manga.DISPLAY_NAME) { if (presenter.manga.displayMode == Manga.DISPLAY_NAME) {
@ -320,9 +323,11 @@ class ChaptersController : NucleusController<ChaptersControllerBinding, Chapters
createActionModeIfNeeded() createActionModeIfNeeded()
when { when {
lastClickPosition == -1 -> setSelection(position) lastClickPosition == -1 -> setSelection(position)
lastClickPosition > position -> for (i in position until lastClickPosition) lastClickPosition > position ->
for (i in position until lastClickPosition)
setSelection(i) setSelection(i)
lastClickPosition < position -> for (i in lastClickPosition + 1..position) lastClickPosition < position ->
for (i in lastClickPosition + 1..position)
setSelection(i) setSelection(i)
else -> setSelection(position) else -> setSelection(position)
} }

View file

@ -71,7 +71,8 @@ class ChaptersPresenter(
// Add the subscription that retrieves the chapters from the database, keeps subscribed to // Add the subscription that retrieves the chapters from the database, keeps subscribed to
// changes, and sends the list of chapters to the relay. // changes, and sends the list of chapters to the relay.
add(db.getChapters(manga).asRxObservable() add(
db.getChapters(manga).asRxObservable()
.map { chapters -> .map { chapters ->
// Convert every chapter to a model. // Convert every chapter to a model.
chapters.map { it.toModel() } chapters.map { it.toModel() }
@ -86,7 +87,8 @@ class ChaptersPresenter(
// Listen for download status changes // Listen for download status changes
observeDownloads() observeDownloads()
} }
.subscribe { chaptersRelay.call(it) }) .subscribe { chaptersRelay.call(it) }
)
} }
private fun observeDownloads() { private fun observeDownloads() {
@ -141,9 +143,12 @@ class ChaptersPresenter(
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.map { syncChaptersWithSource(db, it, manga, source) } .map { syncChaptersWithSource(db, it, manga, source) }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ -> .subscribeFirst(
{ view, _ ->
view.onFetchChaptersDone() view.onFetchChaptersDone()
}, ChaptersController::onFetchChaptersError) },
ChaptersController::onFetchChaptersError
)
} }
/** /**
@ -200,9 +205,10 @@ class ChaptersPresenter(
} }
// Force UI update if downloaded filter active and download finished. // Force UI update if downloaded filter active and download finished.
if (onlyDownloaded() && download.status == Download.DOWNLOADED) if (onlyDownloaded() && download.status == Download.DOWNLOADED) {
refreshChapters() refreshChapters()
} }
}
/** /**
* Returns the next unread chapter or null if everything is read. * Returns the next unread chapter or null if everything is read.
@ -263,9 +269,12 @@ class ChaptersPresenter(
.doOnNext { if (onlyDownloaded()) refreshChapters() } .doOnNext { if (onlyDownloaded()) refreshChapters() }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ -> .subscribeFirst(
{ view, _ ->
view.onChaptersDeleted(chapters) view.onChaptersDeleted(chapters)
}, ChaptersController::onChaptersDeletedError) },
ChaptersController::onChaptersDeletedError
)
} }
/** /**

View file

@ -24,10 +24,12 @@ class DownloadCustomChaptersDialog<T> : DialogController
* Initialize dialog. * Initialize dialog.
* @param maxChapters maximal number of chapters that user can download. * @param maxChapters maximal number of chapters that user can download.
*/ */
constructor(target: T, maxChapters: Int) : super(Bundle().apply { constructor(target: T, maxChapters: Int) : super(
Bundle().apply {
// Add maximum number of chapters to download value to bundle. // Add maximum number of chapters to download value to bundle.
putInt(KEY_ITEM_MAX, maxChapters) putInt(KEY_ITEM_MAX, maxChapters)
}) { }
) {
targetController = target targetController = target
this.maxChapters = maxChapters this.maxChapters = maxChapters
} }

View file

@ -31,7 +31,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.recent.history.HistoryController import eu.kanade.tachiyomi.ui.recent.history.HistoryController
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.lang.truncateCenter import eu.kanade.tachiyomi.util.lang.truncateCenter
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
@ -217,12 +217,14 @@ class MangaInfoController(private val fromSource: Boolean = false) :
} }
// Update status TextView. // Update status TextView.
binding.mangaStatus.setText(when (manga.status) { binding.mangaStatus.setText(
when (manga.status) {
SManga.ONGOING -> R.string.ongoing SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed SManga.COMPLETED -> R.string.completed
SManga.LICENSED -> R.string.licensed SManga.LICENSED -> R.string.licensed
else -> R.string.unknown else -> R.string.unknown
}) }
)
// Set the favorite drawable to the correct one. // Set the favorite drawable to the correct one.
setFavoriteButtonState(manga.favorite) setFavoriteButtonState(manga.favorite)
@ -291,24 +293,27 @@ class MangaInfoController(private val fromSource: Boolean = false) :
val isExpanded = binding.mangaInfoToggle.text == context.getString(R.string.manga_info_collapse) val isExpanded = binding.mangaInfoToggle.text == context.getString(R.string.manga_info_collapse)
binding.mangaInfoToggle.text = binding.mangaInfoToggle.text =
if (isExpanded) if (isExpanded) {
context.getString(R.string.manga_info_expand) context.getString(R.string.manga_info_expand)
else } else {
context.getString(R.string.manga_info_collapse) context.getString(R.string.manga_info_collapse)
}
with(binding.mangaSummary) { with(binding.mangaSummary) {
maxLines = maxLines =
if (isExpanded) if (isExpanded) {
3 3
else } else {
Int.MAX_VALUE Int.MAX_VALUE
}
ellipsize = ellipsize =
if (isExpanded) if (isExpanded) {
TextUtils.TruncateAt.END TextUtils.TruncateAt.END
else } else {
null null
} }
}
binding.mangaGenresTagsCompact.visibleIf { isExpanded } binding.mangaGenresTagsCompact.visibleIf { isExpanded }
binding.mangaGenresTagsFullChips.visibleIf { !isExpanded } binding.mangaGenresTagsFullChips.visibleIf { !isExpanded }
@ -492,8 +497,10 @@ class MangaInfoController(private val fromSource: Boolean = false) :
val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText(label, content)) clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
activity.toast(view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)), activity.toast(
Toast.LENGTH_SHORT) view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)),
Toast.LENGTH_SHORT
)
} }
/** /**

View file

@ -76,9 +76,12 @@ class MangaInfoPresenter(
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { sendMangaToView() } .doOnNext { sendMangaToView() }
.subscribeFirst({ view, _ -> .subscribeFirst(
{ view, _ ->
view.onFetchMangaDone() view.onFetchMangaDone()
}, MangaInfoController::onFetchMangaError) },
MangaInfoController::onFetchMangaError
)
} }
/** /**

View file

@ -19,9 +19,11 @@ class SetTrackChaptersDialog<T> : DialogController
private val item: TrackItem private val item: TrackItem
constructor(target: T, item: TrackItem) : super(Bundle().apply { constructor(target: T, item: TrackItem) : super(
Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track) putSerializable(KEY_ITEM_TRACK, item.track)
}) { }
) {
targetController = target targetController = target
this.item = item this.item = item
} }

View file

@ -20,9 +20,11 @@ class SetTrackReadingDatesDialog<T> : DialogController
private val dateToUpdate: ReadingDate private val dateToUpdate: ReadingDate
constructor(target: T, dateToUpdate: ReadingDate, item: TrackItem) : super(Bundle().apply { constructor(target: T, dateToUpdate: ReadingDate, item: TrackItem) : super(
Bundle().apply {
putSerializable(SetTrackReadingDatesDialog.KEY_ITEM_TRACK, item.track) putSerializable(SetTrackReadingDatesDialog.KEY_ITEM_TRACK, item.track)
}) { }
) {
targetController = target targetController = target
this.item = item this.item = item
this.dateToUpdate = dateToUpdate this.dateToUpdate = dateToUpdate
@ -40,10 +42,12 @@ class SetTrackReadingDatesDialog<T> : DialogController
val listener = (targetController as? Listener) val listener = (targetController as? Listener)
return MaterialDialog(activity!!) return MaterialDialog(activity!!)
.title(when (dateToUpdate) { .title(
when (dateToUpdate) {
ReadingDate.Start -> R.string.track_started_reading_date ReadingDate.Start -> R.string.track_started_reading_date
ReadingDate.Finish -> R.string.track_finished_reading_date ReadingDate.Finish -> R.string.track_finished_reading_date
}) }
)
.datePicker(currentDate = getCurrentDate()) { _, date -> .datePicker(currentDate = getCurrentDate()) { _, date ->
listener?.setReadingDate(item, dateToUpdate, date.timeInMillis) listener?.setReadingDate(item, dateToUpdate, date.timeInMillis)
} }
@ -60,11 +64,12 @@ class SetTrackReadingDatesDialog<T> : DialogController
ReadingDate.Start -> it.started_reading_date ReadingDate.Start -> it.started_reading_date
ReadingDate.Finish -> it.finished_reading_date ReadingDate.Finish -> it.finished_reading_date
} }
if (date != 0L) if (date != 0L) {
timeInMillis = date timeInMillis = date
} }
} }
} }
}
interface Listener { interface Listener {
fun setReadingDate(item: TrackItem, type: ReadingDate, date: Long) fun setReadingDate(item: TrackItem, type: ReadingDate, date: Long)

View file

@ -19,9 +19,11 @@ class SetTrackScoreDialog<T> : DialogController
private val item: TrackItem private val item: TrackItem
constructor(target: T, item: TrackItem) : super(Bundle().apply { constructor(target: T, item: TrackItem) : super(
Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track) putSerializable(KEY_ITEM_TRACK, item.track)
}) { }
) {
targetController = target targetController = target
this.item = item this.item = item
} }

View file

@ -17,9 +17,11 @@ class SetTrackStatusDialog<T> : DialogController
private val item: TrackItem private val item: TrackItem
constructor(target: T, item: TrackItem) : super(Bundle().apply { constructor(target: T, item: TrackItem) : super(
Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track) putSerializable(KEY_ITEM_TRACK, item.track)
}) { }
) {
targetController = target targetController = target
this.item = item this.item = item
} }

View file

@ -17,7 +17,8 @@ import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.swiperefreshlayout.refreshes import reactivecircus.flowbinding.swiperefreshlayout.refreshes
import timber.log.Timber import timber.log.Timber
class TrackController : NucleusController<TrackControllerBinding, TrackPresenter>(), class TrackController :
NucleusController<TrackControllerBinding, TrackPresenter>(),
TrackAdapter.OnClickListener, TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener, SetTrackStatusDialog.Listener,
SetTrackChaptersDialog.Listener, SetTrackChaptersDialog.Listener,

View file

@ -67,8 +67,10 @@ class TrackPresenter(
.toList() .toList()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ -> view.onRefreshDone() }, .subscribeFirst(
TrackController::onRefreshError) { view, _ -> view.onRefreshDone() },
TrackController::onRefreshError
)
} }
fun search(query: String, service: TrackService) { fun search(query: String, service: TrackService) {
@ -76,19 +78,25 @@ class TrackPresenter(
searchSubscription = service.search(query) searchSubscription = service.search(query)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(TrackController::onSearchResults, .subscribeLatestCache(
TrackController::onSearchResultsError) TrackController::onSearchResults,
TrackController::onSearchResultsError
)
} }
fun registerTracking(item: Track?, service: TrackService) { fun registerTracking(item: Track?, service: TrackService) {
if (item != null) { if (item != null) {
item.manga_id = manga.id!! item.manga_id = manga.id!!
add(service.bind(item) add(
service.bind(item)
.flatMap { db.insertTrack(item).asRxObservable() } .flatMap { db.insertTrack(item).asRxObservable() }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ }, .subscribe(
{ error -> context.toast(error.message) })) { },
{ error -> context.toast(error.message) }
)
)
} else { } else {
unregisterTracking(service) unregisterTracking(service)
} }
@ -103,13 +111,15 @@ class TrackPresenter(
.flatMap { db.insertTrack(track).asRxObservable() } .flatMap { db.insertTrack(track).asRxObservable() }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ -> view.onRefreshDone() }, .subscribeFirst(
{ view, _ -> view.onRefreshDone() },
{ view, error -> { view, error ->
view.onRefreshError(error) view.onRefreshError(error)
// Restart on error to set old values // Restart on error to set old values
fetchTrackings() fetchTrackings()
}) }
)
} }
fun setStatus(item: TrackItem, index: Int) { fun setStatus(item: TrackItem, index: Int) {

View file

@ -40,9 +40,11 @@ class TrackSearchDialog : DialogController {
private val trackController private val trackController
get() = targetController as TrackController get() = targetController as TrackController
constructor(target: TrackController, service: TrackService) : super(Bundle().apply { constructor(target: TrackController, service: TrackService) : super(
Bundle().apply {
putInt(KEY_SERVICE, service.id) putInt(KEY_SERVICE, service.id)
}) { }
) {
targetController = target targetController = target
this.service = service this.service = service
} }

View file

@ -24,7 +24,6 @@ class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>() {
position: Int, position: Int,
payloads: List<Any?>? payloads: List<Any?>?
) { ) {
holder.bind(this) holder.bind(this)
} }

View file

@ -11,7 +11,8 @@ import eu.kanade.tachiyomi.databinding.MigrationControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
class MigrationController : NucleusController<MigrationControllerBinding, MigrationPresenter>(), class MigrationController :
NucleusController<MigrationControllerBinding, MigrationPresenter>(),
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
SourceAdapter.OnSelectClickListener { SourceAdapter.OnSelectClickListener {

View file

@ -34,7 +34,8 @@ class MigrationPresenter(
.asRxObservable() .asRxObservable()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) } .doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) }
.combineLatest(stateRelay.map { it.selectedSource } .combineLatest(
stateRelay.map { it.selectedSource }
.distinctUntilChanged() .distinctUntilChanged()
) { library, source -> library to source } ) { library, source -> library to source }
.filter { (_, source) -> source != null } .filter { (_, source) -> source != null }

View file

@ -8,8 +8,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchPresenter import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy

View file

@ -8,9 +8,9 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchCardItem import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchCardItem
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchItem import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchItem
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchPresenter import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers

View file

@ -40,7 +40,6 @@ data class SourceItem(val source: Source, val header: SelectionHeader? = null) :
position: Int, position: Int,
payloads: List<Any?>? payloads: List<Any?>?
) { ) {
holder.bind(this) holder.bind(this)
} }
} }

View file

@ -50,11 +50,12 @@ class AboutController : SettingsController() {
preference { preference {
titleRes = R.string.version titleRes = R.string.version
summary = if (BuildConfig.DEBUG) summary = if (BuildConfig.DEBUG) {
"Preview r${BuildConfig.COMMIT_COUNT} (${BuildConfig.COMMIT_SHA})" "Preview r${BuildConfig.COMMIT_COUNT} (${BuildConfig.COMMIT_SHA})"
else } else {
"Stable ${BuildConfig.VERSION_NAME}" "Stable ${BuildConfig.VERSION_NAME}"
} }
}
preference { preference {
titleRes = R.string.build_time titleRes = R.string.build_time
summary = getFormattedBuildTime() summary = getFormattedBuildTime()
@ -157,10 +158,12 @@ class AboutController : SettingsController() {
class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) { class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) {
constructor(body: String, url: String) : this(Bundle().apply { constructor(body: String, url: String) : this(
Bundle().apply {
putString(BODY_KEY, body) putString(BODY_KEY, body)
putString(URL_KEY, url) putString(URL_KEY, url)
}) }
)
override fun onCreateDialog(savedViewState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!) return MaterialDialog(activity!!)
@ -190,7 +193,8 @@ class AboutController : SettingsController() {
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME) val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
val outputDf = DateFormat.getDateTimeInstance( val outputDf = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault()) DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault()
)
outputDf.timeZone = TimeZone.getDefault() outputDf.timeZone = TimeZone.getDefault()
buildTime.toDateTimestampString(dateFormat) buildTime.toDateTimestampString(dateFormat)

View file

@ -26,7 +26,8 @@ class ChapterLoadByNumber {
// If there is only one chapter for this number, use it // If there is only one chapter for this number, use it
chaptersForNumber.size == 1 -> chaptersForNumber.first() chaptersForNumber.size == 1 -> chaptersForNumber.first()
// Prefer a chapter of the same scanlator as the selected // Prefer a chapter of the same scanlator as the selected
else -> chaptersForNumber.find { it.scanlator == selectedChapter.scanlator } else ->
chaptersForNumber.find { it.scanlator == selectedChapter.scanlator }
?: chaptersForNumber.first() ?: chaptersForNumber.first()
} }
chapters.add(preferredChapter) chapters.add(preferredChapter)

View file

@ -127,10 +127,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
* Called when the activity is created. Initializes the presenter and configuration. * Called when the activity is created. Initializes the presenter and configuration.
*/ */
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(when (preferences.readerTheme().get()) { setTheme(
when (preferences.readerTheme().get()) {
0 -> R.style.Theme_Reader_Light 0 -> R.style.Theme_Reader_Light
else -> R.style.Theme_Reader else -> R.style.Theme_Reader
}) }
)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ReaderActivityBinding.inflate(layoutInflater) binding = ReaderActivityBinding.inflate(layoutInflater)
@ -278,7 +280,8 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
insets.systemWindowInsetLeft, insets.systemWindowInsetLeft,
insets.systemWindowInsetTop, insets.systemWindowInsetTop,
insets.systemWindowInsetRight, insets.systemWindowInsetRight,
insets.systemWindowInsetBottom) insets.systemWindowInsetBottom
)
} }
insets insets
} }
@ -293,20 +296,22 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
}) })
binding.leftChapter.setOnClickListener { binding.leftChapter.setOnClickListener {
if (viewer != null) { if (viewer != null) {
if (viewer is R2LPagerViewer) if (viewer is R2LPagerViewer) {
loadNextChapter() loadNextChapter()
else } else {
loadPreviousChapter() loadPreviousChapter()
} }
} }
}
binding.rightChapter.setOnClickListener { binding.rightChapter.setOnClickListener {
if (viewer != null) { if (viewer != null) {
if (viewer is R2LPagerViewer) if (viewer is R2LPagerViewer) {
loadPreviousChapter() loadPreviousChapter()
else } else {
loadNextChapter() loadNextChapter()
} }
} }
}
// Set initial visibility // Set initial visibility
setMenuVisibility(menuVisible) setMenuVisibility(menuVisible)
@ -590,11 +595,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
* depending on the [result]. * depending on the [result].
*/ */
fun onSetAsCoverResult(result: ReaderPresenter.SetAsCoverResult) { fun onSetAsCoverResult(result: ReaderPresenter.SetAsCoverResult) {
toast(when (result) { toast(
when (result) {
Success -> R.string.cover_updated Success -> R.string.cover_updated
AddToLibraryFirst -> R.string.notification_first_add_to_library AddToLibraryFirst -> R.string.notification_first_add_to_library
Error -> R.string.notification_cover_update_failed Error -> R.string.notification_cover_update_failed
}) }
)
} }
/** /**
@ -700,11 +707,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
* Sets the 32-bit color mode according to [enabled]. * Sets the 32-bit color mode according to [enabled].
*/ */
private fun setTrueColor(enabled: Boolean) { private fun setTrueColor(enabled: Boolean) {
if (enabled) if (enabled) {
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.ARGB_8888) SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.ARGB_8888)
else } else {
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.RGB_565) SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.RGB_565)
} }
}
@TargetApi(Build.VERSION_CODES.P) @TargetApi(Build.VERSION_CODES.P)
private fun setCutoutShort(enabled: Boolean) { private fun setCutoutShort(enabled: Boolean) {

View file

@ -214,9 +214,10 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet
brightness_overlay.gone() brightness_overlay.gone()
} }
if (!isDisabled) if (!isDisabled) {
txt_brightness_seekbar_value.text = value.toString() txt_brightness_seekbar_value.text = value.toString()
} }
}
/** /**
* Manages the color filter value subscription * Manages the color filter value subscription

View file

@ -17,14 +17,16 @@ class ReaderColorFilterView(
fun setFilterColor(color: Int, filterMode: Int) { fun setFilterColor(color: Int, filterMode: Int) {
colorFilterPaint.color = color colorFilterPaint.color = color
colorFilterPaint.xfermode = PorterDuffXfermode(when (filterMode) { colorFilterPaint.xfermode = PorterDuffXfermode(
when (filterMode) {
1 -> PorterDuff.Mode.MULTIPLY 1 -> PorterDuff.Mode.MULTIPLY
2 -> PorterDuff.Mode.SCREEN 2 -> PorterDuff.Mode.SCREEN
3 -> PorterDuff.Mode.OVERLAY 3 -> PorterDuff.Mode.OVERLAY
4 -> PorterDuff.Mode.LIGHTEN 4 -> PorterDuff.Mode.LIGHTEN
5 -> PorterDuff.Mode.DARKEN 5 -> PorterDuff.Mode.DARKEN
else -> PorterDuff.Mode.SRC_OVER else -> PorterDuff.Mode.SRC_OVER
}) }
)
invalidate() invalidate()
} }

View file

@ -100,8 +100,10 @@ class ReaderPresenter(
if ( if (
(manga.readFilter == Manga.SHOW_READ && !it.read) || (manga.readFilter == Manga.SHOW_READ && !it.read) ||
(manga.readFilter == Manga.SHOW_UNREAD && it.read) || (manga.readFilter == Manga.SHOW_UNREAD && it.read) ||
(manga.downloadedFilter == Manga.SHOW_DOWNLOADED && (
!downloadManager.isChapterDownloaded(it, manga)) || manga.downloadedFilter == Manga.SHOW_DOWNLOADED &&
!downloadManager.isChapterDownloaded(it, manga)
) ||
(manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED && !it.bookmark) (manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED && !it.bookmark)
) { ) {
return@filter false return@filter false
@ -201,9 +203,12 @@ class ReaderPresenter(
.first() .first()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { init(it, initialChapterId) } .doOnNext { init(it, initialChapterId) }
.subscribeFirst({ _, _ -> .subscribeFirst(
{ _, _ ->
// Ignore onNext event // Ignore onNext event
}, ReaderActivity::setInitialChapterError) },
ReaderActivity::setInitialChapterError
)
} }
/** /**
@ -245,9 +250,12 @@ class ReaderPresenter(
.flatMap { getLoadObservable(loader!!, it) } .flatMap { getLoadObservable(loader!!, it) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ _, _ -> .subscribeFirst(
{ _, _ ->
// Ignore onNext event // Ignore onNext event
}, ReaderActivity::setInitialChapterError) },
ReaderActivity::setInitialChapterError
)
} }
/** /**
@ -262,13 +270,17 @@ class ReaderPresenter(
chapter: ReaderChapter chapter: ReaderChapter
): Observable<ViewerChapters> { ): Observable<ViewerChapters> {
return loader.loadChapter(chapter) return loader.loadChapter(chapter)
.andThen(Observable.fromCallable { .andThen(
Observable.fromCallable {
val chapterPos = chapterList.indexOf(chapter) val chapterPos = chapterList.indexOf(chapter)
ViewerChapters(chapter, ViewerChapters(
chapter,
chapterList.getOrNull(chapterPos - 1), chapterList.getOrNull(chapterPos - 1),
chapterList.getOrNull(chapterPos + 1)) chapterList.getOrNull(chapterPos + 1)
}) )
}
)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { newChapters -> .doOnNext { newChapters ->
val oldChapters = viewerChaptersRelay.value val oldChapters = viewerChaptersRelay.value
@ -312,11 +324,14 @@ class ReaderPresenter(
activeChapterSubscription = getLoadObservable(loader, chapter) activeChapterSubscription = getLoadObservable(loader, chapter)
.doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) } .doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) }
.doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) } .doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) }
.subscribeFirst({ view, _ -> .subscribeFirst(
{ view, _ ->
view.moveToPageIndex(0) view.moveToPageIndex(0)
}, { _, _ -> },
{ _, _ ->
// Ignore onError event, viewers handle that state // Ignore onError event, viewers handle that state
}) }
)
} }
/** /**
@ -509,9 +524,11 @@ class ReaderPresenter(
notifier.onClear() notifier.onClear()
// Pictures directory. // Pictures directory.
val destDir = File(Environment.getExternalStorageDirectory().absolutePath + val destDir = File(
Environment.getExternalStorageDirectory().absolutePath +
File.separator + Environment.DIRECTORY_PICTURES + File.separator + Environment.DIRECTORY_PICTURES +
File.separator + "Tachiyomi") File.separator + "Tachiyomi"
)
// Copy file in background. // Copy file in background.
Observable.fromCallable { saveImage(page, destDir, manga) } Observable.fromCallable { saveImage(page, destDir, manga) }
@ -614,7 +631,8 @@ class ReaderPresenter(
db.getTracks(manga).asRxSingle() db.getTracks(manga).asRxSingle()
.flatMapCompletable { trackList -> .flatMapCompletable { trackList ->
Completable.concat(trackList.map { track -> Completable.concat(
trackList.map { track ->
val service = trackManager.getService(track.sync_id) val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged && chapterRead > track.last_chapter_read) { if (service != null && service.isLogged && chapterRead > track.last_chapter_read) {
track.last_chapter_read = chapterRead track.last_chapter_read = chapterRead
@ -628,7 +646,8 @@ class ReaderPresenter(
} else { } else {
Completable.complete() Completable.complete()
} }
}) }
)
} }
.onErrorComplete() .onErrorComplete()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())

View file

@ -66,13 +66,17 @@ class SaveImageNotifier(private val context: Context) {
setContentIntent(NotificationHandler.openImagePendingActivity(context, file)) setContentIntent(NotificationHandler.openImagePendingActivity(context, file))
// Share action // Share action
addAction(R.drawable.ic_share_24dp, addAction(
R.drawable.ic_share_24dp,
context.getString(R.string.action_share), context.getString(R.string.action_share),
NotificationReceiver.shareImagePendingBroadcast(context, file.absolutePath, notificationId)) NotificationReceiver.shareImagePendingBroadcast(context, file.absolutePath, notificationId)
)
// Delete action // Delete action
addAction(R.drawable.ic_delete_24dp, addAction(
R.drawable.ic_delete_24dp,
context.getString(R.string.action_delete), context.getString(R.string.action_delete),
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId)) NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId)
)
updateNotification() updateNotification()
} }

View file

@ -44,10 +44,12 @@ class EpubPageLoader(file: File) : PageLoader() {
* Returns an observable that emits a ready state unless the loader was recycled. * Returns an observable that emits a ready state unless the loader was recycled.
*/ */
override fun getPage(page: ReaderPage): Observable<Int> { override fun getPage(page: ReaderPage): Observable<Int> {
return Observable.just(if (isRecycled) { return Observable.just(
if (isRecycled) {
Page.ERROR Page.ERROR
} else { } else {
Page.READY Page.READY
}) }
)
} }
} }

View file

@ -46,12 +46,15 @@ class HttpPageLoader(
.concatMap { source.fetchImageFromCacheThenNet(it) } .concatMap { source.fetchImageFromCacheThenNet(it) }
.repeat() .repeat()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe({ .subscribe(
}, { error -> {
},
{ error ->
if (error !is InterruptedException) { if (error !is InterruptedException) {
Timber.e(error) Timber.e(error)
} }
}) }
)
} }
/** /**
@ -186,11 +189,12 @@ class HttpPageLoader(
* @param page the page whose source image has to be downloaded. * @param page the page whose source image has to be downloaded.
*/ */
private fun HttpSource.fetchImageFromCacheThenNet(page: ReaderPage): Observable<ReaderPage> { private fun HttpSource.fetchImageFromCacheThenNet(page: ReaderPage): Observable<ReaderPage> {
return if (page.imageUrl.isNullOrEmpty()) return if (page.imageUrl.isNullOrEmpty()) {
getImageUrl(page).flatMap { getCachedImage(it) } getImageUrl(page).flatMap { getCachedImage(it) }
else } else {
getCachedImage(page) getCachedImage(page)
} }
}
private fun HttpSource.getImageUrl(page: ReaderPage): Observable<ReaderPage> { private fun HttpSource.getImageUrl(page: ReaderPage): Observable<ReaderPage> {
page.status = Page.LOAD_PAGE page.status = Page.LOAD_PAGE

View file

@ -60,11 +60,13 @@ class RarPageLoader(file: File) : PageLoader() {
* Returns an observable that emits a ready state unless the loader was recycled. * Returns an observable that emits a ready state unless the loader was recycled.
*/ */
override fun getPage(page: ReaderPage): Observable<Int> { override fun getPage(page: ReaderPage): Observable<Int> {
return Observable.just(if (isRecycled) { return Observable.just(
if (isRecycled) {
Page.ERROR Page.ERROR
} else { } else {
Page.READY Page.READY
}) }
)
} }
/** /**

View file

@ -49,10 +49,12 @@ class ZipPageLoader(file: File) : PageLoader() {
* Returns an observable that emits a ready state unless the loader was recycled. * Returns an observable that emits a ready state unless the loader was recycled.
*/ */
override fun getPage(page: ReaderPage): Observable<Int> { override fun getPage(page: ReaderPage): Observable<Int> {
return Observable.just(if (isRecycled) { return Observable.just(
if (isRecycled) {
Page.ERROR Page.ERROR
} else { } else {
Page.READY Page.READY
}) }
)
} }
} }

View file

@ -61,7 +61,8 @@ class ReaderProgressBar @JvmOverloads constructor(
* The rotation animation to use while the progress bar is visible. * The rotation animation to use while the progress bar is visible.
*/ */
private val rotationAnimation by lazy { private val rotationAnimation by lazy {
RotateAnimation(0f, 360f, RotateAnimation(
0f, 360f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f Animation.RELATIVE_TO_SELF, 0.5f
).apply { ).apply {

Some files were not shown because too many files have changed in this diff Show more