mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 21:11:52 +01:00
Linting fixes
This commit is contained in:
parent
4da760d614
commit
3f63b320c4
@ -22,7 +22,6 @@ import uy.kohesive.injekt.api.get
|
||||
class AppModule(val app: Application) : InjektModule {
|
||||
|
||||
override fun InjektRegistrar.registerInjectables() {
|
||||
|
||||
addSingleton(app)
|
||||
|
||||
addSingletonFactory { PreferencesHelper(app) }
|
||||
|
@ -33,7 +33,8 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
|
||||
if (interval > 0) {
|
||||
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
||||
interval.toLong(), TimeUnit.HOURS,
|
||||
10, TimeUnit.MINUTES)
|
||||
10, TimeUnit.MINUTES
|
||||
)
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
||||
|
@ -85,7 +85,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
|
||||
private fun initParser(): Gson = when (version) {
|
||||
1 -> GsonBuilder().create()
|
||||
2 -> GsonBuilder()
|
||||
2 ->
|
||||
GsonBuilder()
|
||||
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
|
||||
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
|
||||
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
||||
@ -442,8 +443,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
||||
|
||||
// Return if fetch is needed
|
||||
if (dbChapters.isEmpty() || dbChapters.size < chapters.size)
|
||||
if (dbChapters.isEmpty() || dbChapters.size < chapters.size) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (chapter in chapters) {
|
||||
val pos = dbChapters.indexOf(chapter)
|
||||
|
@ -143,7 +143,8 @@ class BackupRestoreService : Service() {
|
||||
startForeground(Notifications.ID_RESTORE, notifier.showRestoreProgress().build())
|
||||
|
||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock")
|
||||
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock"
|
||||
)
|
||||
wakeLock.acquire()
|
||||
}
|
||||
|
||||
@ -184,7 +185,8 @@ class BackupRestoreService : Service() {
|
||||
subscription = Observable.using(
|
||||
{ db.lowLevel().beginTransaction() },
|
||||
{ getRestoreObservable(uri).doOnNext { db.lowLevel().setTransactionSuccessful() } },
|
||||
{ executor.execute { db.lowLevel().endTransaction() } })
|
||||
{ executor.execute { db.lowLevel().endTransaction() } }
|
||||
)
|
||||
.doAfterTerminate { stopSelf(startId) }
|
||||
.subscribeOn(Schedulers.from(executor))
|
||||
.subscribe()
|
||||
@ -231,14 +233,22 @@ class BackupRestoreService : Service() {
|
||||
.concatMap {
|
||||
val obj = it.asJsonObject
|
||||
val manga = backupManager.parser.fromJson<MangaImpl>(obj.get(MANGA))
|
||||
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(obj.get(CHAPTERS)
|
||||
?: JsonArray())
|
||||
val categories = backupManager.parser.fromJson<List<String>>(obj.get(CATEGORIES)
|
||||
?: JsonArray())
|
||||
val history = backupManager.parser.fromJson<List<DHistory>>(obj.get(HISTORY)
|
||||
?: JsonArray())
|
||||
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(obj.get(TRACK)
|
||||
?: JsonArray())
|
||||
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
|
||||
obj.get(CHAPTERS)
|
||||
?: JsonArray()
|
||||
)
|
||||
val categories = backupManager.parser.fromJson<List<String>>(
|
||||
obj.get(CATEGORIES)
|
||||
?: 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)
|
||||
if (observable != null) {
|
||||
@ -379,7 +389,6 @@ class BackupRestoreService : Service() {
|
||||
history: List<DHistory>,
|
||||
tracks: List<Track>
|
||||
): Observable<Manga> {
|
||||
|
||||
return Observable.just(backupManga)
|
||||
.flatMap { manga ->
|
||||
if (!backupManager.restoreChaptersForManga(manga, chapters)) {
|
||||
|
@ -46,10 +46,12 @@ class ChapterCache(private val context: Context) {
|
||||
private val gson: Gson by injectLazy()
|
||||
|
||||
/** 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_VALUE_COUNT,
|
||||
PARAMETER_CACHE_SIZE)
|
||||
PARAMETER_CACHE_SIZE
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns directory of cache.
|
||||
@ -77,8 +79,9 @@ class ChapterCache(private val context: Context) {
|
||||
*/
|
||||
fun removeFileFromCache(file: String): Boolean {
|
||||
// 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 try {
|
||||
// Remove the extension from the file to get the key of the cache
|
||||
|
@ -56,8 +56,9 @@ class CoverCache(private val context: Context) {
|
||||
*/
|
||||
fun deleteFromCache(thumbnailUrl: String?): Boolean {
|
||||
// Check if url is empty.
|
||||
if (thumbnailUrl.isNullOrEmpty())
|
||||
if (thumbnailUrl.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove file.
|
||||
val file = getCoverFile(thumbnailUrl)
|
||||
|
@ -44,8 +44,10 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
||||
|
||||
// Fix kissmanga covers after supporting cloudflare
|
||||
db.execSQL("""UPDATE mangas SET thumbnail_url =
|
||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""")
|
||||
db.execSQL(
|
||||
"""UPDATE mangas SET thumbnail_url =
|
||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4"""
|
||||
)
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
// Initialize history tables
|
||||
|
@ -11,18 +11,22 @@ interface CategoryQueries : DbProvider {
|
||||
|
||||
fun getCategories() = db.get()
|
||||
.listOfObjects(Category::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(CategoryTable.TABLE)
|
||||
.orderBy(CategoryTable.COL_ORDER)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getCategoriesForManga(manga: Manga) = db.get()
|
||||
.listOfObjects(Category::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getCategoriesForMangaQuery())
|
||||
.args(manga.id)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertCategory(category: Category) = db.put().`object`(category).prepare()
|
||||
|
@ -17,48 +17,58 @@ interface ChapterQueries : DbProvider {
|
||||
|
||||
fun getChapters(manga: Manga) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getRecentChapters(date: Date) = db.get()
|
||||
.listOfObjects(MangaChapter::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentsQuery())
|
||||
.args(date.time)
|
||||
.observesTables(ChapterTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getChapter(id: Long) = db.get()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_ID} = ?")
|
||||
.whereArgs(id)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChapter(url: String) = db.get()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(url)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChapter(url: String, mangaId: Long) = db.get()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(url, mangaId)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
||||
|
@ -24,30 +24,36 @@ interface HistoryQueries : DbProvider {
|
||||
*/
|
||||
fun getRecentManga(date: Date) = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentMangasQuery())
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getHistoryByMangaId(mangaId: Long) = db.get()
|
||||
.listOfObjects(History::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getHistoryByMangaId())
|
||||
.args(mangaId)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getHistoryByChapterUrl(chapterUrl: String) = db.get()
|
||||
.`object`(History::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getHistoryByChapterUrl())
|
||||
.args(chapterUrl)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
/**
|
||||
@ -71,16 +77,20 @@ interface HistoryQueries : DbProvider {
|
||||
.prepare()
|
||||
|
||||
fun deleteHistory() = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(HistoryTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun deleteHistoryNoLastRead() = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(HistoryTable.TABLE)
|
||||
.where("${HistoryTable.COL_LAST_READ} = ?")
|
||||
.whereArgs(0)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
@ -15,11 +15,13 @@ interface MangaCategoryQueries : DbProvider {
|
||||
fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare()
|
||||
|
||||
fun deleteOldMangasCategories(mangas: List<Manga>) = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(MangaCategoryTable.TABLE)
|
||||
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
|
||||
.whereArgs(*mangas.map { it.id }.toTypedArray())
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
|
||||
|
@ -21,46 +21,56 @@ interface MangaQueries : DbProvider {
|
||||
|
||||
fun getMangas() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getLibraryMangas() = db.get()
|
||||
.listOfObjects(LibraryManga::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(libraryQuery)
|
||||
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getFavoriteMangas() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||
.whereArgs(1)
|
||||
.orderBy(MangaTable.COL_TITLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getManga(url: String, sourceId: Long) = db.get()
|
||||
.`object`(Manga::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
|
||||
.whereArgs(url, sourceId)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getManga(id: Long) = db.get()
|
||||
.`object`(Manga::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(id)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.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 deleteMangasNotInLibrary() = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||
.whereArgs(0)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun deleteMangas() = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getLastReadManga() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getLastReadMangaQuery())
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getTotalChapterManga() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getTotalChapterMangaQuery())
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getLatestChapterManga() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getLatestChapterMangaQuery())
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
val libraryQuery = """
|
||||
val libraryQuery =
|
||||
"""
|
||||
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
|
||||
FROM (
|
||||
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.
|
||||
*/
|
||||
fun getRecentsQuery() = """
|
||||
fun getRecentsQuery() =
|
||||
"""
|
||||
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}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Chapter.COL_DATE_UPLOAD} > ?
|
||||
@ -47,7 +49,8 @@ fun getRecentsQuery() = """
|
||||
* and are read after the given time period
|
||||
* @return return limit is 25
|
||||
*/
|
||||
fun getRecentMangasQuery() = """
|
||||
fun getRecentMangasQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
@ -65,7 +68,8 @@ fun getRecentMangasQuery() = """
|
||||
LIMIT 25
|
||||
"""
|
||||
|
||||
fun getHistoryByMangaId() = """
|
||||
fun getHistoryByMangaId() =
|
||||
"""
|
||||
SELECT ${History.TABLE}.*
|
||||
FROM ${History.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}
|
||||
"""
|
||||
|
||||
fun getHistoryByChapterUrl() = """
|
||||
fun getHistoryByChapterUrl() =
|
||||
"""
|
||||
SELECT ${History.TABLE}.*
|
||||
FROM ${History.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}
|
||||
"""
|
||||
|
||||
fun getLastReadMangaQuery() = """
|
||||
fun getLastReadMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
@ -93,7 +99,8 @@ fun getLastReadMangaQuery() = """
|
||||
ORDER BY max DESC
|
||||
"""
|
||||
|
||||
fun getTotalChapterMangaQuery() = """
|
||||
fun getTotalChapterMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
@ -102,7 +109,8 @@ fun getTotalChapterMangaQuery() = """
|
||||
ORDER by COUNT(*)
|
||||
"""
|
||||
|
||||
fun getLatestChapterMangaQuery() = """
|
||||
fun getLatestChapterMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD}) AS max
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
@ -114,7 +122,8 @@ fun getLatestChapterMangaQuery() = """
|
||||
/**
|
||||
* Query to get the categories for a manga.
|
||||
*/
|
||||
fun getCategoriesForMangaQuery() = """
|
||||
fun getCategoriesForMangaQuery() =
|
||||
"""
|
||||
SELECT ${Category.TABLE}.* FROM ${Category.TABLE}
|
||||
JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} =
|
||||
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}
|
||||
|
@ -12,11 +12,13 @@ interface TrackQueries : DbProvider {
|
||||
|
||||
fun getTracks(manga: Manga) = db.get()
|
||||
.listOfObjects(Track::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(TrackTable.TABLE)
|
||||
.where("${TrackTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.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 deleteTrackForManga(manga: Manga, sync: TrackService) = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(TrackTable.TABLE)
|
||||
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
|
||||
.whereArgs(manga.id, sync.id)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
@ -19,11 +19,13 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
|
||||
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
|
||||
val updateQuery = mapToUpdateQuery(history)
|
||||
|
||||
val cursor = db.lowLevel().query(Query.builder()
|
||||
val cursor = db.lowLevel().query(
|
||||
Query.builder()
|
||||
.table(updateQuery.table())
|
||||
.where(updateQuery.where())
|
||||
.whereArgs(updateQuery.whereArgs())
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
|
||||
val putResult: PutResult
|
||||
|
||||
|
@ -13,7 +13,8 @@ object CategoryTable {
|
||||
const val COL_FLAGS = "flags"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_NAME TEXT NOT NULL,
|
||||
$COL_ORDER INTEGER NOT NULL,
|
||||
|
@ -29,7 +29,8 @@ object ChapterTable {
|
||||
const val COL_SOURCE_ORDER = "source_order"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_MANGA_ID INTEGER NOT NULL,
|
||||
$COL_URL TEXT NOT NULL,
|
||||
|
@ -31,7 +31,8 @@ object HistoryTable {
|
||||
* query to create history table
|
||||
*/
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_CHAPTER_ID INTEGER NOT NULL UNIQUE,
|
||||
$COL_LAST_READ LONG,
|
||||
|
@ -11,7 +11,8 @@ object MangaCategoryTable {
|
||||
const val COL_CATEGORY_ID = "category_id"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_MANGA_ID INTEGER NOT NULL,
|
||||
$COL_CATEGORY_ID INTEGER NOT NULL,
|
||||
|
@ -39,7 +39,8 @@ object MangaTable {
|
||||
const val COL_CATEGORY = "category"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_SOURCE INTEGER NOT NULL,
|
||||
$COL_URL TEXT NOT NULL,
|
||||
|
@ -31,7 +31,8 @@ object TrackTable {
|
||||
const val COL_FINISH_DATE = "finish_date"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_MANGA_ID INTEGER NOT NULL,
|
||||
$COL_SYNC_ID INTEGER NOT NULL,
|
||||
|
@ -87,9 +87,11 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||
isDownloading = true
|
||||
// Pause action
|
||||
addAction(R.drawable.ic_pause_24dp,
|
||||
addAction(
|
||||
R.drawable.ic_pause_24dp,
|
||||
context.getString(R.string.action_pause),
|
||||
NotificationReceiver.pauseDownloadsPendingBroadcast(context))
|
||||
NotificationReceiver.pauseDownloadsPendingBroadcast(context)
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||
// Resume action
|
||||
addAction(R.drawable.ic_play_arrow_24dp,
|
||||
addAction(
|
||||
R.drawable.ic_play_arrow_24dp,
|
||||
context.getString(R.string.action_resume),
|
||||
NotificationReceiver.resumeDownloadsPendingBroadcast(context))
|
||||
NotificationReceiver.resumeDownloadsPendingBroadcast(context)
|
||||
)
|
||||
// Clear action
|
||||
addAction(R.drawable.ic_close_24dp,
|
||||
addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
context.getString(R.string.action_cancel_all),
|
||||
NotificationReceiver.clearDownloadsPendingBroadcast(context))
|
||||
NotificationReceiver.clearDownloadsPendingBroadcast(context)
|
||||
)
|
||||
}
|
||||
|
||||
// Show notification.
|
||||
@ -173,8 +179,10 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
fun onError(error: String? = null, chapter: String? = null) {
|
||||
// Create notification
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(chapter
|
||||
?: context.getString(R.string.download_notifier_downloader_title))
|
||||
setContentTitle(
|
||||
chapter
|
||||
?: context.getString(R.string.download_notifier_downloader_title)
|
||||
)
|
||||
setContentText(error ?: context.getString(R.string.download_notifier_unknown_error))
|
||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
clearActions()
|
||||
|
@ -125,12 +125,15 @@ class DownloadService : Service() {
|
||||
subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ state ->
|
||||
.subscribe(
|
||||
{ state ->
|
||||
onNetworkStateChanged(state)
|
||||
}, {
|
||||
},
|
||||
{
|
||||
toast(R.string.download_queue_error)
|
||||
stopSelf()
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,12 +165,13 @@ class DownloadService : Service() {
|
||||
*/
|
||||
private fun listenDownloaderState() {
|
||||
subscriptions += downloadManager.runningRelay.subscribe { running ->
|
||||
if (running)
|
||||
if (running) {
|
||||
wakeLock.acquireIfNeeded()
|
||||
else
|
||||
} else {
|
||||
wakeLock.releaseIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the wake lock if it's held.
|
||||
|
@ -100,11 +100,13 @@ class Downloader(
|
||||
* @return true if the downloader is started, false otherwise.
|
||||
*/
|
||||
fun start(): Boolean {
|
||||
if (isRunning || queue.isEmpty())
|
||||
if (isRunning || queue.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!subscriptions.hasSubscriptions())
|
||||
if (!subscriptions.hasSubscriptions()) {
|
||||
initializeSubscriptions()
|
||||
}
|
||||
|
||||
val pending = queue.filter { it.status != Download.DOWNLOADED }
|
||||
pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE }
|
||||
@ -177,13 +179,16 @@ class Downloader(
|
||||
.concatMap { downloadChapter(it).subscribeOn(Schedulers.io()) }
|
||||
.onBackpressureBuffer()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
completeDownload(it)
|
||||
}, { error ->
|
||||
},
|
||||
{ error ->
|
||||
DownloadService.stop(context)
|
||||
Timber.e(error)
|
||||
notifier.onError(error.message)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -304,8 +309,9 @@ class Downloader(
|
||||
*/
|
||||
private fun getOrDownloadImage(page: Page, download: Download, tmpDir: UniFile): Observable<Page> {
|
||||
// If the image URL is empty, do nothing
|
||||
if (page.imageUrl == null)
|
||||
if (page.imageUrl == null) {
|
||||
return Observable.just(page)
|
||||
}
|
||||
|
||||
val filename = String.format("%03d", page.number)
|
||||
val tmpFile = tmpDir.findFile("$filename.tmp")
|
||||
@ -317,10 +323,11 @@ class Downloader(
|
||||
val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") }
|
||||
|
||||
// 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)
|
||||
else
|
||||
} else {
|
||||
downloadImage(page, download.source, tmpDir, filename)
|
||||
}
|
||||
|
||||
return pageObservable
|
||||
// When the image is ready, set image path, progress (just in case) and status
|
||||
@ -400,7 +407,6 @@ class Downloader(
|
||||
tmpDir: UniFile,
|
||||
dirname: String
|
||||
) {
|
||||
|
||||
// Ensure that the chapter folder has all the images.
|
||||
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") }
|
||||
|
||||
|
@ -25,7 +25,9 @@ class LibraryMangaUrlFetcher(
|
||||
|
||||
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
||||
if (!file.exists()) {
|
||||
networkFetcher.loadData(priority, object : DataFetcher.DataCallback<InputStream> {
|
||||
networkFetcher.loadData(
|
||||
priority,
|
||||
object : DataFetcher.DataCallback<InputStream> {
|
||||
override fun onDataReady(data: InputStream?) {
|
||||
if (data != null) {
|
||||
val tmpFile = File(file.path + ".tmp")
|
||||
@ -54,7 +56,8 @@ class LibraryMangaUrlFetcher(
|
||||
override fun onLoadFailed(e: Exception) {
|
||||
callback.onLoadFailed(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
loadFromFile(callback)
|
||||
}
|
||||
|
@ -27,8 +27,10 @@ class TachiGlideModule : AppGlideModule() {
|
||||
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
||||
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024))
|
||||
builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
|
||||
builder.setDefaultTransitionOptions(Drawable::class.java,
|
||||
DrawableTransitionOptions.withCrossFade())
|
||||
builder.setDefaultTransitionOptions(
|
||||
Drawable::class.java,
|
||||
DrawableTransitionOptions.withCrossFade()
|
||||
)
|
||||
}
|
||||
|
||||
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.append(MangaThumbnail::class.java, InputStream::class.java, MangaThumbnailModelLoader.Factory())
|
||||
registry.append(InputStream::class.java, InputStream::class.java, PassthroughModelLoader
|
||||
.Factory())
|
||||
registry.append(
|
||||
InputStream::class.java, InputStream::class.java,
|
||||
PassthroughModelLoader
|
||||
.Factory()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -30,10 +30,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
if (interval > 0) {
|
||||
val restrictions = preferences.libraryUpdateRestriction()!!
|
||||
val acRestriction = "ac" in restrictions
|
||||
val wifiRestriction = if ("wifi" in restrictions)
|
||||
val wifiRestriction = if ("wifi" in restrictions) {
|
||||
NetworkType.UNMETERED
|
||||
else
|
||||
} else {
|
||||
NetworkType.CONNECTED
|
||||
}
|
||||
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(wifiRestriction)
|
||||
@ -42,7 +43,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
||||
interval.toLong(), TimeUnit.HOURS,
|
||||
10, TimeUnit.MINUTES)
|
||||
10, TimeUnit.MINUTES
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
@ -9,7 +9,8 @@ object LibraryUpdateRanker {
|
||||
|
||||
val rankingScheme = listOf(
|
||||
(this::lexicographicRanking)(),
|
||||
(this::latestFirstRanking)())
|
||||
(this::latestFirstRanking)()
|
||||
)
|
||||
|
||||
/**
|
||||
* Provides a total ordering over all the Mangas.
|
||||
|
@ -184,7 +184,8 @@ class LibraryUpdateService(
|
||||
super.onCreate()
|
||||
startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotificationBuilder.build())
|
||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock")
|
||||
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock"
|
||||
)
|
||||
wakeLock.acquire()
|
||||
}
|
||||
|
||||
@ -238,13 +239,17 @@ class LibraryUpdateService(
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe({
|
||||
}, {
|
||||
.subscribe(
|
||||
{
|
||||
},
|
||||
{
|
||||
Timber.e(it)
|
||||
stopSelf(startId)
|
||||
}, {
|
||||
},
|
||||
{
|
||||
stopSelf(startId)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
@ -259,17 +264,18 @@ class LibraryUpdateService(
|
||||
fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> {
|
||||
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
|
||||
|
||||
var listToUpdate = if (categoryId != -1)
|
||||
var listToUpdate = if (categoryId != -1) {
|
||||
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
|
||||
else {
|
||||
} else {
|
||||
val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt)
|
||||
if (categoriesToUpdate.isNotEmpty())
|
||||
if (categoriesToUpdate.isNotEmpty()) {
|
||||
db.getLibraryMangas().executeAsBlocking()
|
||||
.filter { it.category in categoriesToUpdate }
|
||||
.distinctBy { it.id }
|
||||
else
|
||||
} else {
|
||||
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
|
||||
}
|
||||
}
|
||||
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
|
||||
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
|
||||
}
|
||||
@ -315,9 +321,11 @@ class LibraryUpdateService(
|
||||
// Filter out mangas without new chapters (or failed).
|
||||
.filter { pair -> pair.first.isNotEmpty() }
|
||||
.doOnNext {
|
||||
if (downloadNew && (categoriesToDownload.isEmpty() ||
|
||||
manga.category in categoriesToDownload)) {
|
||||
|
||||
if (downloadNew && (
|
||||
categoriesToDownload.isEmpty() ||
|
||||
manga.category in categoriesToDownload
|
||||
)
|
||||
) {
|
||||
downloadChapters(manga, it.first)
|
||||
hasDownloads = true
|
||||
}
|
||||
@ -453,15 +461,19 @@ class LibraryUpdateService(
|
||||
* @param total the total progress.
|
||||
*/
|
||||
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)
|
||||
else
|
||||
} else {
|
||||
manga.title
|
||||
}
|
||||
|
||||
notificationManager.notify(Notifications.ID_LIBRARY_PROGRESS, progressNotificationBuilder
|
||||
notificationManager.notify(
|
||||
Notifications.ID_LIBRARY_PROGRESS,
|
||||
progressNotificationBuilder
|
||||
.setContentTitle(title)
|
||||
.setProgress(total, current, false)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -476,7 +488,9 @@ class LibraryUpdateService(
|
||||
|
||||
NotificationManagerCompat.from(this).apply {
|
||||
// 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))
|
||||
if (updates.size == 1 && !preferences.hideNotificationContent()) {
|
||||
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))
|
||||
|
||||
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)
|
||||
}))
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -500,7 +518,8 @@ class LibraryUpdateService(
|
||||
|
||||
setContentIntent(getNotificationIntent())
|
||||
setAutoCancel(true)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Per-manga notification
|
||||
if (!preferences.hideNotificationContent()) {
|
||||
@ -536,13 +555,21 @@ class LibraryUpdateService(
|
||||
setAutoCancel(true)
|
||||
|
||||
// Mark chapters as read action
|
||||
addAction(R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read),
|
||||
NotificationReceiver.markAsReadPendingBroadcast(this@LibraryUpdateService,
|
||||
manga, chapters, Notifications.ID_NEW_CHAPTERS))
|
||||
addAction(
|
||||
R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read),
|
||||
NotificationReceiver.markAsReadPendingBroadcast(
|
||||
this@LibraryUpdateService,
|
||||
manga, chapters, Notifications.ID_NEW_CHAPTERS
|
||||
)
|
||||
)
|
||||
// View chapters action
|
||||
addAction(R.drawable.ic_book_24dp, getString(R.string.action_view_chapters),
|
||||
NotificationReceiver.openChapterPendingActivity(this@LibraryUpdateService,
|
||||
manga, Notifications.ID_NEW_CHAPTERS))
|
||||
addAction(
|
||||
R.drawable.ic_book_24dp, getString(R.string.action_view_chapters),
|
||||
NotificationReceiver.openChapterPendingActivity(
|
||||
this@LibraryUpdateService,
|
||||
manga, Notifications.ID_NEW_CHAPTERS
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,8 +597,11 @@ class LibraryUpdateService(
|
||||
}
|
||||
|
||||
private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
|
||||
val formatter = DecimalFormat("#.###", DecimalFormatSymbols()
|
||||
.apply { decimalSeparator = '.' })
|
||||
val formatter = DecimalFormat(
|
||||
"#.###",
|
||||
DecimalFormatSymbols()
|
||||
.apply { decimalSeparator = '.' }
|
||||
)
|
||||
|
||||
val displayableChapterNumbers = chapters
|
||||
.filter { it.isRecognizedNumber }
|
||||
|
@ -54,22 +54,35 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
// Clear the download queue
|
||||
ACTION_CLEAR_DOWNLOADS -> downloadManager.clearQueue(true)
|
||||
// Launch share activity and dismiss notification
|
||||
ACTION_SHARE_IMAGE -> shareImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
|
||||
ACTION_SHARE_IMAGE ->
|
||||
shareImage(
|
||||
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Delete image from path and dismiss notification
|
||||
ACTION_DELETE_IMAGE -> deleteImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
|
||||
ACTION_DELETE_IMAGE ->
|
||||
deleteImage(
|
||||
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Share backup file
|
||||
ACTION_SHARE_BACKUP -> shareBackup(context, intent.getParcelableExtra(EXTRA_URI),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
|
||||
ACTION_CANCEL_RESTORE -> cancelRestore(context,
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
|
||||
ACTION_SHARE_BACKUP ->
|
||||
shareBackup(
|
||||
context, intent.getParcelableExtra(EXTRA_URI),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
ACTION_CANCEL_RESTORE -> cancelRestore(
|
||||
context,
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Cancel library update and dismiss notification
|
||||
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context, Notifications.ID_LIBRARY_PROGRESS)
|
||||
// Open reader activity
|
||||
ACTION_OPEN_CHAPTER -> {
|
||||
openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
||||
intent.getLongExtra(EXTRA_CHAPTER_ID, -1))
|
||||
openChapter(
|
||||
context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
||||
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
|
||||
)
|
||||
}
|
||||
// Mark updated manga chapters as read
|
||||
ACTION_MARK_AS_READ -> {
|
||||
|
@ -61,22 +61,34 @@ object Notifications {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
|
||||
val channels = listOf(
|
||||
NotificationChannel(CHANNEL_COMMON, context.getString(R.string.channel_common),
|
||||
NotificationManager.IMPORTANCE_LOW),
|
||||
NotificationChannel(CHANNEL_LIBRARY, context.getString(R.string.channel_library),
|
||||
NotificationManager.IMPORTANCE_LOW).apply {
|
||||
NotificationChannel(
|
||||
CHANNEL_COMMON, context.getString(R.string.channel_common),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_LIBRARY, context.getString(R.string.channel_library),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader),
|
||||
NotificationManager.IMPORTANCE_LOW).apply {
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters),
|
||||
NotificationManager.IMPORTANCE_DEFAULT),
|
||||
NotificationChannel(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 {
|
||||
NotificationChannel(
|
||||
CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
),
|
||||
NotificationChannel(
|
||||
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)
|
||||
}
|
||||
)
|
||||
|
@ -45,12 +45,20 @@ class PreferencesHelper(val context: Context) {
|
||||
private val flowPrefs = FlowSharedPreferences(prefs)
|
||||
|
||||
private val defaultDownloadsDir = Uri.fromFile(
|
||||
File(Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name), "downloads"))
|
||||
File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name),
|
||||
"downloads"
|
||||
)
|
||||
)
|
||||
|
||||
private val defaultBackupDir = Uri.fromFile(
|
||||
File(Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name), "backup"))
|
||||
File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name),
|
||||
"backup"
|
||||
)
|
||||
)
|
||||
|
||||
fun startScreen() = prefs.getInt(Keys.startScreen, 1)
|
||||
|
||||
|
@ -25,7 +25,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||
|
||||
fun addLibManga(track: Track): Observable<Track> {
|
||||
val query = """
|
||||
val query =
|
||||
"""
|
||||
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|
||||
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
|
||||
| id
|
||||
@ -62,7 +63,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
}
|
||||
|
||||
fun updateLibManga(track: Track): Observable<Track> {
|
||||
val query = """
|
||||
val query =
|
||||
"""
|
||||
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|
||||
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|
||||
|id
|
||||
@ -94,7 +96,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
}
|
||||
|
||||
fun search(search: String): Observable<List<TrackSearch>> {
|
||||
val query = """
|
||||
val query =
|
||||
"""
|
||||
|query Search(${'$'}query: String) {
|
||||
|Page (perPage: 50) {
|
||||
|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?> {
|
||||
val query = """
|
||||
val query =
|
||||
"""
|
||||
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|
||||
|Page {
|
||||
|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>> {
|
||||
val query = """
|
||||
val query =
|
||||
"""
|
||||
|query User {
|
||||
|Viewer {
|
||||
|id
|
||||
@ -251,17 +256,24 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
private fun jsonToALManga(struct: JsonObject): ALManga {
|
||||
val date = try {
|
||||
val date = Calendar.getInstance()
|
||||
date.set(struct["startDate"]["year"].nullInt ?: 0, (struct["startDate"]["month"].nullInt
|
||||
?: 0) - 1,
|
||||
struct["startDate"]["day"].nullInt ?: 0)
|
||||
date.set(
|
||||
struct["startDate"]["year"].nullInt ?: 0,
|
||||
(
|
||||
struct["startDate"]["month"].nullInt
|
||||
?: 0
|
||||
) - 1,
|
||||
struct["startDate"]["day"].nullInt ?: 0
|
||||
)
|
||||
date.timeInMillis
|
||||
} catch (_: Exception) {
|
||||
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,
|
||||
date, struct["chapters"].nullInt ?: 0)
|
||||
date, struct["chapters"].nullInt ?: 0
|
||||
)
|
||||
}
|
||||
|
||||
private fun jsonToALUserManga(struct: JsonObject): ALUserManga {
|
||||
|
@ -73,7 +73,8 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
||||
|
||||
fun search(search: String): Observable<List<TrackSearch>> {
|
||||
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")
|
||||
.build()
|
||||
val request = Request.Builder()
|
||||
@ -109,9 +110,15 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
||||
return Track.create(TrackManager.BANGUMI).apply {
|
||||
title = mangas["name"].asString
|
||||
media_id = mangas["id"].asInt
|
||||
score = if (mangas["rating"] != null)
|
||||
(if (mangas["rating"].isJsonObject) mangas["rating"].obj["score"].asFloat else 0f)
|
||||
else 0f
|
||||
score = if (mangas["rating"] != null) {
|
||||
if (mangas["rating"].isJsonObject) {
|
||||
mangas["rating"].obj["score"].asFloat
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
status = Bangumi.DEFAULT_STATUS
|
||||
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()
|
||||
.add("grant_type", "authorization_code")
|
||||
.add("client_id", clientId)
|
||||
@ -196,13 +204,15 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
||||
.appendQueryParameter("redirect_uri", redirectUrl)
|
||||
.build()
|
||||
|
||||
fun refreshTokenRequest(token: String) = POST(oauthUrl,
|
||||
fun refreshTokenRequest(token: String) = POST(
|
||||
oauthUrl,
|
||||
body = FormBody.Builder()
|
||||
.add("grant_type", "refresh_token")
|
||||
.add("client_id", clientId)
|
||||
.add("client_secret", clientSecret)
|
||||
.add("refresh_token", token)
|
||||
.add("redirect_uri", redirectUrl)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +37,10 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
|
||||
|
||||
val authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder()
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.url(originalRequest.url.newBuilder()
|
||||
.addQueryParameter("access_token", currAuth.access_token).build())
|
||||
.url(
|
||||
originalRequest.url.newBuilder()
|
||||
.addQueryParameter("access_token", currAuth.access_token).build()
|
||||
)
|
||||
.build() else originalRequest.newBuilder()
|
||||
.post(addTocken(currAuth.access_token, originalRequest.body as FormBody))
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
@ -54,7 +56,8 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
|
||||
System.currentTimeMillis() / 1000,
|
||||
oauth.expires_in,
|
||||
oauth.refresh_token,
|
||||
this.oauth?.user_id)
|
||||
this.oauth?.user_id
|
||||
)
|
||||
|
||||
bangumi.saveToken(oauth)
|
||||
}
|
||||
|
@ -242,12 +242,14 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
||||
return baseMangaUrl + remoteId
|
||||
}
|
||||
|
||||
fun refreshTokenRequest(token: String) = POST("${loginUrl}oauth/token",
|
||||
fun refreshTokenRequest(token: String) = POST(
|
||||
"${loginUrl}oauth/token",
|
||||
body = FormBody.Builder()
|
||||
.add("grant_type", "refresh_token")
|
||||
.add("client_id", clientId)
|
||||
.add("client_secret", clientSecret)
|
||||
.add("refresh_token", token)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -152,9 +152,10 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
|
||||
var ckCount = 0
|
||||
val url = BASE_URL.toHttpUrlOrNull()!!
|
||||
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++
|
||||
}
|
||||
}
|
||||
|
||||
return ckCount == 2
|
||||
}
|
||||
|
@ -46,10 +46,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
||||
client.newCall(GET(searchUrl(query)))
|
||||
.asObservable()
|
||||
.flatMap { response ->
|
||||
Observable.from(Jsoup.parse(response.consumeBody())
|
||||
Observable.from(
|
||||
Jsoup.parse(response.consumeBody())
|
||||
.select("div.js-categories-seasonal.js-block-list.list")
|
||||
.select("table").select("tbody")
|
||||
.select("tr").drop(1))
|
||||
.select("tr").drop(1)
|
||||
)
|
||||
}
|
||||
.filter { row ->
|
||||
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 {
|
||||
val text = selectText(field, "0000-00-00")!!
|
||||
// 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 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 day = select(id + "_day > 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 GregorianCalendar(year, month - 1, day).timeInMillis
|
||||
}
|
||||
@ -472,8 +476,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
||||
fun copyPersonalFrom(track: Track) {
|
||||
num_read_chapters = track.last_chapter_read.toString()
|
||||
val numScore = track.score.toInt()
|
||||
if (numScore in 1..9)
|
||||
if (numScore in 1..9) {
|
||||
score = numScore.toString()
|
||||
}
|
||||
status = track.status.toString()
|
||||
if (track.started_reading_date == 0L) {
|
||||
start_date_month = ""
|
||||
|
@ -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()
|
||||
.add("grant_type", "authorization_code")
|
||||
.add("client_id", clientId)
|
||||
@ -192,12 +193,14 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
||||
.appendQueryParameter("response_type", "code")
|
||||
.build()
|
||||
|
||||
fun refreshTokenRequest(token: String) = POST(oauthUrl,
|
||||
fun refreshTokenRequest(token: String) = POST(
|
||||
oauthUrl,
|
||||
body = FormBody.Builder()
|
||||
.add("grant_type", "refresh_token")
|
||||
.add("client_id", clientId)
|
||||
.add("client_secret", clientSecret)
|
||||
.add("refresh_token", token)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +37,11 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
setContentText(context.getString(R.string.update_check_notification_update_available))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
// Download action
|
||||
addAction(android.R.drawable.stat_sys_download_done,
|
||||
addAction(
|
||||
android.R.drawable.stat_sys_download_done,
|
||||
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()
|
||||
@ -64,7 +66,8 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<UpdaterJob>(
|
||||
3, TimeUnit.DAYS,
|
||||
3, TimeUnit.HOURS)
|
||||
3, TimeUnit.HOURS
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
@ -69,13 +69,17 @@ internal class UpdaterNotifier(private val context: Context) {
|
||||
setProgress(0, 0, false)
|
||||
// Install action
|
||||
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),
|
||||
NotificationHandler.installApkPendingActivity(context, uri))
|
||||
NotificationHandler.installApkPendingActivity(context, uri)
|
||||
)
|
||||
// Cancel action
|
||||
addAction(R.drawable.ic_close_24dp,
|
||||
addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
context.getString(R.string.action_cancel),
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
|
||||
)
|
||||
}
|
||||
notificationBuilder.show()
|
||||
}
|
||||
@ -92,13 +96,17 @@ internal class UpdaterNotifier(private val context: Context) {
|
||||
setOnlyAlertOnce(false)
|
||||
setProgress(0, 0, false)
|
||||
// Retry action
|
||||
addAction(R.drawable.ic_refresh_24dp,
|
||||
addAction(
|
||||
R.drawable.ic_refresh_24dp,
|
||||
context.getString(R.string.action_retry),
|
||||
UpdaterService.downloadApkPendingService(context, url))
|
||||
UpdaterService.downloadApkPendingService(context, url)
|
||||
)
|
||||
// Cancel action
|
||||
addAction(R.drawable.ic_close_24dp,
|
||||
addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
context.getString(R.string.action_cancel),
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
|
||||
)
|
||||
}
|
||||
notificationBuilder.show(Notifications.ID_UPDATER)
|
||||
}
|
||||
|
@ -40,7 +40,8 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
|
||||
private fun createUpdateNotification(names: List<String>) {
|
||||
NotificationManagerCompat.from(context).apply {
|
||||
notify(Notifications.ID_UPDATES_TO_EXTS,
|
||||
notify(
|
||||
Notifications.ID_UPDATES_TO_EXTS,
|
||||
context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) {
|
||||
setContentTitle(
|
||||
context.resources.getQuantityString(
|
||||
@ -55,7 +56,8 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
setSmallIcon(R.drawable.ic_extension_24dp)
|
||||
setContentIntent(NotificationReceiver.openExtensionsPendingActivity(context))
|
||||
setAutoCancel(true)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +74,8 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
|
||||
12, TimeUnit.HOURS,
|
||||
1, TimeUnit.HOURS)
|
||||
1, TimeUnit.HOURS
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
@ -107,8 +107,10 @@ internal object ExtensionLoader {
|
||||
// Validate lib version
|
||||
val libVersion = versionName.substringBeforeLast('.').toDouble()
|
||||
if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
|
||||
val exception = Exception("Lib version is $libVersion, while only versions " +
|
||||
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed")
|
||||
val exception = Exception(
|
||||
"Lib version is $libVersion, while only versions " +
|
||||
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
|
||||
)
|
||||
Timber.w(exception)
|
||||
return LoadResult.Error(exception)
|
||||
}
|
||||
@ -129,11 +131,12 @@ internal object ExtensionLoader {
|
||||
.split(";")
|
||||
.map {
|
||||
val sourceClass = it.trim()
|
||||
if (sourceClass.startsWith("."))
|
||||
if (sourceClass.startsWith(".")) {
|
||||
pkgInfo.packageName + sourceClass
|
||||
else
|
||||
} else {
|
||||
sourceClass
|
||||
}
|
||||
}
|
||||
.flatMap {
|
||||
try {
|
||||
when (val obj = Class.forName(it, false, classLoader).newInstance()) {
|
||||
|
@ -83,18 +83,20 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||
val state = ((if (filters.isEmpty()) POPULAR_FILTERS else filters)[0] as OrderBy).state
|
||||
when (state?.index) {
|
||||
0 -> {
|
||||
mangaDirs = if (state.ascending)
|
||||
mangaDirs = if (state.ascending) {
|
||||
mangaDirs.sortedBy { it.name.toLowerCase(Locale.ENGLISH) }
|
||||
else
|
||||
} else {
|
||||
mangaDirs.sortedByDescending { it.name.toLowerCase(Locale.ENGLISH) }
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
mangaDirs = if (state.ascending)
|
||||
mangaDirs = if (state.ascending) {
|
||||
mangaDirs.sortedBy(File::lastModified)
|
||||
else
|
||||
} else {
|
||||
mangaDirs.sortedByDescending(File::lastModified)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mangas = mangaDirs.map { mangaDir ->
|
||||
SManga.create().apply {
|
||||
@ -167,10 +169,12 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||
ChapterRecognition.parseChapterNumber(this, manga)
|
||||
}
|
||||
}
|
||||
.sortedWith(Comparator { c1, c2 ->
|
||||
.sortedWith(
|
||||
Comparator { c1, c2 ->
|
||||
val c = c2.chapter_number.compareTo(c1.chapter_number)
|
||||
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
|
||||
})
|
||||
}
|
||||
)
|
||||
.toList()
|
||||
|
||||
return Observable.just(chapters)
|
||||
|
@ -23,26 +23,32 @@ interface SManga : Serializable {
|
||||
var initialized: Boolean
|
||||
|
||||
fun copyFrom(other: SManga) {
|
||||
if (other.author != null)
|
||||
if (other.author != null) {
|
||||
author = other.author
|
||||
}
|
||||
|
||||
if (other.artist != null)
|
||||
if (other.artist != null) {
|
||||
artist = other.artist
|
||||
}
|
||||
|
||||
if (other.description != null)
|
||||
if (other.description != null) {
|
||||
description = other.description
|
||||
}
|
||||
|
||||
if (other.genre != null)
|
||||
if (other.genre != null) {
|
||||
genre = other.genre
|
||||
}
|
||||
|
||||
if (other.thumbnail_url != null)
|
||||
if (other.thumbnail_url != null) {
|
||||
thumbnail_url = other.thumbnail_url
|
||||
}
|
||||
|
||||
status = other.status
|
||||
|
||||
if (!initialized)
|
||||
if (!initialized) {
|
||||
initialized = other.initialized
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val UNKNOWN = 0
|
||||
|
@ -343,10 +343,12 @@ abstract class HttpSource : CatalogueSource {
|
||||
return try {
|
||||
val uri = URI(orig)
|
||||
var out = uri.path
|
||||
if (uri.query != null)
|
||||
if (uri.query != null) {
|
||||
out += "?" + uri.query
|
||||
if (uri.fragment != null)
|
||||
}
|
||||
if (uri.fragment != null) {
|
||||
out += "#" + uri.fragment
|
||||
}
|
||||
out
|
||||
} catch (e: URISyntaxException) {
|
||||
orig
|
||||
|
@ -59,7 +59,8 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(when (preferences.themeMode().get()) {
|
||||
setTheme(
|
||||
when (preferences.themeMode().get()) {
|
||||
Values.THEME_MODE_SYSTEM -> {
|
||||
if (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) {
|
||||
darkTheme
|
||||
@ -69,7 +70,8 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
}
|
||||
Values.THEME_MODE_DARK -> darkTheme
|
||||
else -> lightTheme
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -15,7 +15,8 @@ import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.clearFindViewByIdCache
|
||||
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 {
|
||||
|
||||
lateinit var binding: VB
|
||||
|
@ -87,10 +87,12 @@ abstract class DialogController : RestoreViewOnCreateController {
|
||||
*/
|
||||
fun showDialog(router: Router, tag: String?) {
|
||||
dismissed = false
|
||||
router.pushController(RouterTransaction.with(this)
|
||||
router.pushController(
|
||||
RouterTransaction.with(this)
|
||||
.pushChangeHandler(SimpleSwapChangeHandler(false))
|
||||
.popChangeHandler(SimpleSwapChangeHandler(false))
|
||||
.tag(tag))
|
||||
.tag(tag)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,12 +44,10 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDetach(): Subscription {
|
||||
|
||||
return subscribe().also { untilDetachSubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDetach(onNext: (T) -> Unit): Subscription {
|
||||
|
||||
return subscribe(onNext).also { untilDetachSubscriptions.add(it) }
|
||||
}
|
||||
|
||||
@ -57,7 +55,6 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
|
||||
onNext: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit
|
||||
): Subscription {
|
||||
|
||||
return subscribe(onNext, onError).also { untilDetachSubscriptions.add(it) }
|
||||
}
|
||||
|
||||
@ -66,17 +63,14 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
|
||||
onError: (Throwable) -> Unit,
|
||||
onCompleted: () -> Unit
|
||||
): Subscription {
|
||||
|
||||
return subscribe(onNext, onError, onCompleted).also { untilDetachSubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDestroy(): Subscription {
|
||||
|
||||
return subscribe().also { untilDestroySubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription {
|
||||
|
||||
return subscribe(onNext).also { untilDestroySubscriptions.add(it) }
|
||||
}
|
||||
|
||||
@ -84,7 +78,6 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
|
||||
onNext: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit
|
||||
): Subscription {
|
||||
|
||||
return subscribe(onNext, onError).also { untilDestroySubscriptions.add(it) }
|
||||
}
|
||||
|
||||
@ -93,7 +86,6 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
|
||||
onError: (Throwable) -> Unit,
|
||||
onCompleted: () -> Unit
|
||||
): Subscription {
|
||||
|
||||
return subscribe(onNext, onError, onCompleted).also { untilDestroySubscriptions.add(it) }
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ import reactivecircus.flowbinding.android.view.clicks
|
||||
/**
|
||||
* Controller to manage the categories for the users' library.
|
||||
*/
|
||||
class CategoryController : NucleusController<CategoriesControllerBinding, CategoryPresenter>(),
|
||||
class CategoryController :
|
||||
NucleusController<CategoriesControllerBinding, CategoryPresenter>(),
|
||||
ActionMode.Callback,
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemLongClickListener,
|
||||
@ -176,8 +177,10 @@ class CategoryController : NucleusController<CategoriesControllerBinding, Catego
|
||||
when (item.itemId) {
|
||||
R.id.action_delete -> {
|
||||
undoHelper = UndoHelper(adapter, this)
|
||||
undoHelper?.start(adapter.selectedPositions, view!!,
|
||||
R.string.snack_categories_deleted, R.string.action_undo, 3000)
|
||||
undoHelper?.start(
|
||||
adapter.selectedPositions, view!!,
|
||||
R.string.snack_categories_deleted, R.string.action_undo, 3000
|
||||
)
|
||||
|
||||
mode.finish()
|
||||
}
|
||||
|
@ -49,7 +49,6 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
|
||||
holder.bind(category)
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,8 @@ import rx.android.schedulers.AndroidSchedulers
|
||||
* Controller that shows the currently active downloads.
|
||||
* Uses R.layout.fragment_download_queue.
|
||||
*/
|
||||
class DownloadController : NucleusController<DownloadControllerBinding, DownloadPresenter>(),
|
||||
class DownloadController :
|
||||
NucleusController<DownloadControllerBinding, DownloadPresenter>(),
|
||||
DownloadAdapter.DownloadItemListener {
|
||||
|
||||
/**
|
||||
@ -123,8 +124,9 @@ class DownloadController : NucleusController<DownloadControllerBinding, Download
|
||||
val adapter = adapter ?: return false
|
||||
val items = adapter.currentItems.sortedBy { it.download.chapter.date_upload }
|
||||
.toMutableList()
|
||||
if (item.itemId == R.id.newest)
|
||||
if (item.itemId == R.id.newest) {
|
||||
items.reverse()
|
||||
}
|
||||
adapter.updateDataSet(items)
|
||||
val downloads = items.mapNotNull { it.download }
|
||||
presenter.reorder(downloads)
|
||||
@ -279,10 +281,11 @@ class DownloadController : NucleusController<DownloadControllerBinding, Download
|
||||
val items = adapter?.currentItems?.toMutableList() ?: return
|
||||
val item = items[position]
|
||||
items.remove(item)
|
||||
if (menuItem.itemId == R.id.move_to_top)
|
||||
if (menuItem.itemId == R.id.move_to_top) {
|
||||
items.add(0, item)
|
||||
else
|
||||
} else {
|
||||
items.add(item)
|
||||
}
|
||||
|
||||
val adapter = adapter ?: return
|
||||
adapter.updateDataSet(items)
|
||||
|
@ -80,13 +80,17 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
|
||||
}
|
||||
|
||||
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_bottom).isVisible =
|
||||
bindingAdapterPosition != adapter.itemCount - 1
|
||||
}, {
|
||||
},
|
||||
{
|
||||
adapter.downloadItemListener.onMenuItemClick(bindingAdapterPosition, this)
|
||||
true
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ import uy.kohesive.injekt.api.get
|
||||
/**
|
||||
* Controller to manage the catalogues available in the app.
|
||||
*/
|
||||
open class ExtensionController : NucleusController<ExtensionControllerBinding, ExtensionPresenter>(),
|
||||
open class ExtensionController :
|
||||
NucleusController<ExtensionControllerBinding, ExtensionPresenter>(),
|
||||
ExtensionAdapter.OnButtonClickListener,
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemLongClickListener,
|
||||
@ -92,9 +93,11 @@ open class ExtensionController : NucleusController<ExtensionControllerBinding, E
|
||||
when (item.itemId) {
|
||||
R.id.action_search -> expandActionViewFromInteraction = true
|
||||
R.id.action_settings -> {
|
||||
router.pushController((RouterTransaction.with(ExtensionFilterController()))
|
||||
router.pushController(
|
||||
(RouterTransaction.with(ExtensionFilterController()))
|
||||
.popChangeHandler(SettingsExtensionsFadeChangeHandler())
|
||||
.pushChangeHandler(FadeChangeHandler()))
|
||||
.pushChangeHandler(FadeChangeHandler())
|
||||
)
|
||||
}
|
||||
R.id.action_auto_check -> {
|
||||
item.isChecked = !item.isChecked
|
||||
@ -198,7 +201,8 @@ open class ExtensionController : NucleusController<ExtensionControllerBinding, E
|
||||
adapter?.updateDataSet(
|
||||
extensions.filter {
|
||||
it.extension.name.contains(query, ignoreCase = true)
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
adapter?.updateDataSet(extensions)
|
||||
}
|
||||
|
@ -46,9 +46,11 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||
|
||||
private var preferenceScreen: PreferenceScreen? = null
|
||||
|
||||
constructor(pkgName: String) : this(Bundle().apply {
|
||||
constructor(pkgName: String) : this(
|
||||
Bundle().apply {
|
||||
putString(PKGNAME_KEY, pkgName)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
|
||||
@ -137,7 +139,8 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||
source.preferences
|
||||
} else {*/
|
||||
context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE)
|
||||
/*}*/)
|
||||
/*}*/
|
||||
)
|
||||
|
||||
if (source is ConfigurableSource) {
|
||||
if (multiSource) {
|
||||
@ -178,14 +181,19 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||
}
|
||||
|
||||
val f = when (preference) {
|
||||
is EditTextPreference -> EditTextPreferenceDialogController
|
||||
is EditTextPreference ->
|
||||
EditTextPreferenceDialogController
|
||||
.newInstance(preference.getKey())
|
||||
is ListPreference -> ListPreferenceDialogController
|
||||
is ListPreference ->
|
||||
ListPreferenceDialogController
|
||||
.newInstance(preference.getKey())
|
||||
is MultiSelectListPreference -> MultiSelectListPreferenceDialogController
|
||||
is MultiSelectListPreference ->
|
||||
MultiSelectListPreferenceDialogController
|
||||
.newInstance(preference.getKey())
|
||||
else -> throw IllegalArgumentException("Tried to display dialog for unknown " +
|
||||
"preference type. Did you forget to override onDisplayPreferenceDialog()?")
|
||||
else -> throw IllegalArgumentException(
|
||||
"Tried to display dialog for unknown " +
|
||||
"preference type. Did you forget to override onDisplayPreferenceDialog()?"
|
||||
)
|
||||
}
|
||||
f.targetController = this
|
||||
f.showDialog(router)
|
||||
|
@ -23,7 +23,8 @@ class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecora
|
||||
val child = parent.getChildAt(i)
|
||||
val holder = parent.getChildViewHolder(child)
|
||||
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 top = child.bottom + params.bottomMargin
|
||||
val bottom = top + divider.intrinsicHeight
|
||||
|
@ -38,7 +38,6 @@ data class ExtensionGroupItem(val name: String, val size: Int, val showSize: Boo
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
|
||||
holder.bind(this)
|
||||
}
|
||||
|
||||
|
@ -69,13 +69,15 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
|
||||
|
||||
val installStep = item.installStep
|
||||
if (installStep != null) {
|
||||
setText(when (installStep) {
|
||||
setText(
|
||||
when (installStep) {
|
||||
InstallStep.Pending -> R.string.ext_pending
|
||||
InstallStep.Downloading -> R.string.ext_downloading
|
||||
InstallStep.Installing -> R.string.ext_installing
|
||||
InstallStep.Installed -> R.string.ext_installed
|
||||
InstallStep.Error -> R.string.action_retry
|
||||
})
|
||||
}
|
||||
)
|
||||
if (installStep != InstallStep.Error) {
|
||||
isEnabled = false
|
||||
isClickable = false
|
||||
|
@ -46,7 +46,6 @@ data class ExtensionItem(
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
|
||||
if (payloads == null || payloads.isEmpty()) {
|
||||
holder.bind(this)
|
||||
} else {
|
||||
|
@ -10,10 +10,12 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
||||
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(PKGNAME_KEY, pkgName)
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
targetController = target
|
||||
}
|
||||
|
||||
|
@ -255,9 +255,11 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
||||
controller.createActionModeIfNeeded()
|
||||
when {
|
||||
lastClickPosition == -1 -> setSelection(position)
|
||||
lastClickPosition > position -> for (i in position until lastClickPosition)
|
||||
lastClickPosition > position ->
|
||||
for (i in position until lastClickPosition)
|
||||
setSelection(i)
|
||||
lastClickPosition < position -> for (i in lastClickPosition + 1..position)
|
||||
lastClickPosition < position ->
|
||||
for (i in lastClickPosition + 1..position)
|
||||
setSelection(i)
|
||||
else -> setSelection(position)
|
||||
}
|
||||
|
@ -230,10 +230,11 @@ class LibraryController(
|
||||
}
|
||||
|
||||
// Get the current active category.
|
||||
val activeCat = if (adapter.categories.isNotEmpty())
|
||||
val activeCat = if (adapter.categories.isNotEmpty()) {
|
||||
binding.libraryPager.currentItem
|
||||
else
|
||||
} else {
|
||||
activeCategory
|
||||
}
|
||||
|
||||
// Set the categories
|
||||
adapter.categories = categories
|
||||
@ -260,11 +261,12 @@ class LibraryController(
|
||||
* @return the preference.
|
||||
*/
|
||||
private fun getColumnsPreferenceForCurrentOrientation(): Preference<Int> {
|
||||
return if (resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT)
|
||||
return if (resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
preferences.portraitColumns()
|
||||
else
|
||||
} else {
|
||||
preferences.landscapeColumns()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a filter is changed.
|
||||
@ -510,8 +512,13 @@ class LibraryController(
|
||||
if (manga.favorite) {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.type = "image/*"
|
||||
startActivityForResult(Intent.createChooser(intent,
|
||||
resources?.getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN)
|
||||
startActivityForResult(
|
||||
Intent.createChooser(
|
||||
intent,
|
||||
resources?.getString(R.string.file_select_cover)
|
||||
),
|
||||
REQUEST_IMAGE_OPEN
|
||||
)
|
||||
} else {
|
||||
activity?.toast(R.string.notification_first_add_to_library)
|
||||
}
|
||||
|
@ -27,11 +27,12 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
|
||||
var downloadCount = -1
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return if (libraryAsList.get())
|
||||
return if (libraryAsList.get()) {
|
||||
R.layout.source_list_item
|
||||
else
|
||||
} else {
|
||||
R.layout.source_grid_item
|
||||
}
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder {
|
||||
val parent = adapter.recyclerView
|
||||
@ -40,7 +41,8 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
|
||||
val coverHeight = parent.itemWidth / 3 * 4
|
||||
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
|
||||
gradient.layoutParams = FrameLayout.LayoutParams(
|
||||
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM)
|
||||
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
|
||||
)
|
||||
}
|
||||
LibraryGridHolder(view, adapter)
|
||||
} else {
|
||||
@ -76,15 +78,16 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
|
||||
}
|
||||
|
||||
private fun containsGenre(tag: String, genres: List<String>?): Boolean {
|
||||
return if (tag.startsWith("-"))
|
||||
return if (tag.startsWith("-")) {
|
||||
genres?.find {
|
||||
it.trim().toLowerCase() == tag.substringAfter("-").toLowerCase()
|
||||
} == null
|
||||
else
|
||||
} else {
|
||||
genres?.find {
|
||||
it.trim().toLowerCase() == tag.toLowerCase()
|
||||
} != null
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is LibraryItem) {
|
||||
|
@ -214,10 +214,11 @@ class LibraryPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
val comparator = if (preferences.librarySortingAscending().get())
|
||||
val comparator = if (preferences.librarySortingAscending().get()) {
|
||||
Comparator(sortFn)
|
||||
else
|
||||
} else {
|
||||
Collections.reverseOrder(sortFn)
|
||||
}
|
||||
|
||||
return map.mapValues { entry -> entry.value.sortedWith(comparator) }
|
||||
}
|
||||
@ -229,10 +230,11 @@ class LibraryPresenter(
|
||||
*/
|
||||
private fun getLibraryObservable(): Observable<Library> {
|
||||
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { dbCategories, libraryManga ->
|
||||
val categories = if (libraryManga.containsKey(0))
|
||||
val categories = if (libraryManga.containsKey(0)) {
|
||||
arrayListOf(Category.createDefault()) + dbCategories
|
||||
else
|
||||
} else {
|
||||
dbCategories
|
||||
}
|
||||
|
||||
this.categories = categories
|
||||
Library(categories, libraryManga)
|
||||
|
@ -129,8 +129,11 @@ class LibrarySettingsSheet(
|
||||
|
||||
override fun initModels() {
|
||||
val sorting = preferences.librarySortingMode().get()
|
||||
val order = if (preferences.librarySortingAscending().get())
|
||||
Item.MultiSort.SORT_ASC else Item.MultiSort.SORT_DESC
|
||||
val order = if (preferences.librarySortingAscending().get()) {
|
||||
Item.MultiSort.SORT_ASC
|
||||
} else {
|
||||
Item.MultiSort.SORT_DESC
|
||||
}
|
||||
|
||||
alphabetically.state =
|
||||
if (sorting == LibrarySort.ALPHA) order else Item.MultiSort.SORT_NONE
|
||||
|
@ -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.updates.UpdatesController
|
||||
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.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
|
@ -35,10 +35,12 @@ import uy.kohesive.injekt.api.get
|
||||
|
||||
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)
|
||||
putBoolean(FROM_SOURCE_EXTRA, fromSource)
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
this.manga = manga
|
||||
if (manga != null) {
|
||||
source = Injekt.get<SourceManager>().getOrStub(manga.source)
|
||||
@ -46,7 +48,8 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
|
||||
}
|
||||
|
||||
constructor(mangaId: Long) : this(
|
||||
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking())
|
||||
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking()
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA))
|
||||
@ -87,9 +90,10 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
|
||||
binding.mangaPager.offscreenPageLimit = 3
|
||||
binding.mangaPager.adapter = adapter
|
||||
|
||||
if (!fromSource)
|
||||
if (!fromSource) {
|
||||
binding.mangaPager.currentItem = CHAPTERS_CONTROLLER
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
super.onDestroyView(view)
|
||||
@ -130,9 +134,11 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
|
||||
|
||||
private fun setTrackingIconInternal(visible: Boolean) {
|
||||
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)
|
||||
else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
tab.icon = drawable
|
||||
}
|
||||
@ -144,7 +150,8 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
|
||||
private val tabTitles = listOf(
|
||||
R.string.manga_detail_tab,
|
||||
R.string.manga_chapters_tab,
|
||||
R.string.manga_tracking_tab)
|
||||
R.string.manga_tracking_tab
|
||||
)
|
||||
.map { resources!!.getString(it) }
|
||||
|
||||
override fun getCount(): Int {
|
||||
|
@ -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.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 {
|
||||
|
||||
private var _status: Int = 0
|
||||
|
@ -26,8 +26,11 @@ class ChaptersAdapter(
|
||||
|
||||
val bookmarkedColor = context.getResourceColor(R.attr.colorAccent)
|
||||
|
||||
val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols()
|
||||
.apply { decimalSeparator = '.' })
|
||||
val decimalFormat = DecimalFormat(
|
||||
"#.###",
|
||||
DecimalFormatSymbols()
|
||||
.apply { decimalSeparator = '.' }
|
||||
)
|
||||
|
||||
val dateFormat: DateFormat = preferences.dateFormat().getOrDefault()
|
||||
|
||||
|
@ -39,7 +39,8 @@ import reactivecircus.flowbinding.android.view.clicks
|
||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||
import timber.log.Timber
|
||||
|
||||
class ChaptersController : NucleusController<ChaptersControllerBinding, ChaptersPresenter>(),
|
||||
class ChaptersController :
|
||||
NucleusController<ChaptersControllerBinding, ChaptersPresenter>(),
|
||||
ActionMode.Callback,
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemLongClickListener,
|
||||
@ -168,11 +169,13 @@ class ChaptersController : NucleusController<ChaptersControllerBinding, Chapters
|
||||
menuFilterEmpty.isVisible = filterSet
|
||||
|
||||
// Disable unread filter option if read filter is enabled.
|
||||
if (presenter.onlyRead())
|
||||
if (presenter.onlyRead()) {
|
||||
menuFilterUnread.isEnabled = false
|
||||
}
|
||||
// Disable read filter option if unread filter is enabled.
|
||||
if (presenter.onlyUnread())
|
||||
if (presenter.onlyUnread()) {
|
||||
menuFilterRead.isEnabled = false
|
||||
}
|
||||
|
||||
// Display mode submenu
|
||||
if (presenter.manga.displayMode == Manga.DISPLAY_NAME) {
|
||||
@ -320,9 +323,11 @@ class ChaptersController : NucleusController<ChaptersControllerBinding, Chapters
|
||||
createActionModeIfNeeded()
|
||||
when {
|
||||
lastClickPosition == -1 -> setSelection(position)
|
||||
lastClickPosition > position -> for (i in position until lastClickPosition)
|
||||
lastClickPosition > position ->
|
||||
for (i in position until lastClickPosition)
|
||||
setSelection(i)
|
||||
lastClickPosition < position -> for (i in lastClickPosition + 1..position)
|
||||
lastClickPosition < position ->
|
||||
for (i in lastClickPosition + 1..position)
|
||||
setSelection(i)
|
||||
else -> setSelection(position)
|
||||
}
|
||||
|
@ -71,7 +71,8 @@ class ChaptersPresenter(
|
||||
|
||||
// Add the subscription that retrieves the chapters from the database, keeps subscribed to
|
||||
// changes, and sends the list of chapters to the relay.
|
||||
add(db.getChapters(manga).asRxObservable()
|
||||
add(
|
||||
db.getChapters(manga).asRxObservable()
|
||||
.map { chapters ->
|
||||
// Convert every chapter to a model.
|
||||
chapters.map { it.toModel() }
|
||||
@ -86,7 +87,8 @@ class ChaptersPresenter(
|
||||
// Listen for download status changes
|
||||
observeDownloads()
|
||||
}
|
||||
.subscribe { chaptersRelay.call(it) })
|
||||
.subscribe { chaptersRelay.call(it) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun observeDownloads() {
|
||||
@ -141,9 +143,12 @@ class ChaptersPresenter(
|
||||
.subscribeOn(Schedulers.io())
|
||||
.map { syncChaptersWithSource(db, it, manga, source) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst({ view, _ ->
|
||||
.subscribeFirst(
|
||||
{ view, _ ->
|
||||
view.onFetchChaptersDone()
|
||||
}, ChaptersController::onFetchChaptersError)
|
||||
},
|
||||
ChaptersController::onFetchChaptersError
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,9 +205,10 @@ class ChaptersPresenter(
|
||||
}
|
||||
|
||||
// Force UI update if downloaded filter active and download finished.
|
||||
if (onlyDownloaded() && download.status == Download.DOWNLOADED)
|
||||
if (onlyDownloaded() && download.status == Download.DOWNLOADED) {
|
||||
refreshChapters()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next unread chapter or null if everything is read.
|
||||
@ -263,9 +269,12 @@ class ChaptersPresenter(
|
||||
.doOnNext { if (onlyDownloaded()) refreshChapters() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst({ view, _ ->
|
||||
.subscribeFirst(
|
||||
{ view, _ ->
|
||||
view.onChaptersDeleted(chapters)
|
||||
}, ChaptersController::onChaptersDeletedError)
|
||||
},
|
||||
ChaptersController::onChaptersDeletedError
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,10 +24,12 @@ class DownloadCustomChaptersDialog<T> : DialogController
|
||||
* Initialize dialog.
|
||||
* @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.
|
||||
putInt(KEY_ITEM_MAX, maxChapters)
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
targetController = target
|
||||
this.maxChapters = maxChapters
|
||||
}
|
||||
|
@ -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.updates.UpdatesController
|
||||
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.util.lang.truncateCenter
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
@ -217,12 +217,14 @@ class MangaInfoController(private val fromSource: Boolean = false) :
|
||||
}
|
||||
|
||||
// Update status TextView.
|
||||
binding.mangaStatus.setText(when (manga.status) {
|
||||
binding.mangaStatus.setText(
|
||||
when (manga.status) {
|
||||
SManga.ONGOING -> R.string.ongoing
|
||||
SManga.COMPLETED -> R.string.completed
|
||||
SManga.LICENSED -> R.string.licensed
|
||||
else -> R.string.unknown
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Set the favorite drawable to the correct one.
|
||||
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)
|
||||
|
||||
binding.mangaInfoToggle.text =
|
||||
if (isExpanded)
|
||||
if (isExpanded) {
|
||||
context.getString(R.string.manga_info_expand)
|
||||
else
|
||||
} else {
|
||||
context.getString(R.string.manga_info_collapse)
|
||||
}
|
||||
|
||||
with(binding.mangaSummary) {
|
||||
maxLines =
|
||||
if (isExpanded)
|
||||
if (isExpanded) {
|
||||
3
|
||||
else
|
||||
} else {
|
||||
Int.MAX_VALUE
|
||||
}
|
||||
|
||||
ellipsize =
|
||||
if (isExpanded)
|
||||
if (isExpanded) {
|
||||
TextUtils.TruncateAt.END
|
||||
else
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
binding.mangaGenresTagsCompact.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
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
|
||||
|
||||
activity.toast(view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)),
|
||||
Toast.LENGTH_SHORT)
|
||||
activity.toast(
|
||||
view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,9 +76,12 @@ class MangaInfoPresenter(
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { sendMangaToView() }
|
||||
.subscribeFirst({ view, _ ->
|
||||
.subscribeFirst(
|
||||
{ view, _ ->
|
||||
view.onFetchMangaDone()
|
||||
}, MangaInfoController::onFetchMangaError)
|
||||
},
|
||||
MangaInfoController::onFetchMangaError
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,9 +19,11 @@ class SetTrackChaptersDialog<T> : DialogController
|
||||
|
||||
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)
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
targetController = target
|
||||
this.item = item
|
||||
}
|
||||
|
@ -20,9 +20,11 @@ class SetTrackReadingDatesDialog<T> : DialogController
|
||||
|
||||
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)
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
targetController = target
|
||||
this.item = item
|
||||
this.dateToUpdate = dateToUpdate
|
||||
@ -40,10 +42,12 @@ class SetTrackReadingDatesDialog<T> : DialogController
|
||||
val listener = (targetController as? Listener)
|
||||
|
||||
return MaterialDialog(activity!!)
|
||||
.title(when (dateToUpdate) {
|
||||
.title(
|
||||
when (dateToUpdate) {
|
||||
ReadingDate.Start -> R.string.track_started_reading_date
|
||||
ReadingDate.Finish -> R.string.track_finished_reading_date
|
||||
})
|
||||
}
|
||||
)
|
||||
.datePicker(currentDate = getCurrentDate()) { _, date ->
|
||||
listener?.setReadingDate(item, dateToUpdate, date.timeInMillis)
|
||||
}
|
||||
@ -60,11 +64,12 @@ class SetTrackReadingDatesDialog<T> : DialogController
|
||||
ReadingDate.Start -> it.started_reading_date
|
||||
ReadingDate.Finish -> it.finished_reading_date
|
||||
}
|
||||
if (date != 0L)
|
||||
if (date != 0L) {
|
||||
timeInMillis = date
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun setReadingDate(item: TrackItem, type: ReadingDate, date: Long)
|
||||
|
@ -19,9 +19,11 @@ class SetTrackScoreDialog<T> : DialogController
|
||||
|
||||
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)
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
targetController = target
|
||||
this.item = item
|
||||
}
|
||||
|
@ -17,9 +17,11 @@ class SetTrackStatusDialog<T> : DialogController
|
||||
|
||||
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)
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
targetController = target
|
||||
this.item = item
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||
import timber.log.Timber
|
||||
|
||||
class TrackController : NucleusController<TrackControllerBinding, TrackPresenter>(),
|
||||
class TrackController :
|
||||
NucleusController<TrackControllerBinding, TrackPresenter>(),
|
||||
TrackAdapter.OnClickListener,
|
||||
SetTrackStatusDialog.Listener,
|
||||
SetTrackChaptersDialog.Listener,
|
||||
|
@ -67,8 +67,10 @@ class TrackPresenter(
|
||||
.toList()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst({ view, _ -> view.onRefreshDone() },
|
||||
TrackController::onRefreshError)
|
||||
.subscribeFirst(
|
||||
{ view, _ -> view.onRefreshDone() },
|
||||
TrackController::onRefreshError
|
||||
)
|
||||
}
|
||||
|
||||
fun search(query: String, service: TrackService) {
|
||||
@ -76,19 +78,25 @@ class TrackPresenter(
|
||||
searchSubscription = service.search(query)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeLatestCache(TrackController::onSearchResults,
|
||||
TrackController::onSearchResultsError)
|
||||
.subscribeLatestCache(
|
||||
TrackController::onSearchResults,
|
||||
TrackController::onSearchResultsError
|
||||
)
|
||||
}
|
||||
|
||||
fun registerTracking(item: Track?, service: TrackService) {
|
||||
if (item != null) {
|
||||
item.manga_id = manga.id!!
|
||||
add(service.bind(item)
|
||||
add(
|
||||
service.bind(item)
|
||||
.flatMap { db.insertTrack(item).asRxObservable() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ },
|
||||
{ error -> context.toast(error.message) }))
|
||||
.subscribe(
|
||||
{ },
|
||||
{ error -> context.toast(error.message) }
|
||||
)
|
||||
)
|
||||
} else {
|
||||
unregisterTracking(service)
|
||||
}
|
||||
@ -103,13 +111,15 @@ class TrackPresenter(
|
||||
.flatMap { db.insertTrack(track).asRxObservable() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst({ view, _ -> view.onRefreshDone() },
|
||||
.subscribeFirst(
|
||||
{ view, _ -> view.onRefreshDone() },
|
||||
{ view, error ->
|
||||
view.onRefreshError(error)
|
||||
|
||||
// Restart on error to set old values
|
||||
fetchTrackings()
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun setStatus(item: TrackItem, index: Int) {
|
||||
|
@ -40,9 +40,11 @@ class TrackSearchDialog : DialogController {
|
||||
private val 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)
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
targetController = target
|
||||
this.service = service
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>() {
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
|
||||
holder.bind(this)
|
||||
}
|
||||
|
||||
|
@ -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.withFadeTransaction
|
||||
|
||||
class MigrationController : NucleusController<MigrationControllerBinding, MigrationPresenter>(),
|
||||
class MigrationController :
|
||||
NucleusController<MigrationControllerBinding, MigrationPresenter>(),
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
SourceAdapter.OnSelectClickListener {
|
||||
|
||||
|
@ -34,7 +34,8 @@ class MigrationPresenter(
|
||||
.asRxObservable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) }
|
||||
.combineLatest(stateRelay.map { it.selectedSource }
|
||||
.combineLatest(
|
||||
stateRelay.map { it.selectedSource }
|
||||
.distinctUntilChanged()
|
||||
) { library, source -> library to source }
|
||||
.filter { (_, source) -> source != null }
|
||||
|
@ -8,8 +8,8 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController
|
||||
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchPresenter
|
||||
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
|
||||
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter
|
||||
import eu.kanade.tachiyomi.util.view.gone
|
||||
import eu.kanade.tachiyomi.util.view.visible
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
@ -8,9 +8,9 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchCardItem
|
||||
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchItem
|
||||
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchPresenter
|
||||
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchCardItem
|
||||
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchItem
|
||||
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
|
@ -40,7 +40,6 @@ data class SourceItem(val source: Source, val header: SelectionHeader? = null) :
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
|
||||
holder.bind(this)
|
||||
}
|
||||
}
|
||||
|
@ -50,11 +50,12 @@ class AboutController : SettingsController() {
|
||||
|
||||
preference {
|
||||
titleRes = R.string.version
|
||||
summary = if (BuildConfig.DEBUG)
|
||||
summary = if (BuildConfig.DEBUG) {
|
||||
"Preview r${BuildConfig.COMMIT_COUNT} (${BuildConfig.COMMIT_SHA})"
|
||||
else
|
||||
} else {
|
||||
"Stable ${BuildConfig.VERSION_NAME}"
|
||||
}
|
||||
}
|
||||
preference {
|
||||
titleRes = R.string.build_time
|
||||
summary = getFormattedBuildTime()
|
||||
@ -157,10 +158,12 @@ class AboutController : SettingsController() {
|
||||
|
||||
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(URL_KEY, url)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
return MaterialDialog(activity!!)
|
||||
@ -190,7 +193,8 @@ class AboutController : SettingsController() {
|
||||
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
|
||||
|
||||
val outputDf = DateFormat.getDateTimeInstance(
|
||||
DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault())
|
||||
DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault()
|
||||
)
|
||||
outputDf.timeZone = TimeZone.getDefault()
|
||||
|
||||
buildTime.toDateTimestampString(dateFormat)
|
||||
|
@ -26,7 +26,8 @@ class ChapterLoadByNumber {
|
||||
// If there is only one chapter for this number, use it
|
||||
chaptersForNumber.size == 1 -> chaptersForNumber.first()
|
||||
// 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()
|
||||
}
|
||||
chapters.add(preferredChapter)
|
||||
|
@ -127,10 +127,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
* Called when the activity is created. Initializes the presenter and configuration.
|
||||
*/
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(when (preferences.readerTheme().get()) {
|
||||
setTheme(
|
||||
when (preferences.readerTheme().get()) {
|
||||
0 -> R.style.Theme_Reader_Light
|
||||
else -> R.style.Theme_Reader
|
||||
})
|
||||
}
|
||||
)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ReaderActivityBinding.inflate(layoutInflater)
|
||||
@ -278,7 +280,8 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
insets.systemWindowInsetLeft,
|
||||
insets.systemWindowInsetTop,
|
||||
insets.systemWindowInsetRight,
|
||||
insets.systemWindowInsetBottom)
|
||||
insets.systemWindowInsetBottom
|
||||
)
|
||||
}
|
||||
insets
|
||||
}
|
||||
@ -293,20 +296,22 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
})
|
||||
binding.leftChapter.setOnClickListener {
|
||||
if (viewer != null) {
|
||||
if (viewer is R2LPagerViewer)
|
||||
if (viewer is R2LPagerViewer) {
|
||||
loadNextChapter()
|
||||
else
|
||||
} else {
|
||||
loadPreviousChapter()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.rightChapter.setOnClickListener {
|
||||
if (viewer != null) {
|
||||
if (viewer is R2LPagerViewer)
|
||||
if (viewer is R2LPagerViewer) {
|
||||
loadPreviousChapter()
|
||||
else
|
||||
} else {
|
||||
loadNextChapter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial visibility
|
||||
setMenuVisibility(menuVisible)
|
||||
@ -590,11 +595,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
* depending on the [result].
|
||||
*/
|
||||
fun onSetAsCoverResult(result: ReaderPresenter.SetAsCoverResult) {
|
||||
toast(when (result) {
|
||||
toast(
|
||||
when (result) {
|
||||
Success -> R.string.cover_updated
|
||||
AddToLibraryFirst -> R.string.notification_first_add_to_library
|
||||
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].
|
||||
*/
|
||||
private fun setTrueColor(enabled: Boolean) {
|
||||
if (enabled)
|
||||
if (enabled) {
|
||||
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.ARGB_8888)
|
||||
else
|
||||
} else {
|
||||
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.RGB_565)
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
private fun setCutoutShort(enabled: Boolean) {
|
||||
|
@ -214,9 +214,10 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet
|
||||
brightness_overlay.gone()
|
||||
}
|
||||
|
||||
if (!isDisabled)
|
||||
if (!isDisabled) {
|
||||
txt_brightness_seekbar_value.text = value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the color filter value subscription
|
||||
|
@ -17,14 +17,16 @@ class ReaderColorFilterView(
|
||||
|
||||
fun setFilterColor(color: Int, filterMode: Int) {
|
||||
colorFilterPaint.color = color
|
||||
colorFilterPaint.xfermode = PorterDuffXfermode(when (filterMode) {
|
||||
colorFilterPaint.xfermode = PorterDuffXfermode(
|
||||
when (filterMode) {
|
||||
1 -> PorterDuff.Mode.MULTIPLY
|
||||
2 -> PorterDuff.Mode.SCREEN
|
||||
3 -> PorterDuff.Mode.OVERLAY
|
||||
4 -> PorterDuff.Mode.LIGHTEN
|
||||
5 -> PorterDuff.Mode.DARKEN
|
||||
else -> PorterDuff.Mode.SRC_OVER
|
||||
})
|
||||
}
|
||||
)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
|
@ -100,8 +100,10 @@ class ReaderPresenter(
|
||||
if (
|
||||
(manga.readFilter == Manga.SHOW_READ && !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)
|
||||
) {
|
||||
return@filter false
|
||||
@ -201,9 +203,12 @@ class ReaderPresenter(
|
||||
.first()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { init(it, initialChapterId) }
|
||||
.subscribeFirst({ _, _ ->
|
||||
.subscribeFirst(
|
||||
{ _, _ ->
|
||||
// Ignore onNext event
|
||||
}, ReaderActivity::setInitialChapterError)
|
||||
},
|
||||
ReaderActivity::setInitialChapterError
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -245,9 +250,12 @@ class ReaderPresenter(
|
||||
.flatMap { getLoadObservable(loader!!, it) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst({ _, _ ->
|
||||
.subscribeFirst(
|
||||
{ _, _ ->
|
||||
// Ignore onNext event
|
||||
}, ReaderActivity::setInitialChapterError)
|
||||
},
|
||||
ReaderActivity::setInitialChapterError
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,13 +270,17 @@ class ReaderPresenter(
|
||||
chapter: ReaderChapter
|
||||
): Observable<ViewerChapters> {
|
||||
return loader.loadChapter(chapter)
|
||||
.andThen(Observable.fromCallable {
|
||||
.andThen(
|
||||
Observable.fromCallable {
|
||||
val chapterPos = chapterList.indexOf(chapter)
|
||||
|
||||
ViewerChapters(chapter,
|
||||
ViewerChapters(
|
||||
chapter,
|
||||
chapterList.getOrNull(chapterPos - 1),
|
||||
chapterList.getOrNull(chapterPos + 1))
|
||||
})
|
||||
chapterList.getOrNull(chapterPos + 1)
|
||||
)
|
||||
}
|
||||
)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { newChapters ->
|
||||
val oldChapters = viewerChaptersRelay.value
|
||||
@ -312,11 +324,14 @@ class ReaderPresenter(
|
||||
activeChapterSubscription = getLoadObservable(loader, chapter)
|
||||
.doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) }
|
||||
.doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) }
|
||||
.subscribeFirst({ view, _ ->
|
||||
.subscribeFirst(
|
||||
{ view, _ ->
|
||||
view.moveToPageIndex(0)
|
||||
}, { _, _ ->
|
||||
},
|
||||
{ _, _ ->
|
||||
// Ignore onError event, viewers handle that state
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -509,9 +524,11 @@ class ReaderPresenter(
|
||||
notifier.onClear()
|
||||
|
||||
// Pictures directory.
|
||||
val destDir = File(Environment.getExternalStorageDirectory().absolutePath +
|
||||
val destDir = File(
|
||||
Environment.getExternalStorageDirectory().absolutePath +
|
||||
File.separator + Environment.DIRECTORY_PICTURES +
|
||||
File.separator + "Tachiyomi")
|
||||
File.separator + "Tachiyomi"
|
||||
)
|
||||
|
||||
// Copy file in background.
|
||||
Observable.fromCallable { saveImage(page, destDir, manga) }
|
||||
@ -614,7 +631,8 @@ class ReaderPresenter(
|
||||
|
||||
db.getTracks(manga).asRxSingle()
|
||||
.flatMapCompletable { trackList ->
|
||||
Completable.concat(trackList.map { track ->
|
||||
Completable.concat(
|
||||
trackList.map { track ->
|
||||
val service = trackManager.getService(track.sync_id)
|
||||
if (service != null && service.isLogged && chapterRead > track.last_chapter_read) {
|
||||
track.last_chapter_read = chapterRead
|
||||
@ -628,7 +646,8 @@ class ReaderPresenter(
|
||||
} else {
|
||||
Completable.complete()
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
@ -66,13 +66,17 @@ class SaveImageNotifier(private val context: Context) {
|
||||
|
||||
setContentIntent(NotificationHandler.openImagePendingActivity(context, file))
|
||||
// Share action
|
||||
addAction(R.drawable.ic_share_24dp,
|
||||
addAction(
|
||||
R.drawable.ic_share_24dp,
|
||||
context.getString(R.string.action_share),
|
||||
NotificationReceiver.shareImagePendingBroadcast(context, file.absolutePath, notificationId))
|
||||
NotificationReceiver.shareImagePendingBroadcast(context, file.absolutePath, notificationId)
|
||||
)
|
||||
// Delete action
|
||||
addAction(R.drawable.ic_delete_24dp,
|
||||
addAction(
|
||||
R.drawable.ic_delete_24dp,
|
||||
context.getString(R.string.action_delete),
|
||||
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId))
|
||||
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId)
|
||||
)
|
||||
|
||||
updateNotification()
|
||||
}
|
||||
|
@ -44,10 +44,12 @@ class EpubPageLoader(file: File) : PageLoader() {
|
||||
* Returns an observable that emits a ready state unless the loader was recycled.
|
||||
*/
|
||||
override fun getPage(page: ReaderPage): Observable<Int> {
|
||||
return Observable.just(if (isRecycled) {
|
||||
return Observable.just(
|
||||
if (isRecycled) {
|
||||
Page.ERROR
|
||||
} else {
|
||||
Page.READY
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -46,12 +46,15 @@ class HttpPageLoader(
|
||||
.concatMap { source.fetchImageFromCacheThenNet(it) }
|
||||
.repeat()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe({
|
||||
}, { error ->
|
||||
.subscribe(
|
||||
{
|
||||
},
|
||||
{ error ->
|
||||
if (error !is InterruptedException) {
|
||||
Timber.e(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,11 +189,12 @@ class HttpPageLoader(
|
||||
* @param page the page whose source image has to be downloaded.
|
||||
*/
|
||||
private fun HttpSource.fetchImageFromCacheThenNet(page: ReaderPage): Observable<ReaderPage> {
|
||||
return if (page.imageUrl.isNullOrEmpty())
|
||||
return if (page.imageUrl.isNullOrEmpty()) {
|
||||
getImageUrl(page).flatMap { getCachedImage(it) }
|
||||
else
|
||||
} else {
|
||||
getCachedImage(page)
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpSource.getImageUrl(page: ReaderPage): Observable<ReaderPage> {
|
||||
page.status = Page.LOAD_PAGE
|
||||
|
@ -60,11 +60,13 @@ class RarPageLoader(file: File) : PageLoader() {
|
||||
* Returns an observable that emits a ready state unless the loader was recycled.
|
||||
*/
|
||||
override fun getPage(page: ReaderPage): Observable<Int> {
|
||||
return Observable.just(if (isRecycled) {
|
||||
return Observable.just(
|
||||
if (isRecycled) {
|
||||
Page.ERROR
|
||||
} else {
|
||||
Page.READY
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,10 +49,12 @@ class ZipPageLoader(file: File) : PageLoader() {
|
||||
* Returns an observable that emits a ready state unless the loader was recycled.
|
||||
*/
|
||||
override fun getPage(page: ReaderPage): Observable<Int> {
|
||||
return Observable.just(if (isRecycled) {
|
||||
return Observable.just(
|
||||
if (isRecycled) {
|
||||
Page.ERROR
|
||||
} else {
|
||||
Page.READY
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,8 @@ class ReaderProgressBar @JvmOverloads constructor(
|
||||
* The rotation animation to use while the progress bar is visible.
|
||||
*/
|
||||
private val rotationAnimation by lazy {
|
||||
RotateAnimation(0f, 360f,
|
||||
RotateAnimation(
|
||||
0f, 360f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f
|
||||
).apply {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user