Update to Kotlin 1.4.10, coroutines 1.3.9, Kotlinter 3.0.2 (#594)

This commit is contained in:
arkon 2020-09-16 00:31:40 -04:00 committed by GitHub
parent f10f8c6b64
commit 9f15f25f43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
267 changed files with 4025 additions and 2848 deletions

View File

@ -75,7 +75,8 @@ class BackupCreateService : Service() {
startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build()) startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock" PowerManager.PARTIAL_WAKE_LOCK,
"${javaClass.name}:WakeLock"
) )
wakeLock.acquire() wakeLock.acquire()
} }

View File

@ -37,8 +37,11 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
val interval = prefInterval ?: preferences.backupInterval().getOrDefault() val interval = prefInterval ?: preferences.backupInterval().getOrDefault()
if (interval > 0) { if (interval > 0) {
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>( val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
interval.toLong(), TimeUnit.HOURS, interval.toLong(),
10, TimeUnit.MINUTES) TimeUnit.HOURS,
10,
TimeUnit.MINUTES
)
.addTag(TAG) .addTag(TAG)
.build() .build()

View File

@ -103,7 +103,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
private fun initParser(): Gson = when (version) { private fun initParser(): Gson = when (version) {
1 -> GsonBuilder().create() 1 -> GsonBuilder().create()
2 -> GsonBuilder() 2 ->
GsonBuilder()
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build()) .registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build()) .registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build()) .registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())

View File

@ -121,7 +121,9 @@ class BackupRestoreService : Service() {
super.onCreate() super.onCreate()
startForeground(Notifications.ID_RESTORE_PROGRESS, progressNotification.build()) startForeground(Notifications.ID_RESTORE_PROGRESS, progressNotification.build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock") PowerManager.PARTIAL_WAKE_LOCK,
"BackupRestoreService:WakeLock"
)
wakeLock.acquire(TimeUnit.HOURS.toMillis(3)) wakeLock.acquire(TimeUnit.HOURS.toMillis(3))
} }
@ -382,12 +384,20 @@ class BackupRestoreService : Service() {
* @param total the total progress. * @param total the total progress.
*/ */
private fun showProgressNotification(current: Int, total: Int, title: String) { private fun showProgressNotification(current: Int, total: Int, title: String) {
notificationManager.notify(Notifications.ID_RESTORE_PROGRESS, progressNotification notificationManager.notify(
Notifications.ID_RESTORE_PROGRESS,
progressNotification
.setContentTitle(title.chop(30)) .setContentTitle(title.chop(30))
.setContentText(getString(R.string.restoring_progress, restoreProgress, .setContentText(
totalAmount)) getString(
R.string.restoring_progress,
restoreProgress,
totalAmount
)
)
.setProgress(total, current, false) .setProgress(total, current, false)
.build()) .build()
)
} }
/** /**
@ -395,8 +405,14 @@ class BackupRestoreService : Service() {
*/ */
private fun showResultNotification(path: String?, file: String?) { private fun showResultNotification(path: String?, file: String?) {
val content = mutableListOf(getString(R.string.restore_completed_content, restoreProgress val content = mutableListOf(
.toString(), errors.size.toString())) getString(
R.string.restore_completed_content,
restoreProgress
.toString(),
errors.size.toString()
)
)
val sourceMissingCount = sourcesMissing.distinct().size val sourceMissingCount = sourcesMissing.distinct().size
if (sourceMissingCount > 0) { if (sourceMissingCount > 0) {
val sources = sourcesMissing.distinct().filter { it.toLongOrNull() == null } val sources = sourcesMissing.distinct().filter { it.toLongOrNull() == null }
@ -408,20 +424,29 @@ class BackupRestoreService : Service() {
if (sources.isEmpty()) { if (sources.isEmpty()) {
content.add( content.add(
resources.getQuantityString( resources.getQuantityString(
R.plurals.sources_missing, sourceMissingCount, sourceMissingCount R.plurals.sources_missing,
sourceMissingCount,
sourceMissingCount
) )
) )
} else { } else {
content.add( content.add(
resources.getQuantityString( resources.getQuantityString(
R.plurals.sources_missing, sourceMissingCount, sourceMissingCount R.plurals.sources_missing,
sourceMissingCount,
sourceMissingCount
) + ": " + missingSourcesString ) + ": " + missingSourcesString
) )
} }
} }
if (lincensedManga > 0) if (lincensedManga > 0)
content.add(resources.getQuantityString(R.plurals.licensed_manga, lincensedManga, content.add(
lincensedManga)) resources.getQuantityString(
R.plurals.licensed_manga,
lincensedManga,
lincensedManga
)
)
val trackingErrors = trackingErrors.distinct() val trackingErrors = trackingErrors.distinct()
if (trackingErrors.isNotEmpty()) { if (trackingErrors.isNotEmpty()) {
val trackingErrorsString = trackingErrors.distinct().joinToString("\n") val trackingErrorsString = trackingErrors.distinct().joinToString("\n")
@ -440,8 +465,14 @@ class BackupRestoreService : Service() {
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(ContextCompat.getColor(this, R.color.colorAccent)) .setColor(ContextCompat.getColor(this, R.color.colorAccent))
if (errors.size > 0 && !path.isNullOrEmpty() && !file.isNullOrEmpty()) { if (errors.size > 0 && !path.isNullOrEmpty() && !file.isNullOrEmpty()) {
resultNotification.addAction(R.drawable.ic_close_24dp, getString(R.string resultNotification.addAction(
.view_all_errors), getErrorLogIntent(path, file)) R.drawable.ic_close_24dp,
getString(
R.string
.view_all_errors
),
getErrorLogIntent(path, file)
)
} }
notificationManager.notify(Notifications.ID_RESTORE_COMPLETE, resultNotification.build()) notificationManager.notify(Notifications.ID_RESTORE_COMPLETE, resultNotification.build())
} }

View File

@ -46,10 +46,12 @@ class ChapterCache(private val context: Context) {
private val gson: Gson by injectLazy() private val gson: Gson by injectLazy()
/** Cache class used for cache management. */ /** Cache class used for cache management. */
private val diskCache = DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY), private val diskCache = DiskLruCache.open(
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
PARAMETER_APP_VERSION, PARAMETER_APP_VERSION,
PARAMETER_VALUE_COUNT, PARAMETER_VALUE_COUNT,
PARAMETER_CACHE_SIZE) PARAMETER_CACHE_SIZE
)
/** /**
* Returns directory of cache. * Returns directory of cache.

View File

@ -83,7 +83,8 @@ class CoverCache(val context: Context) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
context.toast( context.toast(
context.getString( context.getString(
R.string.deleted_, Formatter.formatFileSize(context, deletedSize) R.string.deleted_,
Formatter.formatFileSize(context, deletedSize)
) )
) )
} }
@ -111,7 +112,8 @@ class CoverCache(val context: Context) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
context.toast( context.toast(
context.getString( context.getString(
R.string.deleted_, Formatter.formatFileSize(context, deletedSize) R.string.deleted_,
Formatter.formatFileSize(context, deletedSize)
) )
) )
} }

View File

@ -30,8 +30,13 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
* This class provides operations to manage the database through its interfaces. * This class provides operations to manage the database through its interfaces.
*/ */
open class DatabaseHelper(context: Context) : open class DatabaseHelper(context: Context) :
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, MangaQueries,
HistoryQueries, SearchMetadataQueries { ChapterQueries,
TrackQueries,
CategoryQueries,
MangaCategoryQueries,
HistoryQueries,
SearchMetadataQueries {
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context) private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
.name(DbOpenCallback.DATABASE_NAME) .name(DbOpenCallback.DATABASE_NAME)

View File

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

View File

@ -73,13 +73,14 @@ interface Manga : SManga {
genre?.split(",")?.map { it.trim().toLowerCase(Locale.US) } ?: emptyList() genre?.split(",")?.map { it.trim().toLowerCase(Locale.US) } ?: emptyList()
return if (currentTags.any { tag -> tag.startsWith("japanese") || isMangaTag(tag) }) { return if (currentTags.any { tag -> tag.startsWith("japanese") || isMangaTag(tag) }) {
TYPE_MANGA TYPE_MANGA
} else if (currentTags.any { tag -> tag.startsWith("english") || isComicTag(tag) } || isComicSource( } else if (currentTags.any { tag -> tag.startsWith("english") || isComicTag(tag) } ||
sourceName isComicSource(sourceName)
)) { ) {
TYPE_COMIC TYPE_COMIC
} else if (currentTags.any { tag -> } else if (currentTags.any { tag ->
tag.startsWith("chinese") || isManhuaTag(tag) tag.startsWith("chinese") || isManhuaTag(tag)
} || sourceName.contains("manhua", true)) { } || sourceName.contains("manhua", true)
) {
TYPE_MANHUA TYPE_MANHUA
} else if (currentTags.any { tag -> isManhwaTag(tag) } || isWebtoonSource(sourceName)) { } else if (currentTags.any { tag -> isManhwaTag(tag) } || isWebtoonSource(sourceName)) {
TYPE_MANHWA TYPE_MANHWA

View File

@ -74,7 +74,8 @@ open class MangaImpl : Manga {
override fun copyFrom(other: SManga) { override fun copyFrom(other: SManga) {
if (other is MangaImpl && other::ogTitle.isInitialized && if (other is MangaImpl && other::ogTitle.isInitialized &&
!other.title.isBlank() && other.ogTitle != ogTitle) { !other.title.isBlank() && other.ogTitle != ogTitle
) {
val oldTitle = ogTitle val oldTitle = ogTitle
title = other.ogTitle title = other.ogTitle
val db: DownloadManager by injectLazy() val db: DownloadManager by injectLazy()

View File

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

View File

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

View File

@ -27,11 +27,13 @@ interface HistoryQueries : DbProvider {
*/ */
fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get() fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getRecentMangasQuery(offset, search.sqLite)) .query(getRecentMangasQuery(offset, search.sqLite))
.args(date.time) .args(date.time)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build()) .build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare() .prepare()
@ -42,11 +44,13 @@ interface HistoryQueries : DbProvider {
*/ */
fun getRecentlyAdded(date: Date, search: String = "", endless: Boolean) = db.get() fun getRecentlyAdded(date: Date, search: String = "", endless: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getRecentAdditionsQuery(search.sqLite, endless)) .query(getRecentAdditionsQuery(search.sqLite, endless))
.args(date.time) .args(date.time)
.observesTables(MangaTable.TABLE) .observesTables(MangaTable.TABLE)
.build()) .build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare() .prepare()
@ -57,11 +61,13 @@ interface HistoryQueries : DbProvider {
*/ */
fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get() fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getRecentMangasLimitQuery(limit, search.sqLite)) .query(getRecentMangasLimitQuery(limit, search.sqLite))
.args(date.time) .args(date.time)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build()) .build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare() .prepare()
@ -72,30 +78,36 @@ interface HistoryQueries : DbProvider {
*/ */
fun getRecentsWithUnread(date: Date, search: String = "", endless: Boolean) = db.get() fun getRecentsWithUnread(date: Date, search: String = "", endless: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getRecentReadWithUnreadChapters(search.sqLite, endless)) .query(getRecentReadWithUnreadChapters(search.sqLite, endless))
.args(date.time) .args(date.time)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build()) .build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare() .prepare()
fun getHistoryByMangaId(mangaId: Long) = db.get() fun getHistoryByMangaId(mangaId: Long) = db.get()
.listOfObjects(History::class.java) .listOfObjects(History::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getHistoryByMangaId()) .query(getHistoryByMangaId())
.args(mangaId) .args(mangaId)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
fun getHistoryByChapterUrl(chapterUrl: String) = db.get() fun getHistoryByChapterUrl(chapterUrl: String) = db.get()
.`object`(History::class.java) .`object`(History::class.java)
.withQuery(RawQuery.builder() .withQuery(
RawQuery.builder()
.query(getHistoryByChapterUrl()) .query(getHistoryByChapterUrl())
.args(chapterUrl) .args(chapterUrl)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
/** /**
@ -119,16 +131,20 @@ interface HistoryQueries : DbProvider {
.prepare() .prepare()
fun deleteHistory() = db.delete() fun deleteHistory() = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
DeleteQuery.builder()
.table(HistoryTable.TABLE) .table(HistoryTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
fun deleteHistoryNoLastRead() = db.delete() fun deleteHistoryNoLastRead() = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
DeleteQuery.builder()
.table(HistoryTable.TABLE) .table(HistoryTable.TABLE)
.where("${HistoryTable.COL_LAST_READ} = ?") .where("${HistoryTable.COL_LAST_READ} = ?")
.whereArgs(0) .whereArgs(0)
.build()) .build()
)
.prepare() .prepare()
} }

View File

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

View File

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

View File

@ -9,7 +9,8 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
/** /**
* Query to get the manga from the library, with their categories and unread count. * Query to get the manga from the library, with their categories and unread count.
*/ */
val libraryQuery = """ val libraryQuery =
"""
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY} SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
FROM ( FROM (
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, COALESCE(R.hasread, 0) AS ${Manga.COL_HAS_READ} SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, COALESCE(R.hasread, 0) AS ${Manga.COL_HAS_READ}
@ -40,7 +41,8 @@ val libraryQuery = """
/** /**
* Query to get the recent chapters of manga from the library up to a date. * Query to get the recent chapters of manga from the library up to a date.
*/ */
fun getRecentsQuery() = """ fun getRecentsQuery() =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE} SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1 WHERE ${Manga.COL_FAVORITE} = 1
@ -52,7 +54,8 @@ fun getRecentsQuery() = """
/** /**
* Query to get the recently added manga * Query to get the recently added manga
*/ */
fun getRecentAdditionsQuery(search: String, endless: Boolean) = """ fun getRecentAdditionsQuery(search: String, endless: Boolean) =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE}
WHERE ${Manga.COL_FAVORITE} = 1 WHERE ${Manga.COL_FAVORITE} = 1
AND ${Manga.COL_DATE_ADDED} > ? AND ${Manga.COL_DATE_ADDED} > ?
@ -64,7 +67,8 @@ fun getRecentAdditionsQuery(search: String, endless: Boolean) = """
/** /**
* Query to get the manga with recently uploaded chapters * Query to get the manga with recently uploaded chapters
*/ */
fun getRecentsQueryDistinct(search: String, endless: Boolean) = """ fun getRecentsQueryDistinct(search: String, endless: Boolean) =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.* SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -92,7 +96,8 @@ fun getRecentsQueryDistinct(search: String, endless: Boolean) = """
* and are read after the given time period * and are read after the given time period
* @return return limit is 25 * @return return limit is 25
*/ */
fun getRecentMangasQuery(offset: Int = 0, search: String = "") = """ fun getRecentMangasQuery(offset: Int = 0, search: String = "") =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -118,7 +123,8 @@ fun getRecentMangasQuery(offset: Int = 0, search: String = "") = """
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read * The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period * and are read after the given time period
*/ */
fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") = """ fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -145,7 +151,8 @@ fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") = """
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read * The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period * and are read after the given time period
*/ */
fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) = """ fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ( FROM (
SELECT ${Manga.TABLE}.* SELECT ${Manga.TABLE}.*
@ -178,7 +185,8 @@ fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) = """
${if (endless) "" else "LIMIT 8"} ${if (endless) "" else "LIMIT 8"}
""" """
fun getHistoryByMangaId() = """ fun getHistoryByMangaId() =
"""
SELECT ${History.TABLE}.* SELECT ${History.TABLE}.*
FROM ${History.TABLE} FROM ${History.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -186,7 +194,8 @@ fun getHistoryByMangaId() = """
WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID} WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
""" """
fun getHistoryByChapterUrl() = """ fun getHistoryByChapterUrl() =
"""
SELECT ${History.TABLE}.* SELECT ${History.TABLE}.*
FROM ${History.TABLE} FROM ${History.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -194,7 +203,8 @@ fun getHistoryByChapterUrl() = """
WHERE ${Chapter.TABLE}.${Chapter.COL_URL} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID} WHERE ${Chapter.TABLE}.${Chapter.COL_URL} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
""" """
fun getLastReadMangaQuery() = """ fun getLastReadMangaQuery() =
"""
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -206,7 +216,8 @@ fun getLastReadMangaQuery() = """
ORDER BY max DESC ORDER BY max DESC
""" """
fun getTotalChapterMangaQuery() = """ fun getTotalChapterMangaQuery() =
"""
SELECT ${Manga.TABLE}.* SELECT ${Manga.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -218,7 +229,8 @@ fun getTotalChapterMangaQuery() = """
/** /**
* Query to get the categories for a manga. * Query to get the categories for a manga.
*/ */
fun getCategoriesForMangaQuery() = """ fun getCategoriesForMangaQuery() =
"""
SELECT ${Category.TABLE}.* FROM ${Category.TABLE} SELECT ${Category.TABLE}.* FROM ${Category.TABLE}
JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} = JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} =
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID} ${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}

View File

@ -10,35 +10,43 @@ interface SearchMetadataQueries : DbProvider {
fun getSearchMetadataForManga(mangaId: Long) = db.get() fun getSearchMetadataForManga(mangaId: Long) = db.get()
.`object`(SearchMetadata::class.java) .`object`(SearchMetadata::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(SearchMetadataTable.TABLE) .table(SearchMetadataTable.TABLE)
.where("${SearchMetadataTable.COL_MANGA_ID} = ?") .where("${SearchMetadataTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId) .whereArgs(mangaId)
.build()) .build()
)
.prepare() .prepare()
fun getSearchMetadata() = db.get() fun getSearchMetadata() = db.get()
.listOfObjects(SearchMetadata::class.java) .listOfObjects(SearchMetadata::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(SearchMetadataTable.TABLE) .table(SearchMetadataTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
fun getSearchMetadataByIndexedExtra(extra: String) = db.get() fun getSearchMetadataByIndexedExtra(extra: String) = db.get()
.listOfObjects(SearchMetadata::class.java) .listOfObjects(SearchMetadata::class.java)
.withQuery(Query.builder() .withQuery(
Query.builder()
.table(SearchMetadataTable.TABLE) .table(SearchMetadataTable.TABLE)
.where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?") .where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?")
.whereArgs(extra) .whereArgs(extra)
.build()) .build()
)
.prepare() .prepare()
fun insertSearchMetadata(metadata: SearchMetadata) = db.put().`object`(metadata).prepare() fun insertSearchMetadata(metadata: SearchMetadata) = db.put().`object`(metadata).prepare()
fun deleteSearchMetadata(metadata: SearchMetadata) = db.delete().`object`(metadata).prepare() fun deleteSearchMetadata(metadata: SearchMetadata) = db.delete().`object`(metadata).prepare()
fun deleteAllSearchMetadata() = db.delete().byQuery(DeleteQuery.builder() fun deleteAllSearchMetadata() = db.delete().byQuery(
DeleteQuery.builder()
.table(SearchMetadataTable.TABLE) .table(SearchMetadataTable.TABLE)
.build()) .build()
)
.prepare() .prepare()
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,8 @@ object SearchMetadataTable {
// Insane foreign, primary key to avoid touch manga table // Insane foreign, primary key to avoid touch manga table
val createTableQuery: String val createTableQuery: String
get() = """CREATE TABLE $TABLE( get() =
"""CREATE TABLE $TABLE(
$COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY, $COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY,
$COL_UPLOADER TEXT, $COL_UPLOADER TEXT,
$COL_EXTRA TEXT NOT NULL, $COL_EXTRA TEXT NOT NULL,

View File

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

View File

@ -251,7 +251,9 @@ class DownloadManager(val context: Context) {
queue.remove(chapters) queue.remove(chapters)
val chapterDirs = val chapterDirs =
provider.findChapterDirs(chapters, manga, source) + provider.findTempChapterDirs( provider.findChapterDirs(chapters, manga, source) + provider.findTempChapterDirs(
chapters, manga, source chapters,
manga,
source
) )
chapterDirs.forEach { it.delete() } chapterDirs.forEach { it.delete() }
cache.removeChapters(chapters, manga) cache.removeChapters(chapters, manga)

View File

@ -78,16 +78,21 @@ internal class DownloadNotifier(private val context: Context) {
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
isDownloading = true isDownloading = true
// Pause action // Pause action
addAction(R.drawable.ic_pause_24dp, addAction(
R.drawable.ic_pause_24dp,
context.getString(R.string.pause), context.getString(R.string.pause),
NotificationReceiver.pauseDownloadsPendingBroadcast(context)) NotificationReceiver.pauseDownloadsPendingBroadcast(context)
)
} }
if (download != null) { if (download != null) {
val title = download.manga.title.chop(15) val title = download.manga.title.chop(15)
val quotedTitle = Pattern.quote(title) val quotedTitle = Pattern.quote(title)
val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*" val chapter = download.chapter.name.replaceFirst(
.toRegex(RegexOption.IGNORE_CASE), "") "$quotedTitle[\\s]*[-]*[\\s]*"
.toRegex(RegexOption.IGNORE_CASE),
""
)
setContentTitle("$title - $chapter".chop(30)) setContentTitle("$title - $chapter".chop(30))
setContentText( setContentText(
context.getString(R.string.downloading) context.getString(R.string.downloading)
@ -124,17 +129,21 @@ internal class DownloadNotifier(private val context: Context) {
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
isDownloading = true isDownloading = true
// Pause action // Pause action
addAction(R.drawable.ic_pause_24dp, addAction(
R.drawable.ic_pause_24dp,
context.getString(R.string.pause), context.getString(R.string.pause),
NotificationReceiver.pauseDownloadsPendingBroadcast(context)) NotificationReceiver.pauseDownloadsPendingBroadcast(context)
)
} }
val title = download.manga.title.chop(15) val title = download.manga.title.chop(15)
val quotedTitle = Pattern.quote(title) val quotedTitle = Pattern.quote(title)
val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), "") val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), "")
setContentTitle("$title - $chapter".chop(30)) setContentTitle("$title - $chapter".chop(30))
setContentText(context.getString(R.string.downloading_progress) setContentText(
.format(download.downloadedImages, download.pages!!.size)) context.getString(R.string.downloading_progress)
.format(download.downloadedImages, download.pages!!.size)
)
setStyle(null) setStyle(null)
setProgress(download.pages!!.size, download.downloadedImages, false) setProgress(download.pages!!.size, download.downloadedImages, false)
} }

View File

@ -163,11 +163,15 @@ class DownloadService : Service() {
subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext) subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ state -> onNetworkStateChanged(state) .subscribe(
}, { { state ->
onNetworkStateChanged(state)
},
{
toast(R.string.could_not_download_chapter_can_try_again) toast(R.string.could_not_download_chapter_can_try_again)
stopSelf() stopSelf()
}) }
)
} }
/** /**

View File

@ -20,7 +20,9 @@ class CoverViewTarget(
progress?.gone() progress?.gone()
view.scaleType = ImageView.ScaleType.CENTER view.scaleType = ImageView.ScaleType.CENTER
val vector = VectorDrawableCompat.create( val vector = VectorDrawableCompat.create(
view.context.resources, R.drawable.ic_broken_image_24dp, null view.context.resources,
R.drawable.ic_broken_image_24dp,
null
) )
vector?.setTint(view.context.getResourceColor(android.R.attr.textColorSecondary)) vector?.setTint(view.context.getResourceColor(android.R.attr.textColorSecondary))
view.setImageDrawable(vector) view.setImageDrawable(vector)

View File

@ -70,7 +70,8 @@ class MangaFetcher : Fetcher<Manga> {
return fileLoader(coverFile) return fileLoader(coverFile)
} }
val (_, body) = awaitGetCall( val (_, body) = awaitGetCall(
manga, if (manga.favorite) { manga,
if (manga.favorite) {
!options.networkCachePolicy.readEnabled !options.networkCachePolicy.readEnabled
} else { } else {
false false

View File

@ -29,7 +29,8 @@ class CustomMangaManager(val context: Context) {
val json = try { val json = try {
Gson().fromJson( Gson().fromJson(
Scanner(editJson).useDelimiter("\\Z").next(), JsonObject::class.java Scanner(editJson).useDelimiter("\\Z").next(),
JsonObject::class.java
) )
} catch (e: Exception) { } catch (e: Exception) {
null null
@ -83,7 +84,12 @@ class CustomMangaManager(val context: Context) {
fun Manga.toJson(): MangaJson { fun Manga.toJson(): MangaJson {
return MangaJson( return MangaJson(
id!!, title, author, artist, description, genre?.split(", ")?.toTypedArray() id!!,
title,
author,
artist,
description,
genre?.split(", ")?.toTypedArray()
) )
} }

View File

@ -42,8 +42,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
.build() .build()
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>( val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
interval.toLong(), TimeUnit.HOURS, interval.toLong(),
10, TimeUnit.MINUTES) TimeUnit.HOURS,
10,
TimeUnit.MINUTES
)
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()

View File

@ -131,7 +131,9 @@ class LibraryUpdateNotifier(private val context: Context) {
val manga = it.key val manga = it.key
val chapters = it.value val chapters = it.value
val chapterNames = chapters.map { chapter -> chapter.name } val chapterNames = chapters.map { chapter -> chapter.name }
notifications.add(Pair(context.notification(Notifications.CHANNEL_NEW_CHAPTERS) { notifications.add(
Pair(
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachi) setSmallIcon(R.drawable.ic_tachi)
try { try {
val request = GetRequest.Builder(context).data(manga) val request = GetRequest.Builder(context).data(manga)
@ -148,8 +150,8 @@ class LibraryUpdateNotifier(private val context: Context) {
setContentTitle(manga.title) setContentTitle(manga.title)
color = ContextCompat.getColor(context, R.color.colorAccent) color = ContextCompat.getColor(context, R.color.colorAccent)
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) { val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
"${chapterNames.take(MAX_CHAPTERS - 1) "${chapterNames.take(MAX_CHAPTERS - 1).joinToString(", ")}, " +
.joinToString(", ")}, " + context.resources.getQuantityString( context.resources.getQuantityString(
R.plurals.notification_and_n_more, R.plurals.notification_and_n_more,
(chapterNames.size - (MAX_CHAPTERS - 1)), (chapterNames.size - (MAX_CHAPTERS - 1)),
(chapterNames.size - (MAX_CHAPTERS - 1)) (chapterNames.size - (MAX_CHAPTERS - 1))
@ -161,30 +163,41 @@ class LibraryUpdateNotifier(private val context: Context) {
setGroup(Notifications.GROUP_NEW_CHAPTERS) setGroup(Notifications.GROUP_NEW_CHAPTERS)
setContentIntent( setContentIntent(
NotificationReceiver.openChapterPendingActivity( NotificationReceiver.openChapterPendingActivity(
context, manga, chapters.first() context,
manga,
chapters.first()
) )
) )
addAction( addAction(
R.drawable.ic_eye_24dp, R.drawable.ic_eye_24dp,
context.getString(R.string.mark_as_read), context.getString(R.string.mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast( NotificationReceiver.markAsReadPendingBroadcast(
context, manga, chapters, Notifications.ID_NEW_CHAPTERS context,
manga,
chapters,
Notifications.ID_NEW_CHAPTERS
) )
) )
addAction( addAction(
R.drawable.ic_book_24dp, R.drawable.ic_book_24dp,
context.getString(R.string.view_chapters), context.getString(R.string.view_chapters),
NotificationReceiver.openChapterPendingActivity( NotificationReceiver.openChapterPendingActivity(
context, manga, Notifications.ID_NEW_CHAPTERS context,
manga,
Notifications.ID_NEW_CHAPTERS
) )
) )
setAutoCancel(true) setAutoCancel(true)
}, manga.id.hashCode())) },
manga.id.hashCode()
)
)
} }
NotificationManagerCompat.from(context).apply { NotificationManagerCompat.from(context).apply {
notify(Notifications.ID_NEW_CHAPTERS, notify(
Notifications.ID_NEW_CHAPTERS,
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) { context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachi) setSmallIcon(R.drawable.ic_tachi)
setLargeIcon(notificationBitmap) setLargeIcon(notificationBitmap)
@ -193,14 +206,18 @@ class LibraryUpdateNotifier(private val context: Context) {
if (updates.size > 1) { if (updates.size > 1) {
setContentText( setContentText(
context.resources.getQuantityString( context.resources.getQuantityString(
R.plurals.for_n_titles, updates.size, updates.size R.plurals.for_n_titles,
updates.size,
updates.size
) )
) )
setStyle( setStyle(
NotificationCompat.BigTextStyle() NotificationCompat.BigTextStyle()
.bigText(updates.keys.joinToString("\n") { .bigText(
updates.keys.joinToString("\n") {
it.title.chop(45) it.title.chop(45)
}) }
)
) )
} else { } else {
setContentText(updates.keys.first().title.chop(45)) setContentText(updates.keys.first().title.chop(45))
@ -211,7 +228,8 @@ class LibraryUpdateNotifier(private val context: Context) {
setGroupSummary(true) setGroupSummary(true)
setContentIntent(getNotificationIntent()) setContentIntent(getNotificationIntent())
setAutoCancel(true) setAutoCancel(true)
}) }
)
notifications.forEach { notifications.forEach {
notify(it.second, it.first) notify(it.second, it.first)

View File

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

View File

@ -134,7 +134,8 @@ class LibraryUpdateService(
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault() val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
val savedMangasList = intent.getLongArrayExtra(KEY_MANGAS)?.asList() val savedMangasList = intent.getLongArrayExtra(KEY_MANGAS)?.asList()
val mangaList = (if (savedMangasList != null) { val mangaList = (
if (savedMangasList != null) {
val mangas = db.getLibraryMangas().executeAsBlocking().filter { val mangas = db.getLibraryMangas().executeAsBlocking().filter {
it.id in savedMangasList it.id in savedMangasList
}.distinctBy { it.id } }.distinctBy { it.id }
@ -143,7 +144,8 @@ class LibraryUpdateService(
mangas mangas
} else { } else {
getMangaToUpdate(intent, target) getMangaToUpdate(intent, target)
}).sortedWith(rankingScheme[selectedScheme]) }
).sortedWith(rankingScheme[selectedScheme])
// Update favorite manga. Destroy service when completed or in case of an error. // Update favorite manga. Destroy service when completed or in case of an error.
launchTarget(target, mangaList, startId) launchTarget(target, mangaList, startId)
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
@ -157,7 +159,8 @@ class LibraryUpdateService(
super.onCreate() super.onCreate()
notifier = LibraryUpdateNotifier(this) notifier = LibraryUpdateNotifier(this)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock" PowerManager.PARTIAL_WAKE_LOCK,
"LibraryUpdateService:WakeLock"
) )
wakeLock.acquire(TimeUnit.MINUTES.toMillis(30)) wakeLock.acquire(TimeUnit.MINUTES.toMillis(30))
startForeground(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build()) startForeground(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build())
@ -247,7 +250,9 @@ class LibraryUpdateService(
val hasDLs = try { val hasDLs = try {
requestSemaphore.withPermit { requestSemaphore.withPermit {
updateMangaInSource( updateMangaInSource(
it.key, downloadNew, categoriesToDownload it.key,
downloadNew,
categoriesToDownload
) )
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -351,13 +356,15 @@ class LibraryUpdateService(
var hasDownloads = false var hasDownloads = false
while (count < mangaToUpdateMap[source]!!.size) { while (count < mangaToUpdateMap[source]!!.size) {
val shouldDownload = val shouldDownload =
(downloadNew && (categoriesToDownload.isEmpty() || mangaToUpdateMap[source]!![count].category in categoriesToDownload || db.getCategoriesForManga( (
mangaToUpdateMap[source]!![count] downloadNew && (
).executeOnIO().any { (it.id ?: -1) in categoriesToDownload })) categoriesToDownload.isEmpty() ||
if (updateMangaChapters( mangaToUpdateMap[source]!![count].category in categoriesToDownload ||
mangaToUpdateMap[source]!![count], this.count.andIncrement, shouldDownload db.getCategoriesForManga(mangaToUpdateMap[source]!![count])
.executeOnIO().any { (it.id ?: -1) in categoriesToDownload }
) )
) { )
if (updateMangaChapters(mangaToUpdateMap[source]!![count], this.count.andIncrement, shouldDownload)) {
hasDownloads = true hasDownloads = true
} }
count++ count++

View File

@ -58,29 +58,41 @@ class NotificationReceiver : BroadcastReceiver() {
// Clear the download queue // Clear the download queue
ACTION_CLEAR_DOWNLOADS -> downloadManager.clearQueue(true) ACTION_CLEAR_DOWNLOADS -> downloadManager.clearQueue(true)
// Launch share activity and dismiss notification // Launch share activity and dismiss notification
ACTION_SHARE_IMAGE -> shareImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION), ACTION_SHARE_IMAGE -> shareImage(
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) context,
intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
// Delete image from path and dismiss notification // Delete image from path and dismiss notification
ACTION_DELETE_IMAGE -> deleteImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION), ACTION_DELETE_IMAGE -> deleteImage(
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) context,
intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
// Cancel library update and dismiss notification // Cancel library update and dismiss notification
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context) ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context)
ACTION_CANCEL_RESTORE -> cancelRestoreUpdate(context) ACTION_CANCEL_RESTORE -> cancelRestoreUpdate(context)
// Share backup file // Share backup file
ACTION_SHARE_BACKUP -> ACTION_SHARE_BACKUP ->
shareBackup( shareBackup(
context, intent.getParcelableExtra(EXTRA_URI), context,
intent.getParcelableExtra(EXTRA_URI),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
// Open reader activity // Open reader activity
ACTION_OPEN_CHAPTER -> { ACTION_OPEN_CHAPTER -> {
openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1), openChapter(
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)) context,
intent.getLongExtra(EXTRA_MANGA_ID, -1),
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
)
} }
ACTION_MARK_AS_READ -> { ACTION_MARK_AS_READ -> {
val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
if (notificationId > -1) dismissNotification( if (notificationId > -1) dismissNotification(
context, notificationId, intent.getIntExtra(EXTRA_GROUP_ID, 0) context,
notificationId,
intent.getIntExtra(EXTRA_GROUP_ID, 0)
) )
val urls = intent.getStringArrayExtra(EXTRA_CHAPTER_URL) ?: return val urls = intent.getStringArrayExtra(EXTRA_CHAPTER_URL) ?: return
val mangaId = intent.getLongExtra(EXTRA_MANGA_ID, -1) val mangaId = intent.getLongExtra(EXTRA_MANGA_ID, -1)
@ -377,8 +389,13 @@ class NotificationReceiver : BroadcastReceiver() {
clipData = ClipData.newRawUri(null, uri) clipData = ClipData.newRawUri(null, uri)
type = "image/*" type = "image/*"
} }
return PendingIntent.getActivity(context, 0, shareIntent, PendingIntent return PendingIntent.getActivity(
.FLAG_CANCEL_CURRENT) context,
0,
shareIntent,
PendingIntent
.FLAG_CANCEL_CURRENT
)
} }
/** /**
@ -412,8 +429,13 @@ class NotificationReceiver : BroadcastReceiver() {
Chapter Chapter
): PendingIntent { ): PendingIntent {
val newIntent = ReaderActivity.newIntent(context, manga, chapter) val newIntent = ReaderActivity.newIntent(context, manga, chapter)
return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent return PendingIntent.getActivity(
.FLAG_UPDATE_CURRENT) context,
manga.id.hashCode(),
newIntent,
PendingIntent
.FLAG_UPDATE_CURRENT
)
} }
/** /**
@ -431,7 +453,10 @@ class NotificationReceiver : BroadcastReceiver() {
.putExtra("notificationId", manga.id.hashCode()) .putExtra("notificationId", manga.id.hashCode())
.putExtra("groupId", groupId) .putExtra("groupId", groupId)
return PendingIntent.getActivity( return PendingIntent.getActivity(
context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT context,
manga.id.hashCode(),
newIntent,
PendingIntent.FLAG_UPDATE_CURRENT
) )
} }
@ -462,7 +487,10 @@ class NotificationReceiver : BroadcastReceiver() {
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_EXTENSIONS) Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_EXTENSIONS)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
return PendingIntent.getActivity( return PendingIntent.getActivity(
context, 0, newIntent, PendingIntent.FLAG_UPDATE_CURRENT context,
0,
newIntent,
PendingIntent.FLAG_UPDATE_CURRENT
) )
} }

View File

@ -61,37 +61,44 @@ object Notifications {
fun createChannels(context: Context) { fun createChannels(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val channels = listOf(NotificationChannel( val channels = listOf(
NotificationChannel(
CHANNEL_COMMON, CHANNEL_COMMON,
context.getString(R.string.common), context.getString(R.string.common),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
), NotificationChannel( ),
NotificationChannel(
CHANNEL_LIBRARY, CHANNEL_LIBRARY,
context.getString(R.string.updating_library), context.getString(R.string.updating_library),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
setShowBadge(false) setShowBadge(false)
}, NotificationChannel( },
NotificationChannel(
CHANNEL_DOWNLOADER, CHANNEL_DOWNLOADER,
context.getString(R.string.downloads), context.getString(R.string.downloads),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
setShowBadge(false) setShowBadge(false)
}, NotificationChannel( },
NotificationChannel(
CHANNEL_UPDATES_TO_EXTS, CHANNEL_UPDATES_TO_EXTS,
context.getString(R.string.extension_updates), context.getString(R.string.extension_updates),
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
), NotificationChannel( ),
NotificationChannel(
CHANNEL_NEW_CHAPTERS, CHANNEL_NEW_CHAPTERS,
context.getString(R.string.new_chapters), context.getString(R.string.new_chapters),
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
), NotificationChannel( ),
NotificationChannel(
CHANNEL_BACKUP_RESTORE, CHANNEL_BACKUP_RESTORE,
context.getString(R.string.restoring_backup), context.getString(R.string.restoring_backup),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
setShowBadge(false) setShowBadge(false)
}) }
)
context.notificationManager.createNotificationChannels(channels) context.notificationManager.createNotificationChannels(channels)
} }
} }

View File

@ -43,12 +43,20 @@ class PreferencesHelper(val context: Context) {
private val flowPrefs = FlowSharedPreferences(prefs) private val flowPrefs = FlowSharedPreferences(prefs)
private val defaultDownloadsDir = Uri.fromFile( private val defaultDownloadsDir = Uri.fromFile(
File(Environment.getExternalStorageDirectory().absolutePath + File.separator + File(
context.getString(R.string.app_name), "downloads")) Environment.getExternalStorageDirectory().absolutePath + File.separator +
context.getString(R.string.app_name),
"downloads"
)
)
private val defaultBackupDir = Uri.fromFile( private val defaultBackupDir = Uri.fromFile(
File(Environment.getExternalStorageDirectory().absolutePath + File.separator + File(
context.getString(R.string.app_name), "backup")) Environment.getExternalStorageDirectory().absolutePath + File.separator +
context.getString(R.string.app_name),
"backup"
)
)
fun getInt(key: String, default: Int?) = rxPrefs.getInteger(key, default) fun getInt(key: String, default: Int?) = rxPrefs.getInteger(key, default)
fun getStringPref(key: String, default: String?) = rxPrefs.getString(key, default) fun getStringPref(key: String, default: String?) = rxPrefs.getString(key, default)

View File

@ -237,7 +237,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
.appendQueryParameter("response_type", "token") .appendQueryParameter("response_type", "token")
.build()!! .build()!!
fun addToLibraryQuery() = """ fun addToLibraryQuery() =
"""
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) { |mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) { |SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id | id
@ -246,7 +247,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|""".trimMargin() |""".trimMargin()
fun deleteFromLibraryQuery() = """ fun deleteFromLibraryQuery() =
"""
|mutation DeleteManga(${'$'}listId: Int) { |mutation DeleteManga(${'$'}listId: Int) {
|DeleteMediaListEntry (id: ${'$'}listId) { |DeleteMediaListEntry (id: ${'$'}listId) {
|deleted |deleted
@ -254,7 +256,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|}""".trimMargin() |}""".trimMargin()
fun updateInLibraryQuery() = """ fun updateInLibraryQuery() =
"""
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) { |mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) { |SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|id |id
@ -264,7 +267,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|""".trimMargin() |""".trimMargin()
fun searchQuery() = """ fun searchQuery() =
"""
|query Search(${'$'}query: String) { |query Search(${'$'}query: String) {
|Page (perPage: 50) { |Page (perPage: 50) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) { |media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
@ -289,7 +293,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|""".trimMargin() |""".trimMargin()
fun findLibraryMangaQuery() = """ fun findLibraryMangaQuery() =
"""
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) { |query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|Page { |Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) { |mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
@ -320,7 +325,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|""".trimMargin() |""".trimMargin()
fun currentUserQuery() = """ fun currentUserQuery() =
"""
|query User { |query User {
|Viewer { |Viewer {
|id |id

View File

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

View File

@ -44,7 +44,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
android.R.drawable.stat_sys_download_done, android.R.drawable.stat_sys_download_done,
context.getString(R.string.download), context.getString(R.string.download),
PendingIntent.getService( PendingIntent.getService(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
) )
) )
} }
@ -66,8 +69,11 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
.build() .build()
val request = PeriodicWorkRequestBuilder<UpdaterJob>( val request = PeriodicWorkRequestBuilder<UpdaterJob>(
1, TimeUnit.DAYS, 1,
1, TimeUnit.HOURS) TimeUnit.DAYS,
1,
TimeUnit.HOURS
)
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()

View File

@ -75,13 +75,17 @@ internal class UpdaterNotifier(private val context: Context) {
setProgress(0, 0, false) setProgress(0, 0, false)
// Install action // Install action
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri)) setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
addAction(R.drawable.ic_system_update_24dp, addAction(
R.drawable.ic_system_update_24dp,
context.getString(R.string.install), context.getString(R.string.install),
NotificationHandler.installApkPendingActivity(context, uri)) NotificationHandler.installApkPendingActivity(context, uri)
)
// Cancel action // Cancel action
addAction(R.drawable.ic_close_24dp, addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.cancel), context.getString(R.string.cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)) NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
)
} }
notification.show() notification.show()
} }
@ -99,13 +103,17 @@ internal class UpdaterNotifier(private val context: Context) {
setProgress(0, 0, false) setProgress(0, 0, false)
color = ContextCompat.getColor(context, R.color.colorAccent) color = ContextCompat.getColor(context, R.color.colorAccent)
// Retry action // Retry action
addAction(R.drawable.ic_refresh_24dp, addAction(
R.drawable.ic_refresh_24dp,
context.getString(R.string.retry), context.getString(R.string.retry),
UpdaterService.downloadApkPendingService(context, url)) UpdaterService.downloadApkPendingService(context, url)
)
// Cancel action // Cancel action
addAction(R.drawable.ic_close_24dp, addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.cancel), context.getString(R.string.cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)) NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
)
} }
notification.show(Notifications.ID_UPDATER) notification.show(Notifications.ID_UPDATER)
} }

View File

@ -43,7 +43,8 @@ class UpdaterService : Service() {
startForeground(Notifications.ID_UPDATER, notifier.onDownloadStarted(getString(R.string.app_name)).build()) startForeground(Notifications.ID_UPDATER, notifier.onDownloadStarted(getString(R.string.app_name)).build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock" PowerManager.PARTIAL_WAKE_LOCK,
"${javaClass.name}:WakeLock"
) )
wakeLock.acquire() wakeLock.acquire()
} }

View File

@ -80,8 +80,7 @@ class ExtensionManager(
context.packageManager.getApplicationIcon(pkgName) context.packageManager.getApplicationIcon(pkgName)
} catch (e: Exception) { } catch (e: Exception) {
null null
} } else null
else null
} }
/** /**

View File

@ -45,12 +45,15 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
val preferences: PreferencesHelper by injectLazy() val preferences: PreferencesHelper by injectLazy()
preferences.extensionUpdatesCount().set(names.size) preferences.extensionUpdatesCount().set(names.size)
NotificationManagerCompat.from(context).apply { NotificationManagerCompat.from(context).apply {
notify(Notifications.ID_UPDATES_TO_EXTS, notify(
Notifications.ID_UPDATES_TO_EXTS,
context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) { context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) {
setContentTitle( setContentTitle(
context.resources.getQuantityString( context.resources.getQuantityString(
R.plurals.extension_updates_available, names R.plurals.extension_updates_available,
.size, names.size names
.size,
names.size
) )
) )
val extNames = names.joinToString(", ") val extNames = names.joinToString(", ")
@ -64,7 +67,8 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
) )
) )
setAutoCancel(true) setAutoCancel(true)
}) }
)
} }
} }
@ -80,8 +84,11 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
.build() .build()
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>( val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
12, TimeUnit.HOURS, 12,
1, TimeUnit.HOURS) TimeUnit.HOURS,
1,
TimeUnit.HOURS
)
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()

View File

@ -103,8 +103,10 @@ internal object ExtensionLoader {
// Validate lib version // Validate lib version
val majorLibVersion = versionName.substringBefore('.').toInt() val majorLibVersion = versionName.substringBefore('.').toInt()
if (majorLibVersion < LIB_VERSION_MIN || majorLibVersion > LIB_VERSION_MAX) { if (majorLibVersion < LIB_VERSION_MIN || majorLibVersion > LIB_VERSION_MAX) {
val exception = Exception("Lib version is $majorLibVersion, while only versions " + val exception = Exception(
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed") "Lib version is $majorLibVersion, while only versions " +
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
)
Timber.w(exception) Timber.w(exception)
return LoadResult.Error(exception) return LoadResult.Error(exception)
} }

View File

@ -58,7 +58,8 @@ fun Call.asObservable(): Observable<Response> {
// Based on https://github.com/gildor/kotlin-coroutines-okhttp // Based on https://github.com/gildor/kotlin-coroutines-okhttp
suspend fun Call.await(): Response { suspend fun Call.await(): Response {
return suspendCancellableCoroutine { continuation -> return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback { enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) { override fun onResponse(call: Call, response: Response) {
continuation.resume(response) continuation.resume(response)
} }
@ -68,7 +69,8 @@ suspend fun Call.await(): Response {
if (continuation.isCancelled) return if (continuation.isCancelled) return
continuation.resumeWithException(e) continuation.resumeWithException(e)
} }
}) }
)
continuation.invokeOnCancellation { continuation.invokeOnCancellation {
try { try {

View File

@ -241,10 +241,12 @@ class LocalSource(private val context: Context) : CatalogueSource {
date_upload = chapterFile.lastModified() date_upload = chapterFile.lastModified()
ChapterRecognition.parseChapterNumber(this, manga) ChapterRecognition.parseChapterNumber(this, manga)
} }
}.sortedWith(Comparator { c1, c2 -> }.sortedWith(
Comparator { c1, c2 ->
val c = c2.chapter_number.compareTo(c1.chapter_number) val c = c2.chapter_number.compareTo(c1.chapter_number)
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
}) }
)
return Observable.just(chapters) return Observable.just(chapters)
} }
@ -289,18 +291,22 @@ class LocalSource(private val context: Context) : CatalogueSource {
return when (format) { return when (format) {
is Format.Directory -> { is Format.Directory -> {
val entry = format.file.listFiles() val entry = format.file.listFiles()
?.sortedWith(Comparator<File> { f1, f2 -> ?.sortedWith(
Comparator<File> { f1, f2 ->
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
}) }
)
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } ?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
entry?.let { updateCover(context, manga, it.inputStream()) } entry?.let { updateCover(context, manga, it.inputStream()) }
} }
is Format.Zip -> { is Format.Zip -> {
ZipFile(format.file).use { zip -> ZipFile(format.file).use { zip ->
val entry = zip.entries().toList().sortedWith(Comparator<ZipEntry> { f1, f2 -> val entry = zip.entries().toList().sortedWith(
Comparator<ZipEntry> { f1, f2 ->
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
}).find { }
).find {
!it.isDirectory && ImageUtil.isImage(it.name) { !it.isDirectory && ImageUtil.isImage(it.name) {
zip.getInputStream(it) zip.getInputStream(it)
} }
@ -311,9 +317,11 @@ class LocalSource(private val context: Context) : CatalogueSource {
} }
is Format.Rar -> { is Format.Rar -> {
Archive(format.file).use { archive -> Archive(format.file).use { archive ->
val entry = archive.fileHeaders.sortedWith(Comparator<FileHeader> { f1, f2 -> val entry = archive.fileHeaders.sortedWith(
Comparator<FileHeader> { f1, f2 ->
f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString)
}).find { }
).find {
!it.isDirectory && ImageUtil.isImage(it.fileNameString) { !it.isDirectory && ImageUtil.isImage(it.fileNameString) {
archive.getInputStream(it) archive.getInputStream(it)
} }

View File

@ -21,13 +21,24 @@ open class SourceManager(private val context: Context) {
private val delegatedSources = listOf( private val delegatedSources = listOf(
DelegatedSource( DelegatedSource(
"reader.kireicake.com", 5509224355268673176, KireiCake() "reader.kireicake.com",
), DelegatedSource( 5509224355268673176,
"jaiminisbox.com", 9064882169246918586, FoolSlide("jaiminis", "/reader") KireiCake()
), DelegatedSource( ),
"mangadex.org", 2499283573021220255, MangaDex() DelegatedSource(
), DelegatedSource( "jaiminisbox.com",
"mangaplus.shueisha.co.jp", 1998944621602463790, MangaPlus() 9064882169246918586,
FoolSlide("jaiminis", "/reader")
),
DelegatedSource(
"mangadex.org",
2499283573021220255,
MangaDex()
),
DelegatedSource(
"mangaplus.shueisha.co.jp",
1998944621602463790,
MangaPlus()
) )
).associateBy { it.sourceId } ).associateBy { it.sourceId }
@ -92,8 +103,10 @@ open class SourceManager(private val context: Context) {
private fun getSourceNotInstalledException(): Exception { private fun getSourceNotInstalledException(): Exception {
return SourceNotFoundException( return SourceNotFoundException(
context.getString( context.getString(
R.string.source_not_installed_, id.toString() R.string.source_not_installed_,
), id id.toString()
),
id
) )
} }

View File

@ -34,7 +34,11 @@ DelegatedHttpSource
val chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null val chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null
val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull()?.toString() val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull()?.toString()
return "$urlModifier/read/" + listOfNotNull( return "$urlModifier/read/" + listOfNotNull(
mangaName, lang, volume, chapterNumber, subChapterNumber mangaName,
lang,
volume,
chapterNumber,
subChapterNumber
).joinToString("/") + "/" ).joinToString("/") + "/"
} }
@ -95,8 +99,11 @@ DelegatedHttpSource
private fun allowAdult(request: Request) = allowAdult(request.url.toString()) private fun allowAdult(request: Request) = allowAdult(request.url.toString())
private fun allowAdult(url: String): Request { private fun allowAdult(url: String): Request {
return POST(url, body = FormBody.Builder() return POST(
url,
body = FormBody.Builder()
.add("adult", "true") .add("adult", "true")
.build()) .build()
)
} }
} }

View File

@ -17,9 +17,8 @@ class KireiCake : FoolSlide("kireicake") {
this.url = url this.url = url
source = delegate?.id ?: -1 source = delegate?.id ?: -1
title = document.select("$mangaDetailsInfoSelector li:has(b:contains(title))").first() title = document.select("$mangaDetailsInfoSelector li:has(b:contains(title))").first()
?.ownText()?.substringAfter(":")?.trim() ?: url.split("/").last().replace( ?.ownText()?.substringAfter(":")?.trim()
"_", " " + "" ?: url.split("/").last().replace("_", " " + "").capitalizeWords()
).capitalizeWords()
description = description =
document.select("$mangaDetailsInfoSelector li:has(b:contains(description))").first() document.select("$mangaDetailsInfoSelector li:has(b:contains(description))").first()
?.ownText()?.substringAfter(":") ?.ownText()?.substringAfter(":")

View File

@ -61,9 +61,13 @@ class MangaPlus : DelegatedHttpSource() {
context.getString(R.string.chapter_not_found) context.getString(R.string.chapter_not_found)
) )
if (manga != null) { if (manga != null) {
Triple(trueChapter, manga.apply { Triple(
trueChapter,
manga.apply {
this.title = trimmedTitle this.title = trimmedTitle
}, chapters.orEmpty()) },
chapters.orEmpty()
)
} else null } else null
} }
} }

View File

@ -33,12 +33,16 @@ class CenteredToolbar@JvmOverloads constructor(context: Context, attrs: Attribut
} }
fun showDropdown(down: Boolean = true) { fun showDropdown(down: Boolean = true) {
toolbar_title.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_blank_24dp, 0, toolbar_title.setCompoundDrawablesRelativeWithIntrinsicBounds(
R.drawable.ic_blank_24dp,
0,
if (down) { if (down) {
R.drawable.ic_arrow_drop_down_24dp R.drawable.ic_arrow_drop_down_24dp
} else { } else {
R.drawable.ic_arrow_drop_up_24dp R.drawable.ic_arrow_drop_up_24dp
}, 0) },
0
)
} }
fun hideDropdown() { fun hideDropdown() {

View File

@ -21,7 +21,9 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
var scrollOffset = 0 var scrollOffset = 0
init { init {
setViewsToUse( setViewsToUse(
R.layout.material_fastscroll, R.id.fast_scroller_bubble, R.id.fast_scroller_handle R.layout.material_fastscroll,
R.id.fast_scroller_bubble,
R.id.fast_scroller_handle
) )
autoHideEnabled = true autoHideEnabled = true
ignoreTouchesOutsideHandle = false ignoreTouchesOutsideHandle = false
@ -85,7 +87,8 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
val targetPos = getTargetPos(y) val targetPos = getTargetPos(y)
if (layoutManager is StaggeredGridLayoutManager) { if (layoutManager is StaggeredGridLayoutManager) {
(layoutManager as StaggeredGridLayoutManager).scrollToPositionWithOffset( (layoutManager as StaggeredGridLayoutManager).scrollToPositionWithOffset(
targetPos, scrollOffset targetPos,
scrollOffset
) )
} else { } else {
(layoutManager as LinearLayoutManager).scrollToPositionWithOffset(targetPos, scrollOffset) (layoutManager as LinearLayoutManager).scrollToPositionWithOffset(targetPos, scrollOffset)

View File

@ -119,7 +119,10 @@ class MaterialMenuSheet(
isElevated = elevate isElevated = elevate
elevationAnimator?.cancel() elevationAnimator?.cancel()
elevationAnimator = ObjectAnimator.ofFloat( elevationAnimator = ObjectAnimator.ofFloat(
title_layout, "elevation", title_layout.elevation, if (elevate) 10f else 0f title_layout,
"elevation",
title_layout.elevation,
if (elevate) 10f else 0f
) )
elevationAnimator?.start() elevationAnimator?.start()
} }

View File

@ -14,11 +14,13 @@ import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.* import kotlinx.android.synthetic.*
import timber.log.Timber import timber.log.Timber
abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateController(bundle), abstract class BaseController(bundle: Bundle? = null) :
RestoreViewOnCreateController(bundle),
LayoutContainer { LayoutContainer {
init { init {
addLifecycleListener(object : LifecycleListener() { addLifecycleListener(
object : LifecycleListener() {
override fun postCreateView(controller: Controller, view: View) { override fun postCreateView(controller: Controller, view: View) {
onViewCreated(view) onViewCreated(view)
} }
@ -38,7 +40,8 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
override fun preDestroyView(controller: Controller, view: View) { override fun preDestroyView(controller: Controller, view: View) {
Timber.d("Destroy view for ${controller.instance()}") Timber.d("Destroy view for ${controller.instance()}")
} }
}) }
)
} }
override val containerView: View? override val containerView: View?
@ -96,7 +99,8 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
*/ */
var expandActionViewFromInteraction = false var expandActionViewFromInteraction = false
fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) { fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
setOnActionExpandListener(object : MenuItem.OnActionExpandListener { setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {
return onExpand?.invoke(item) ?: true return onExpand?.invoke(item) ?: true
} }
@ -106,7 +110,8 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
return onCollapse?.invoke(item) ?: true return onCollapse?.invoke(item) ?: true
} }
}) }
)
if (expandActionViewFromInteraction) { if (expandActionViewFromInteraction) {
expandActionViewFromInteraction = false expandActionViewFromInteraction = false

View File

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

View File

@ -7,7 +7,8 @@ import nucleus.factory.PresenterFactory
import nucleus.presenter.Presenter import nucleus.presenter.Presenter
@Suppress("LeakingThis") @Suppress("LeakingThis")
abstract class NucleusController<P : Presenter<*>>(val bundle: Bundle? = null) : RxController(bundle), abstract class NucleusController<P : Presenter<*>>(val bundle: Bundle? = null) :
RxController(bundle),
PresenterFactory<P> { PresenterFactory<P> {
private val delegate = NucleusConductorDelegate(this) private val delegate = NucleusConductorDelegate(this)

View File

@ -22,7 +22,8 @@ import kotlinx.android.synthetic.main.categories_controller.*
/** /**
* Controller to manage the categories for the users' library. * Controller to manage the categories for the users' library.
*/ */
class CategoryController(bundle: Bundle? = null) : BaseController(bundle), class CategoryController(bundle: Bundle? = null) :
BaseController(bundle),
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemMoveListener, FlexibleAdapter.OnItemMoveListener,
CategoryAdapter.CategoryItemListener { CategoryAdapter.CategoryItemListener {
@ -147,12 +148,14 @@ class CategoryController(bundle: Bundle? = null) : BaseController(bundle),
adapter?.restoreDeletedItems() adapter?.restoreDeletedItems()
undoing = true undoing = true
} }
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { addCallback(
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event) super.onDismissed(transientBottomBar, event)
if (!undoing) confirmDelete() if (!undoing) confirmDelete()
} }
}) }
)
} }
(activity as? MainActivity)?.setUndoSnackBar(snack) (activity as? MainActivity)?.setUndoSnackBar(snack)
} }

View File

@ -51,16 +51,22 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
createCategory = category.order == CREATE_CATEGORY_ORDER createCategory = category.order == CREATE_CATEGORY_ORDER
if (createCategory) { if (createCategory) {
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.text_color_hint)) title.setTextColor(ContextCompat.getColor(itemView.context, R.color.text_color_hint))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable regularDrawable = ContextCompat.getDrawable(
.ic_add_24dp) itemView.context,
R.drawable
.ic_add_24dp
)
image.gone() image.gone()
edit_button.setImageDrawable(null) edit_button.setImageDrawable(null)
edit_text.setText("") edit_text.setText("")
edit_text.hint = title.text edit_text.hint = title.text
} else { } else {
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary)) title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable regularDrawable = ContextCompat.getDrawable(
.ic_drag_handle_24dp) itemView.context,
R.drawable
.ic_drag_handle_24dp
)
image.visible() image.visible()
edit_text.setText(title.text) edit_text.setText(title.text)
} }
@ -80,7 +86,8 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
if (!createCategory) { if (!createCategory) {
reorder.setImageDrawable( reorder.setImageDrawable(
ContextCompat.getDrawable( ContextCompat.getDrawable(
itemView.context, R.drawable.ic_delete_24dp itemView.context,
R.drawable.ic_delete_24dp
) )
) )
reorder.setOnClickListener { reorder.setOnClickListener {
@ -96,8 +103,13 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
reorder.setOnTouchListener { _, _ -> true } reorder.setOnTouchListener { _, _ -> true }
} }
edit_text.clearFocus() edit_text.clearFocus()
edit_button.drawable?.mutate()?.setTint(ContextCompat.getColor(itemView.context, R edit_button.drawable?.mutate()?.setTint(
.color.gray_button)) ContextCompat.getColor(
itemView.context,
R
.color.gray_button
)
)
reorder.setImageDrawable(regularDrawable) reorder.setImageDrawable(regularDrawable)
} }
} }
@ -105,7 +117,8 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
private fun submitChanges() { private fun submitChanges() {
if (edit_text.visibility == View.VISIBLE) { if (edit_text.visibility == View.VISIBLE) {
if (adapter.categoryItemListener if (adapter.categoryItemListener
.onCategoryRename(adapterPosition, edit_text.text.toString())) { .onCategoryRename(adapterPosition, edit_text.text.toString())
) {
isEditing(false) isEditing(false)
edit_text.inputType = InputType.TYPE_NULL edit_text.inputType = InputType.TYPE_NULL
if (!createCategory) if (!createCategory)
@ -119,8 +132,11 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
private fun showKeyboard() { private fun showKeyboard() {
val inputMethodManager: InputMethodManager = val inputMethodManager: InputMethodManager =
itemView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager itemView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(edit_text, WindowManager.LayoutParams inputMethodManager.showSoftInput(
.SOFT_INPUT_ADJUST_PAN) edit_text,
WindowManager.LayoutParams
.SOFT_INPUT_ADJUST_PAN
)
} }
/** /**

View File

@ -70,7 +70,8 @@ class ManageCategoryDialog(bundle: Bundle? = null) :
preferences.downloadNew().set(true) preferences.downloadNew().set(true)
} }
if (preferences.libraryUpdateInterval().getOrDefault() > 0 && if (preferences.libraryUpdateInterval().getOrDefault() > 0 &&
!updatePref(preferences.libraryUpdateCategories(), view.include_global)) { !updatePref(preferences.libraryUpdateCategories(), view.include_global)
) {
preferences.libraryUpdateInterval().set(0) preferences.libraryUpdateInterval().set(0)
LibraryUpdateJob.setupTask(0) LibraryUpdateJob.setupTask(0)
} }
@ -99,9 +100,11 @@ class ManageCategoryDialog(bundle: Bundle? = null) :
view.title.hint = category.name view.title.hint = category.name
view.title.append(category.name) view.title.append(category.name)
val downloadNew = preferences.downloadNew().getOrDefault() val downloadNew = preferences.downloadNew().getOrDefault()
setCheckbox(view.download_new, setCheckbox(
view.download_new,
preferences.downloadNewCategories(), preferences.downloadNewCategories(),
true) true
)
if (downloadNew && preferences.downloadNewCategories().getOrDefault().isEmpty()) if (downloadNew && preferences.downloadNewCategories().getOrDefault().isEmpty())
view.download_new.gone() view.download_new.gone()
else if (!downloadNew) else if (!downloadNew)

View File

@ -8,8 +8,11 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
* *
* @param context the context of the fragment containing this adapter. * @param context the context of the fragment containing this adapter.
*/ */
class DownloadAdapter(controller: DownloadItemListener) : FlexibleAdapter<DownloadItem>(null, controller, class DownloadAdapter(controller: DownloadItemListener) : FlexibleAdapter<DownloadItem>(
true) { null,
controller,
true
) {
/** /**
* Listener called when an item of the list is released. * Listener called when an item of the list is released.

View File

@ -94,7 +94,8 @@ class DownloadBottomSheet @JvmOverloads constructor(
private fun updateDLTitle() { private fun updateDLTitle() {
val extCount = presenter.downloadQueue.firstOrNull() val extCount = presenter.downloadQueue.firstOrNull()
title_text.text = if (extCount != null) resources.getString( title_text.text = if (extCount != null) resources.getString(
R.string.downloading_, extCount.chapter.name R.string.downloading_,
extCount.chapter.name
) )
else "" else ""
} }
@ -164,7 +165,8 @@ class DownloadBottomSheet @JvmOverloads constructor(
if (presenter.downloadQueue.isEmpty()) { if (presenter.downloadQueue.isEmpty()) {
empty_view?.show( empty_view?.show(
R.drawable.ic_download_off_24dp, R.drawable.ic_download_off_24dp,
R.string.nothing_is_downloading) R.string.nothing_is_downloading
)
} else { } else {
empty_view?.hide() empty_view?.hide()
} }

View File

@ -17,22 +17,38 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut
FrameLayout(context, attrs) { FrameLayout(context, attrs) {
private val activeColor = context.getResourceColor(R.attr.colorAccent) private val activeColor = context.getResourceColor(R.attr.colorAccent)
private val progressBGColor = ContextCompat.getColor(context, private val progressBGColor = ContextCompat.getColor(
R.color.divider) context,
private val disabledColor = ContextCompat.getColor(context, R.color.divider
R.color.material_on_surface_disabled) )
private val downloadedColor = ContextCompat.getColor(context, private val disabledColor = ContextCompat.getColor(
R.color.download) context,
private val errorColor = ContextCompat.getColor(context, R.color.material_on_surface_disabled
R.color.material_red_500) )
private val filledCircle = ContextCompat.getDrawable(context, private val downloadedColor = ContextCompat.getColor(
R.drawable.filled_circle)?.mutate() context,
private val borderCircle = ContextCompat.getDrawable(context, R.color.download
R.drawable.border_circle)?.mutate() )
private val downloadDrawable = ContextCompat.getDrawable(context, private val errorColor = ContextCompat.getColor(
R.drawable.ic_arrow_downward_24dp)?.mutate() context,
private val checkDrawable = ContextCompat.getDrawable(context, R.color.material_red_500
R.drawable.ic_check_24dp)?.mutate() )
private val filledCircle = ContextCompat.getDrawable(
context,
R.drawable.filled_circle
)?.mutate()
private val borderCircle = ContextCompat.getDrawable(
context,
R.drawable.border_circle
)?.mutate()
private val downloadDrawable = ContextCompat.getDrawable(
context,
R.drawable.ic_arrow_downward_24dp
)?.mutate()
private val checkDrawable = ContextCompat.getDrawable(
context,
R.drawable.ic_check_24dp
)?.mutate()
private var isAnimating = false private var isAnimating = false
private var iconAnimation: ObjectAnimator? = null private var iconAnimation: ObjectAnimator? = null
@ -42,8 +58,10 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut
download_icon.alpha = 1f download_icon.alpha = 1f
isAnimating = false isAnimating = false
} }
download_icon.setImageDrawable(if (state == Download.CHECKED) download_icon.setImageDrawable(
checkDrawable else downloadDrawable) if (state == Download.CHECKED)
checkDrawable else downloadDrawable
)
when (state) { when (state) {
Download.CHECKED -> { Download.CHECKED -> {
download_progress.gone() download_progress.gone()

View File

@ -54,8 +54,10 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
migration_menu.visibleIf(adapterPosition != 0 || adapterPosition != adapter.itemCount - 1) migration_menu.visibleIf(adapterPosition != 0 || adapterPosition != adapter.itemCount - 1)
migration_menu.setVectorCompat( migration_menu.setVectorCompat(
R.drawable.ic_more_vert_24dp, view.context R.drawable.ic_more_vert_24dp,
.getResourceColor(android.R.attr.textColorPrimary)) view.context
.getResourceColor(android.R.attr.textColorPrimary)
)
} }
/** /**

View File

@ -83,9 +83,11 @@ class ExtensionBottomPresenter(
val untrustedSorted = untrusted.sortedBy { it.pkgName } val untrustedSorted = untrusted.sortedBy { it.pkgName }
val availableSorted = available val availableSorted = available
// Filter out already installed extensions and disabled languages // Filter out already installed extensions and disabled languages
.filter { avail -> installed.none { it.pkgName == avail.pkgName } && .filter { avail ->
installed.none { it.pkgName == avail.pkgName } &&
untrusted.none { it.pkgName == avail.pkgName } && untrusted.none { it.pkgName == avail.pkgName } &&
(avail.lang in activeLangs || avail.lang == "all") } (avail.lang in activeLangs || avail.lang == "all")
}
.sortedBy { it.pkgName } .sortedBy { it.pkgName }
if (installedSorted.isNotEmpty() || untrustedSorted.isNotEmpty()) { if (installedSorted.isNotEmpty() || untrustedSorted.isNotEmpty()) {

View File

@ -88,11 +88,17 @@ ExtensionAdapter.OnButtonClickListener,
fun updateExtTitle() { fun updateExtTitle() {
val extCount = presenter.getExtensionUpdateCount() val extCount = presenter.getExtensionUpdateCount()
title_text.text = if (extCount == 0) context.getString(R.string.extensions) title_text.text = if (extCount == 0) context.getString(R.string.extensions)
else resources.getQuantityString(R.plurals.extension_updates_available, extCount, else resources.getQuantityString(
extCount) R.plurals.extension_updates_available,
extCount,
extCount
)
title_text.setTextColor(context.getResourceColor( title_text.setTextColor(
if (extCount == 0) R.attr.actionBarTintColor else R.attr.colorAccent)) context.getResourceColor(
if (extCount == 0) R.attr.actionBarTintColor else R.attr.colorAccent
)
)
} }
override fun onButtonClick(position: Int) { override fun onButtonClick(position: Int) {
@ -153,7 +159,8 @@ ExtensionAdapter.OnButtonClickListener,
adapter?.updateDataSet( adapter?.updateDataSet(
extensions.filter { extensions.filter {
it.extension.name.contains(controller.extQuery, ignoreCase = true) it.extension.name.contains(controller.extQuery, ignoreCase = true)
}) }
)
} else { } else {
adapter?.updateDataSet(extensions) adapter?.updateDataSet(extensions)
} }

View File

@ -44,9 +44,11 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
private var preferenceScreen: PreferenceScreen? = null private var preferenceScreen: PreferenceScreen? = null
constructor(pkgName: String) : this(Bundle().apply { constructor(pkgName: String) : this(
Bundle().apply {
putString(PKGNAME_KEY, pkgName) putString(PKGNAME_KEY, pkgName)
}) }
)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext()) val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
@ -107,8 +109,10 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
extension_prefs_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) extension_prefs_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
if (screen.preferenceCount == 0) { if (screen.preferenceCount == 0) {
extension_prefs_empty_view.show(R.drawable.ic_no_settings_24dp, extension_prefs_empty_view.show(
R.string.empty_preferences_for_extension) R.drawable.ic_no_settings_24dp,
R.string.empty_preferences_for_extension
)
} }
} }
@ -139,7 +143,8 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
source.preferences source.preferences
} else {*/ } else {*/
context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE) context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE)
/*}*/) /*}*/
)
if (source is ConfigurableSource) { if (source is ConfigurableSource) {
if (multiSource) { if (multiSource) {
@ -193,14 +198,19 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
} }
val f = when (preference) { val f = when (preference) {
is EditTextPreference -> EditTextPreferenceDialogController is EditTextPreference ->
EditTextPreferenceDialogController
.newInstance(preference.getKey()) .newInstance(preference.getKey())
is ListPreference -> ListPreferenceDialogController is ListPreference ->
ListPreferenceDialogController
.newInstance(preference.getKey()) .newInstance(preference.getKey())
is MultiSelectListPreference -> MultiSelectListPreferenceDialogController is MultiSelectListPreference ->
MultiSelectListPreferenceDialogController
.newInstance(preference.getKey()) .newInstance(preference.getKey())
else -> throw IllegalArgumentException("Tried to display dialog for unknown " + else -> throw IllegalArgumentException(
"preference type. Did you forget to override onDisplayPreferenceDialog()?") "Tried to display dialog for unknown " +
"preference type. Did you forget to override onDisplayPreferenceDialog()?"
)
} }
f.targetController = this f.targetController = this
f.showDialog(router) f.showDialog(router)

View File

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

View File

@ -63,13 +63,15 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
val extension = item.extension val extension = item.extension
val installStep = item.installStep val installStep = item.installStep
if (installStep != null) { if (installStep != null) {
setText(when (installStep) { setText(
when (installStep) {
InstallStep.Pending -> R.string.pending InstallStep.Pending -> R.string.pending
InstallStep.Downloading -> R.string.downloading InstallStep.Downloading -> R.string.downloading
InstallStep.Installing -> R.string.installing InstallStep.Installing -> R.string.installing
InstallStep.Installed -> R.string.installed InstallStep.Installed -> R.string.installed
InstallStep.Error -> R.string.retry InstallStep.Error -> R.string.retry
}) }
)
if (installStep != InstallStep.Error) { if (installStep != InstallStep.Error) {
isEnabled = false isEnabled = false
isClickable = false isClickable = false
@ -79,7 +81,8 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
extension.hasUpdate -> { extension.hasUpdate -> {
isActivated = true isActivated = true
backgroundTintList = ColorStateList.valueOf( backgroundTintList = ColorStateList.valueOf(
context.getResourceColor(R.attr.colorAccent)) context.getResourceColor(R.attr.colorAccent)
)
strokeColor = ColorStateList.valueOf(Color.TRANSPARENT) strokeColor = ColorStateList.valueOf(Color.TRANSPARENT)
setText(R.string.update) setText(R.string.update)
} }

View File

@ -10,10 +10,12 @@ class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
where T : ExtensionTrustDialog.Listener { where T : ExtensionTrustDialog.Listener {
lateinit var listener: Listener lateinit var listener: Listener
constructor(target: T, signatureHash: String, pkgName: String) : this(Bundle().apply { constructor(target: T, signatureHash: String, pkgName: String) : this(
Bundle().apply {
putString(SIGNATURE_KEY, signatureHash) putString(SIGNATURE_KEY, signatureHash)
putString(PKGNAME_KEY, pkgName) putString(PKGNAME_KEY, pkgName)
}) { }
) {
listener = target listener = target
} }

View File

@ -45,7 +45,8 @@ class DisplayBottomSheet(private val controller: LibraryController) : BottomShee
val height = activity.window.decorView.rootWindowInsets.systemWindowInsetBottom val height = activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
sheetBehavior.peekHeight = 220.dpToPx + height sheetBehavior.peekHeight = 220.dpToPx + height
sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { sheetBehavior.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) { } override fun onSlide(bottomSheet: View, progress: Float) { }
override fun onStateChanged(p0: View, state: Int) { override fun onStateChanged(p0: View, state: Int) {
@ -53,7 +54,8 @@ class DisplayBottomSheet(private val controller: LibraryController) : BottomShee
sheetBehavior.skipCollapsed = true sheetBehavior.skipCollapsed = true
} }
} }
}) }
)
} }
override fun onStart() { override fun onStart() {

View File

@ -59,7 +59,8 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
fun indexOf(categoryOrder: Int): Int { fun indexOf(categoryOrder: Int): Int {
return currentItems.indexOfFirst { return currentItems.indexOfFirst {
if (it is LibraryHeaderItem) it.category.order == categoryOrder if (it is LibraryHeaderItem) it.category.order == categoryOrder
else false } else false
}
} }
/** /**
@ -70,13 +71,15 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
fun indexOf(manga: Manga): Int { fun indexOf(manga: Manga): Int {
return currentItems.indexOfFirst { return currentItems.indexOfFirst {
if (it is LibraryItem) it.manga.id == manga.id if (it is LibraryItem) it.manga.id == manga.id
else false } else false
}
} }
fun getHeaderPositions(): List<Int> { fun getHeaderPositions(): List<Int> {
return currentItems.mapIndexedNotNull { index, it -> return currentItems.mapIndexedNotNull { index, it ->
if (it is LibraryHeaderItem) index if (it is LibraryHeaderItem) index
else null } else null
}
} }
/** /**
@ -87,7 +90,8 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
fun allIndexOf(manga: Manga): List<Int> { fun allIndexOf(manga: Manga): List<Int> {
return currentItems.mapIndexedNotNull { index, it -> return currentItems.mapIndexedNotNull { index, it ->
if (it is LibraryItem && it.manga.id == manga.id) index if (it is LibraryItem && it.manga.id == manga.id) index
else null } else null
}
} }
fun performFilter() { fun performFilter() {
@ -140,7 +144,8 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
val last = history.maxBy { it.last_read } val last = history.maxBy { it.last_read }
if (last != null && last.last_read > 100) { if (last != null && last.last_read > 100) {
recyclerView.context.getString( recyclerView.context.getString(
R.string.read_, last.last_read.timeSpanFromNow R.string.read_,
last.last_read.timeSpanFromNow
) )
} else { } else {
"N/A" "N/A"
@ -154,7 +159,9 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
LibrarySort.TOTAL -> { LibrarySort.TOTAL -> {
val total = item.manga.totalChapters val total = item.manga.totalChapters
if (total > 0) recyclerView.resources.getQuantityString( if (total > 0) recyclerView.resources.getQuantityString(
R.plurals.chapters, total, total R.plurals.chapters,
total,
total
) )
else { else {
"N/A" "N/A"
@ -164,7 +171,8 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
val lastUpdate = item.manga.last_update val lastUpdate = item.manga.last_update
if (lastUpdate > 0) { if (lastUpdate > 0) {
recyclerView.context.getString( recyclerView.context.getString(
R.string.updated_, lastUpdate.timeSpanFromNow R.string.updated_,
lastUpdate.timeSpanFromNow
) )
} else { } else {
"N/A" "N/A"

View File

@ -108,10 +108,13 @@ class LibraryController(
) : BaseController(bundle), ) : BaseController(bundle),
ActionMode.Callback, ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener, ChangeMangaCategoriesDialog.Listener,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemMoveListener, LibraryCategoryAdapter.LibraryListener, FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.OnItemMoveListener,
LibraryCategoryAdapter.LibraryListener,
BottomSheetController, BottomSheetController,
RootSearchInterface, LibraryServiceListener { RootSearchInterface,
LibraryServiceListener {
init { init {
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -325,7 +328,8 @@ class LibraryController(
adapter = LibraryCategoryAdapter(this) adapter = LibraryCategoryAdapter(this)
setRecyclerLayout() setRecyclerLayout()
recycler.manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() { recycler.manager.spanSizeLookup = (
object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
if (libraryLayout == 0) return 1 if (libraryLayout == 0) return 1
val item = this@LibraryController.adapter.getItem(position) val item = this@LibraryController.adapter.getItem(position)
@ -335,7 +339,8 @@ class LibraryController(
1 1
} }
} }
}) }
)
recycler.setHasFixedSize(true) recycler.setHasFixedSize(true)
recycler.adapter = adapter recycler.adapter = adapter
@ -360,7 +365,10 @@ class LibraryController(
setUpHopper() setUpHopper()
elevateAppBar = elevateAppBar =
scrollViewWith(recycler, swipeRefreshLayout = swipe_refresh, afterInsets = { insets -> scrollViewWith(
recycler,
swipeRefreshLayout = swipe_refresh,
afterInsets = { insets ->
category_recycler?.updateLayoutParams<ViewGroup.MarginLayoutParams> { category_recycler?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = recycler?.paddingTop ?: 0 topMargin = recycler?.paddingTop ?: 0
} }
@ -368,9 +376,11 @@ class LibraryController(
topMargin = recycler?.paddingTop ?: 0 topMargin = recycler?.paddingTop ?: 0
} }
header_title?.updatePaddingRelative(top = insets.systemWindowInsetTop + 2.dpToPx) header_title?.updatePaddingRelative(top = insets.systemWindowInsetTop + 2.dpToPx)
}, onLeavingController = { },
onLeavingController = {
header_title?.gone() header_title?.gone()
}) }
)
swipe_refresh.setOnRefreshListener { swipe_refresh.setOnRefreshListener {
swipe_refresh.isRefreshing = false swipe_refresh.isRefreshing = false
@ -387,19 +397,24 @@ class LibraryController(
preferences.updateOnRefresh().getOrDefault() == -1 -> { preferences.updateOnRefresh().getOrDefault() == -1 -> {
MaterialDialog(activity!!).title(R.string.what_should_update) MaterialDialog(activity!!).title(R.string.what_should_update)
.negativeButton(android.R.string.cancel) .negativeButton(android.R.string.cancel)
.listItemsSingleChoice(items = listOf( .listItemsSingleChoice(
items = listOf(
view.context.getString(
R.string.top_category,
presenter.allCategories.first().name
),
view.context.getString( view.context.getString(
R.string.top_category, presenter.allCategories.first().name
), view.context.getString(
R.string.categories_in_global_update R.string.categories_in_global_update
) )
), selection = { _, index, _ -> ),
selection = { _, index, _ ->
preferences.updateOnRefresh().set(index) preferences.updateOnRefresh().set(index)
when (index) { when (index) {
0 -> updateLibrary(presenter.allCategories.first()) 0 -> updateLibrary(presenter.allCategories.first())
else -> updateLibrary() else -> updateLibrary()
} }
}).positiveButton(R.string.update).show() }
).positiveButton(R.string.update).show()
} }
else -> { else -> {
when (preferences.updateOnRefresh().getOrDefault()) { when (preferences.updateOnRefresh().getOrDefault()) {
@ -632,7 +647,8 @@ class LibraryController(
if (libraryLayout == 0) { if (libraryLayout == 0) {
recycler.spanCount = 1 recycler.spanCount = 1
recycler.updatePaddingRelative( recycler.updatePaddingRelative(
start = 0, end = 0 start = 0,
end = 0
) )
} else { } else {
recycler.columnWidth = when (preferences.gridSize().getOrDefault()) { recycler.columnWidth = when (preferences.gridSize().getOrDefault()) {
@ -733,7 +749,8 @@ class LibraryController(
category_hopper_frame.visibleIf(!singleCategory && !preferences.hideHopper().get()) category_hopper_frame.visibleIf(!singleCategory && !preferences.hideHopper().get())
filter_bottom_sheet.updateButtons( filter_bottom_sheet.updateButtons(
showExpand = !singleCategory && presenter.showAllCategories, groupType = presenter.groupType showExpand = !singleCategory && presenter.showAllCategories,
groupType = presenter.groupType
) )
adapter.isLongPressDragEnabled = canDrag() adapter.isLongPressDragEnabled = canDrag()
category_recycler.setCategories(presenter.categories) category_recycler.setCategories(presenter.categories)
@ -783,9 +800,11 @@ class LibraryController(
) )
animatorSet.playSequentially(animations) animatorSet.playSequentially(animations)
animatorSet.startDelay = 1250 animatorSet.startDelay = 1250
animatorSet.addListener(EndAnimatorListener { animatorSet.addListener(
EndAnimatorListener {
isAnimatingHopper = false isAnimatingHopper = false
}) }
)
animatorSet.start() animatorSet.start()
} }
@ -836,16 +855,21 @@ class LibraryController(
if (headerPosition > -1) { if (headerPosition > -1) {
val appbar = activity?.appbar val appbar = activity?.appbar
recycler.suppressLayout(true) recycler.suppressLayout(true)
val appbarOffset = if (appbar?.y ?: 0f > -20) 0 else (appbar?.y?.plus( val appbarOffset = if (appbar?.y ?: 0f > -20) 0 else (
appbar?.y?.plus(
view?.rootWindowInsets?.systemWindowInsetTop ?: 0 view?.rootWindowInsets?.systemWindowInsetTop ?: 0
) ?: 0f).roundToInt() + 30.dpToPx ) ?: 0f
).roundToInt() + 30.dpToPx
val previousHeader = adapter.getItem(adapter.indexOf(pos - 1)) as? LibraryHeaderItem val previousHeader = adapter.getItem(adapter.indexOf(pos - 1)) as? LibraryHeaderItem
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( (recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
headerPosition, (when { headerPosition,
(
when {
headerPosition == 0 -> 0 headerPosition == 0 -> 0
previousHeader?.category?.isHidden == true -> (-3).dpToPx previousHeader?.category?.isHidden == true -> (-3).dpToPx
else -> (-30).dpToPx else -> (-30).dpToPx
}) + appbarOffset }
) + appbarOffset
) )
(adapter.getItem(headerPosition) as? LibraryHeaderItem)?.category?.let { (adapter.getItem(headerPosition) as? LibraryHeaderItem)?.category?.let {
saveActiveCategory(it) saveActiveCategory(it)
@ -1033,9 +1057,9 @@ class LibraryController(
val position = viewHolder?.adapterPosition ?: return val position = viewHolder?.adapterPosition ?: return
swipe_refresh.isEnabled = actionState != ItemTouchHelper.ACTION_STATE_DRAG swipe_refresh.isEnabled = actionState != ItemTouchHelper.ACTION_STATE_DRAG
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
if (lastItemPosition != null && position != lastItemPosition && lastItem == adapter.getItem( if (lastItemPosition != null &&
position position != lastItemPosition &&
) lastItem == adapter.getItem(position)
) { ) {
// because for whatever reason you can repeatedly tap on a currently dragging manga // because for whatever reason you can repeatedly tap on a currently dragging manga
adapter.removeSelection(position) adapter.removeSelection(position)
@ -1063,10 +1087,11 @@ class LibraryController(
override fun onItemMove(fromPosition: Int, toPosition: Int) { override fun onItemMove(fromPosition: Int, toPosition: Int) {
// Because padding a recycler causes it to scroll up we have to scroll it back down... wild // Because padding a recycler causes it to scroll up we have to scroll it back down... wild
if ((adapter.getItem(fromPosition) is LibraryItem && adapter.getItem(fromPosition) is if ((
LibraryItem) || adapter.getItem( adapter.getItem(fromPosition) is LibraryItem &&
fromPosition adapter.getItem(fromPosition) is LibraryItem
) == null ) ||
adapter.getItem(fromPosition) == null
) { ) {
recycler.scrollBy(0, recycler.paddingTop) recycler.scrollBy(0, recycler.paddingTop)
} }
@ -1079,9 +1104,12 @@ class LibraryController(
val item = adapter.getItem(fromPosition) as? LibraryItem ?: return false val item = adapter.getItem(fromPosition) as? LibraryItem ?: return false
val newHeader = adapter.getSectionHeader(toPosition) as? LibraryHeaderItem val newHeader = adapter.getSectionHeader(toPosition) as? LibraryHeaderItem
if (toPosition < 1) return false if (toPosition < 1) return false
return (adapter.getItem(toPosition) !is LibraryHeaderItem) && (newHeader?.category?.id == item.manga.category || !presenter.mangaIsInCategory( return (adapter.getItem(toPosition) !is LibraryHeaderItem) && (
item.manga, newHeader?.category?.id newHeader?.category?.id == item.manga.category || !presenter.mangaIsInCategory(
)) item.manga,
newHeader?.category?.id
)
)
} }
override fun onItemReleased(position: Int) { override fun onItemReleased(position: Int) {
@ -1110,7 +1138,9 @@ class LibraryController(
return return
} }
if (newHeader?.category != null) moveMangaToCategory( if (newHeader?.category != null) moveMangaToCategory(
item.manga, newHeader.category, mangaIds item.manga,
newHeader.category,
mangaIds
) )
} }
lastItemPosition = null lastItemPosition = null
@ -1147,8 +1177,10 @@ class LibraryController(
inQueue -> R.string._already_in_queue inQueue -> R.string._already_in_queue
LibraryUpdateService.isRunning() -> R.string.adding_category_to_queue LibraryUpdateService.isRunning() -> R.string.adding_category_to_queue
else -> R.string.updating_ else -> R.string.updating_
}, category.name },
), Snackbar.LENGTH_LONG category.name
),
Snackbar.LENGTH_LONG
) { ) {
anchorView = anchorView() anchorView = anchorView()
view.elevation = 15f.dpToPx view.elevation = 15f.dpToPx
@ -1163,7 +1195,9 @@ class LibraryController(
} }
} }
if (!inQueue) LibraryUpdateService.start( if (!inQueue) LibraryUpdateService.start(
view!!.context, category, mangaToUse = if (category.isDynamic) { view!!.context,
category,
mangaToUse = if (category.isDynamic) {
presenter.getMangaInCategories(category.id) presenter.getMangaInCategories(category.id)
} else null } else null
) )
@ -1327,9 +1361,11 @@ class LibraryController(
} }
R.id.action_migrate -> { R.id.action_migrate -> {
val skipPre = preferences.skipPreMigration().getOrDefault() val skipPre = preferences.skipPreMigration().getOrDefault()
PreMigrationController.navigateToMigration(skipPre, PreMigrationController.navigateToMigration(
skipPre,
router, router,
selectedMangas.filter { it.id != LocalSource.ID }.mapNotNull { it.id }) selectedMangas.filter { it.id != LocalSource.ID }.mapNotNull { it.id }
)
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
} }
else -> return false else -> return false
@ -1356,7 +1392,8 @@ class LibraryController(
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
snack?.dismiss() snack?.dismiss()
snack = view?.snack( snack = view?.snack(
activity?.getString(R.string.removed_from_library) ?: "", Snackbar.LENGTH_INDEFINITE activity?.getString(R.string.removed_from_library) ?: "",
Snackbar.LENGTH_INDEFINITE
) { ) {
anchorView = anchorView() anchorView = anchorView()
view.elevation = 15f.dpToPx view.elevation = 15f.dpToPx
@ -1365,12 +1402,14 @@ class LibraryController(
presenter.reAddMangas(mangas) presenter.reAddMangas(mangas)
undoing = true undoing = true
} }
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { addCallback(
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event) super.onDismissed(transientBottomBar, event)
if (!undoing) presenter.confirmDeletion(mangas) if (!undoing) presenter.confirmDeletion(mangas)
} }
}) }
)
} }
(activity as? MainActivity)?.setUndoSnackBar(snack) (activity as? MainActivity)?.setUndoSnackBar(snack)
} }

View File

@ -26,38 +26,45 @@ class LibraryGestureDetector(private val controller: LibraryController) : Gestur
var result = false var result = false
val diffY = e2.y - e1.y val diffY = e2.y - e1.y
val diffX = e2.x - e1.x val diffX = e2.x - e1.x
if (abs(diffX) <= abs(diffY) && abs(diffY) > MainActivity.SWIPE_THRESHOLD && abs(velocityY) > MainActivity.SWIPE_VELOCITY_THRESHOLD) { if (abs(diffX) <= abs(diffY) &&
abs(diffY) > MainActivity.SWIPE_THRESHOLD &&
abs(velocityY) > MainActivity.SWIPE_VELOCITY_THRESHOLD
) {
if (diffY <= 0) { if (diffY <= 0) {
controller.showSheet() controller.showSheet()
} else { } else {
controller.filter_bottom_sheet.sheetBehavior?.hide() controller.filter_bottom_sheet.sheetBehavior?.hide()
} }
result = true result = true
} else if (abs(diffX) >= abs(diffY) && abs(diffX) > MainActivity.SWIPE_THRESHOLD * 3 && abs( } else if (abs(diffX) >= abs(diffY) &&
velocityX abs(diffX) > MainActivity.SWIPE_THRESHOLD * 3 &&
) > MainActivity.SWIPE_VELOCITY_THRESHOLD abs(velocityX) > MainActivity.SWIPE_VELOCITY_THRESHOLD
) { ) {
if (diffX <= 0) { if (diffX <= 0) {
controller.category_hopper_frame.updateLayoutParams<CoordinatorLayout.LayoutParams> { controller.category_hopper_frame.updateLayoutParams<CoordinatorLayout.LayoutParams> {
anchorGravity = anchorGravity =
Gravity.TOP or (if (anchorGravity == Gravity.TOP or Gravity.RIGHT) { Gravity.TOP or (
if (anchorGravity == Gravity.TOP or Gravity.RIGHT) {
controller.preferences.hopperGravity().set(1) controller.preferences.hopperGravity().set(1)
Gravity.CENTER Gravity.CENTER
} else { } else {
controller.preferences.hopperGravity().set(0) controller.preferences.hopperGravity().set(0)
Gravity.LEFT Gravity.LEFT
}) }
)
} }
} else { } else {
controller.category_hopper_frame.updateLayoutParams<CoordinatorLayout.LayoutParams> { controller.category_hopper_frame.updateLayoutParams<CoordinatorLayout.LayoutParams> {
anchorGravity = anchorGravity =
Gravity.TOP or Gravity.TOP or (if (anchorGravity == Gravity.TOP or Gravity.LEFT) { Gravity.TOP or Gravity.TOP or (
if (anchorGravity == Gravity.TOP or Gravity.LEFT) {
controller.preferences.hopperGravity().set(1) controller.preferences.hopperGravity().set(1)
Gravity.CENTER Gravity.CENTER
} else { } else {
controller.preferences.hopperGravity().set(2) controller.preferences.hopperGravity().set(2)
Gravity.RIGHT Gravity.RIGHT
}) }
)
} }
} }
if (!controller.hasMovedHopper) { if (!controller.hasMovedHopper) {

View File

@ -79,11 +79,13 @@ class LibraryHeaderHolder(val view: View, private val adapter: LibraryCategoryAd
} }
val shorterMargin = adapter.headerItems.firstOrNull() == item val shorterMargin = adapter.headerItems.firstOrNull() == item
sectionText.updateLayoutParams<ConstraintLayout.LayoutParams> { sectionText.updateLayoutParams<ConstraintLayout.LayoutParams> {
topMargin = (when { topMargin = (
when {
shorterMargin -> 2 shorterMargin -> 2
previousIsCollapsed -> 5 previousIsCollapsed -> 5
else -> 32 else -> 32
}).dpToPx }
).dpToPx
} }
val category = item.category val category = item.category
@ -117,7 +119,8 @@ class LibraryHeaderHolder(val view: View, private val adapter: LibraryCategoryAd
sortText.setText(category.sortRes()) sortText.setText(category.sortRes())
expandImage.setImageResource( expandImage.setImageResource(
if (category.isHidden) R.drawable.ic_expand_more_24dp if (category.isHidden) R.drawable.ic_expand_more_24dp
else R.drawable.ic_expand_less_24dp) else R.drawable.ic_expand_less_24dp
)
when { when {
adapter.mode == SelectableAdapter.Mode.MULTI -> { adapter.mode == SelectableAdapter.Mode.MULTI -> {
checkboxImage.visibleIf(!category.isHidden) checkboxImage.visibleIf(!category.isHidden)
@ -160,22 +163,31 @@ class LibraryHeaderHolder(val view: View, private val adapter: LibraryCategoryAd
adapter.controller.activity?.let { activity -> adapter.controller.activity?.let { activity ->
val items = mutableListOf( val items = mutableListOf(
MaterialMenuSheet.MenuSheetItem( MaterialMenuSheet.MenuSheetItem(
LibrarySort.ALPHA, R.drawable.ic_sort_by_alpha_24dp, R.string.title LibrarySort.ALPHA,
), MaterialMenuSheet.MenuSheetItem( R.drawable.ic_sort_by_alpha_24dp,
R.string.title
),
MaterialMenuSheet.MenuSheetItem(
LibrarySort.LAST_READ, LibrarySort.LAST_READ,
R.drawable.ic_recent_read_outline_24dp, R.drawable.ic_recent_read_outline_24dp,
R.string.last_read R.string.last_read
), MaterialMenuSheet.MenuSheetItem( ),
MaterialMenuSheet.MenuSheetItem(
LibrarySort.LATEST_CHAPTER, LibrarySort.LATEST_CHAPTER,
R.drawable.ic_new_releases_24dp, R.drawable.ic_new_releases_24dp,
R.string.latest_chapter R.string.latest_chapter
), MaterialMenuSheet.MenuSheetItem( ),
LibrarySort.UNREAD, R.drawable.ic_eye_24dp, R.string.unread MaterialMenuSheet.MenuSheetItem(
), MaterialMenuSheet.MenuSheetItem( LibrarySort.UNREAD,
R.drawable.ic_eye_24dp,
R.string.unread
),
MaterialMenuSheet.MenuSheetItem(
LibrarySort.TOTAL, LibrarySort.TOTAL,
R.drawable.ic_sort_by_numeric_24dp, R.drawable.ic_sort_by_numeric_24dp,
R.string.total_chapters R.string.total_chapters
), MaterialMenuSheet.MenuSheetItem( ),
MaterialMenuSheet.MenuSheetItem(
LibrarySort.DATE_ADDED, LibrarySort.DATE_ADDED,
R.drawable.ic_heart_outline_24dp, R.drawable.ic_heart_outline_24dp,
R.string.date_added R.string.date_added
@ -192,7 +204,10 @@ class LibraryHeaderHolder(val view: View, private val adapter: LibraryCategoryAd
} }
val sortingMode = category.sortingMode() val sortingMode = category.sortingMode()
val sheet = MaterialMenuSheet( val sheet = MaterialMenuSheet(
activity, items, activity.getString(R.string.sort_by), sortingMode activity,
items,
activity.getString(R.string.sort_by),
sortingMode
) { sheet, item -> ) { sheet, item ->
onCatSortClicked(category, item) onCatSortClicked(category, item)
val nCategory = (adapter.getItem(adapterPosition) as? LibraryHeaderItem)?.category val nCategory = (adapter.getItem(adapterPosition) as? LibraryHeaderItem)?.category
@ -212,7 +227,8 @@ class LibraryHeaderHolder(val view: View, private val adapter: LibraryCategoryAd
sortingMode == LibrarySort.DRAG_AND_DROP -> R.drawable.ic_check_24dp sortingMode == LibrarySort.DRAG_AND_DROP -> R.drawable.ic_check_24dp
if (sortingMode == LibrarySort.DATE_ADDED || if (sortingMode == LibrarySort.DATE_ADDED ||
sortingMode == LibrarySort.LATEST_CHAPTER || sortingMode == LibrarySort.LATEST_CHAPTER ||
sortingMode == LibrarySort.LAST_READ) !isAscending else isAscending -> sortingMode == LibrarySort.LAST_READ
) !isAscending else isAscending ->
R.drawable.ic_arrow_downward_24dp R.drawable.ic_arrow_downward_24dp
else -> R.drawable.ic_arrow_upward_24dp else -> R.drawable.ic_arrow_upward_24dp
} }
@ -257,7 +273,8 @@ class LibraryHeaderHolder(val view: View, private val adapter: LibraryCategoryAd
val tintedDrawable = drawable?.mutate() val tintedDrawable = drawable?.mutate()
tintedDrawable?.setTint( tintedDrawable?.setTint(
ContextCompat.getColor( ContextCompat.getColor(
contentView.context, if (allSelected) R.color.colorAccent contentView.context,
if (allSelected) R.color.colorAccent
else R.color.gray_button else R.color.gray_button
) )
) )

View File

@ -39,7 +39,8 @@ abstract class LibraryHolder(
item.manga.source == LocalSource.ID -> -2 item.manga.source == LocalSource.ID -> -2
else -> item.downloadCount else -> item.downloadCount
}, },
showTotal) showTotal
)
} }
fun setReadingButton(item: LibraryItem) { fun setReadingButton(item: LibraryItem) {

View File

@ -66,18 +66,21 @@ class LibraryItem(
gradient.layoutParams = FrameLayout.LayoutParams( gradient.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT,
(coverHeight * 0.66f).toInt(), (coverHeight * 0.66f).toInt(),
Gravity.BOTTOM) Gravity.BOTTOM
)
card.updateLayoutParams<ConstraintLayout.LayoutParams> { card.updateLayoutParams<ConstraintLayout.LayoutParams> {
bottomMargin = 6.dpToPx bottomMargin = 6.dpToPx
} }
} else if (libraryLayout == 2) { } else if (libraryLayout == 2) {
constraint_layout.background = ContextCompat.getDrawable( constraint_layout.background = ContextCompat.getDrawable(
context, R.drawable.library_item_selector context,
R.drawable.library_item_selector
) )
} }
if (isFixedSize) { if (isFixedSize) {
constraint_layout.layoutParams = FrameLayout.LayoutParams( constraint_layout.layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
) )
cover_thumbnail.maxHeight = Int.MAX_VALUE cover_thumbnail.maxHeight = Int.MAX_VALUE
cover_thumbnail.minimumHeight = 0 cover_thumbnail.minimumHeight = 0
@ -162,7 +165,8 @@ class LibraryItem(
} == null } == null
else else
genres?.find { genres?.find {
it.trim().toLowerCase() == tag.toLowerCase() } != null it.trim().toLowerCase() == tag.toLowerCase()
} != null
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -129,7 +129,8 @@ class LibraryPresenter(
private fun blankItem(id: Int = currentCategory): List<LibraryItem> { private fun blankItem(id: Int = currentCategory): List<LibraryItem> {
return listOf( return listOf(
LibraryItem( LibraryItem(
LibraryManga.createBlank(id), LibraryHeaderItem({ getCategory(id) }, id) LibraryManga.createBlank(id),
LibraryHeaderItem({ getCategory(id) }, id)
) )
) )
} }
@ -146,7 +147,8 @@ class LibraryPresenter(
view.onNextLibraryUpdate( view.onNextLibraryUpdate(
if (!show) sectionedLibraryItems[currentCategory] if (!show) sectionedLibraryItems[currentCategory]
?: sectionedLibraryItems[categories.first().id] ?: blankItem() ?: sectionedLibraryItems[categories.first().id] ?: blankItem()
else libraryItems, true else libraryItems,
true
) )
} }
@ -169,7 +171,8 @@ class LibraryPresenter(
view.onNextLibraryUpdate( view.onNextLibraryUpdate(
if (!showAll) sectionedLibraryItems[currentCategory] if (!showAll) sectionedLibraryItems[currentCategory]
?: sectionedLibraryItems[categories.first().id] ?: blankItem() ?: sectionedLibraryItems[categories.first().id] ?: blankItem()
else libraryItems, freshStart else libraryItems,
freshStart
) )
} }
} }
@ -454,15 +457,19 @@ class LibraryPresenter(
) )
val catItemAll = LibraryHeaderItem({ categoryAll }, -1) val catItemAll = LibraryHeaderItem({ categoryAll }, -1)
val categorySet = mutableSetOf<Int>() val categorySet = mutableSetOf<Int>()
val headerItems = (categories.mapNotNull { category -> val headerItems = (
categories.mapNotNull { category ->
val id = category.id val id = category.id
if (id == null) null if (id == null) null
else id to LibraryHeaderItem({ getCategory(id) }, id) else id to LibraryHeaderItem({ getCategory(id) }, id)
} + (-1 to catItemAll) + (0 to LibraryHeaderItem({ getCategory(0) }, 0))).toMap() } + (-1 to catItemAll) + (0 to LibraryHeaderItem({ getCategory(0) }, 0))
).toMap()
val items = libraryManga.mapNotNull { val items = libraryManga.mapNotNull {
val headerItem = (if (!libraryIsGrouped) catItemAll val headerItem = (
else headerItems[it.category]) ?: return@mapNotNull null if (!libraryIsGrouped) catItemAll
else headerItems[it.category]
) ?: return@mapNotNull null
categorySet.add(it.category) categorySet.add(it.category)
LibraryItem(it, headerItem) LibraryItem(it, headerItem)
}.toMutableList() }.toMutableList()
@ -475,8 +482,11 @@ class LibraryPresenter(
if (libraryIsGrouped) { if (libraryIsGrouped) {
categories.forEach { category -> categories.forEach { category ->
val catId = category.id ?: return@forEach val catId = category.id ?: return@forEach
if (catId > 0 && !categorySet.contains(catId) && (catId !in categoriesHidden || if (catId > 0 && !categorySet.contains(catId) && (
!showAll)) { catId !in categoriesHidden ||
!showAll
)
) {
val headerItem = headerItems[catId] val headerItem = headerItems[catId]
if (headerItem != null) items.add( if (headerItem != null) items.add(
LibraryItem(LibraryManga.createBlank(catId), headerItem) LibraryItem(LibraryManga.createBlank(catId), headerItem)
@ -592,18 +602,21 @@ class LibraryPresenter(
mapTrackingOrder(it.name) mapTrackingOrder(it.name)
} else { } else {
it.name it.name
} } }
}
headers.forEachIndexed { index, category -> category.order = index } headers.forEachIndexed { index, category -> category.order = index }
return items to headers return items to headers
} }
private fun mapStatus(status: Int): String { private fun mapStatus(status: Int): String {
return context.getString(when (status) { return context.getString(
when (status) {
SManga.LICENSED -> R.string.licensed SManga.LICENSED -> R.string.licensed
SManga.ONGOING -> R.string.ongoing SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed SManga.COMPLETED -> R.string.completed
else -> R.string.unknown else -> R.string.unknown
}) }
)
} }
private fun mapTrackingOrder(status: String): String { private fun mapTrackingOrder(status: String): String {

View File

@ -33,7 +33,8 @@ class CategoryRecyclerView @JvmOverloads constructor(
fun setCategories(items: List<Category>) { fun setCategories(items: List<Category>) {
itemAdapter.set(items.map(::CategoryItem)) itemAdapter.set(items.map(::CategoryItem))
fastAdapter.onBindViewHolderListener = fastAdapter.onBindViewHolderListener =
(object : OnBindViewHolderListenerImpl<CategoryItem>() { (
object : OnBindViewHolderListenerImpl<CategoryItem>() {
override fun onBindViewHolder( override fun onBindViewHolder(
viewHolder: ViewHolder, viewHolder: ViewHolder,
position: Int, position: Int,
@ -43,7 +44,8 @@ class CategoryRecyclerView @JvmOverloads constructor(
(viewHolder as? CategoryItem.ViewHolder)?.categoryTitle?.isSelected = (viewHolder as? CategoryItem.ViewHolder)?.categoryTitle?.isSelected =
selectedCategory == position selectedCategory == position
} }
}) }
)
fastAdapter.onClickListener = { _, _, item, _ -> fastAdapter.onClickListener = { _, _, item, _ ->
if (item.category.id != -1) if (item.category.id != -1)
onCategoryClicked(item.category.order) onCategoryClicked(item.category.order)

View File

@ -104,7 +104,8 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
pager = controller.recycler pager = controller.recycler
val shadow2: View = controller.shadow2 val shadow2: View = controller.shadow2
val shadow: View = controller.shadow val shadow: View = controller.shadow
sheetBehavior?.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { sheetBehavior?.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) { override fun onSlide(bottomSheet: View, progress: Float) {
pill.alpha = (1 - max(0f, progress)) * 0.25f pill.alpha = (1 - max(0f, progress)) * 0.25f
shadow2.alpha = (1 - max(0f, progress)) * 0.25f shadow2.alpha = (1 - max(0f, progress)) * 0.25f
@ -115,7 +116,8 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
override fun onStateChanged(p0: View, state: Int) { override fun onStateChanged(p0: View, state: Int) {
stateChanged(state) stateChanged(state)
} }
}) }
)
if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
second_layout.removeView(view_options) second_layout.removeView(view_options)
@ -284,13 +286,15 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
unreadProgress.state = unreadP - 3 unreadProgress.state = unreadP - 3
} }
tracked?.setState(preferences.filterTracked()) tracked?.setState(preferences.filterTracked())
mangaType?.setState(when (preferences.filterMangaType().getOrDefault()) { mangaType?.setState(
when (preferences.filterMangaType().getOrDefault()) {
Manga.TYPE_MANGA -> context.getString(R.string.manga) Manga.TYPE_MANGA -> context.getString(R.string.manga)
Manga.TYPE_MANHUA -> context.getString(R.string.manhua) Manga.TYPE_MANHUA -> context.getString(R.string.manhua)
Manga.TYPE_MANHWA -> context.getString(R.string.manhwa) Manga.TYPE_MANHWA -> context.getString(R.string.manhwa)
Manga.TYPE_COMIC -> context.getString(R.string.comic) Manga.TYPE_COMIC -> context.getString(R.string.comic)
else -> "" else -> ""
}) }
)
reorderFilters() reorderFilters()
reSortViews() reSortViews()
} }
@ -361,8 +365,11 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
val recycler = RecyclerView(context) val recycler = RecyclerView(context)
if (filterOrder.count() != 6) if (filterOrder.count() != 6)
filterOrder = "urdcmt" filterOrder = "urdcmt"
val adapter = FlexibleAdapter(filterOrder.toCharArray().map(::ManageFilterItem), val adapter = FlexibleAdapter(
this, true) filterOrder.toCharArray().map(::ManageFilterItem),
this,
true
)
recycler.layoutManager = LinearLayoutManager(context) recycler.layoutManager = LinearLayoutManager(context)
recycler.adapter = adapter recycler.adapter = adapter
adapter.isHandleDragEnabled = true adapter.isHandleDragEnabled = true

View File

@ -83,19 +83,23 @@ class FilterTagGroup@JvmOverloads constructor(context: Context, attrs: Attribute
private fun toggleButton(index: Int, callBack: Boolean = true) { private fun toggleButton(index: Int, callBack: Boolean = true) {
if (index < 0 || itemCount == 0 || if (index < 0 || itemCount == 0 ||
(isActivated && index != buttons.indexOfFirst { it.isActivated })) (isActivated && index != buttons.indexOfFirst { it.isActivated })
)
return return
if (callBack) { if (callBack) {
val transition = androidx.transition.AutoTransition() val transition = androidx.transition.AutoTransition()
transition.duration = 150 transition.duration = 150
androidx.transition.TransitionManager.beginDelayedTransition( androidx.transition.TransitionManager.beginDelayedTransition(
parent.parent as ViewGroup, transition parent.parent as ViewGroup,
transition
) )
} }
if (itemCount == 1) { if (itemCount == 1) {
firstButton.isActivated = !firstButton.isActivated firstButton.isActivated = !firstButton.isActivated
firstButton.setTextColor(if (firstButton.isActivated) Color.WHITE else context firstButton.setTextColor(
.getResourceColor(android.R.attr.textColorPrimary)) if (firstButton.isActivated) Color.WHITE else context
.getResourceColor(android.R.attr.textColorPrimary)
)
listener?.onFilterClicked(this, if (firstButton.isActivated) index else -1, callBack) listener?.onFilterClicked(this, if (firstButton.isActivated) index else -1, callBack)
return return
} }
@ -118,8 +122,10 @@ class FilterTagGroup@JvmOverloads constructor(context: Context, attrs: Attribute
buttons.forEach { if (it != mainButton) it.gone() } buttons.forEach { if (it != mainButton) it.gone() }
separators.forEach { it.gone() } separators.forEach { it.gone() }
} }
mainButton.setTextColor(if (mainButton.isActivated) Color.WHITE else context mainButton.setTextColor(
.getResourceColor(android.R.attr.textColorPrimary)) if (mainButton.isActivated) Color.WHITE else context
.getResourceColor(android.R.attr.textColorPrimary)
)
} }
} }

View File

@ -144,10 +144,12 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
drawerArrow = DrawerArrowDrawable(this) drawerArrow = DrawerArrowDrawable(this)
drawerArrow?.color = getResourceColor(R.attr.actionBarTintColor) drawerArrow?.color = getResourceColor(R.attr.actionBarTintColor)
searchDrawable = ContextCompat.getDrawable( searchDrawable = ContextCompat.getDrawable(
this, R.drawable.ic_search_24dp this,
R.drawable.ic_search_24dp
) )
dismissDrawable = ContextCompat.getDrawable( dismissDrawable = ContextCompat.getDrawable(
this, R.drawable.ic_close_24dp this,
R.drawable.ic_close_24dp
) )
var continueSwitchingTabs = false var continueSwitchingTabs = false
@ -182,18 +184,22 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
if (!currentController.canChangeTabs { if (!currentController.canChangeTabs {
continueSwitchingTabs = true continueSwitchingTabs = true
this@MainActivity.bottom_nav.selectedItemId = id this@MainActivity.bottom_nav.selectedItemId = id
}) return@setOnNavigationItemSelectedListener false }
) return@setOnNavigationItemSelectedListener false
} }
continueSwitchingTabs = false continueSwitchingTabs = false
if (item.itemId != R.id.nav_browse) if (item.itemId != R.id.nav_browse)
preferences.lastTab().set(item.itemId) preferences.lastTab().set(item.itemId)
val currentRoot = router.backstack.firstOrNull() val currentRoot = router.backstack.firstOrNull()
if (currentRoot?.tag()?.toIntOrNull() != id) { if (currentRoot?.tag()?.toIntOrNull() != id) {
setRoot(when (id) { setRoot(
when (id) {
R.id.nav_library -> LibraryController() R.id.nav_library -> LibraryController()
R.id.nav_recents -> RecentsController() R.id.nav_recents -> RecentsController()
else -> SourceController() else -> SourceController()
}, id) },
id
)
} else if (currentRoot.tag()?.toIntOrNull() == id) { } else if (currentRoot.tag()?.toIntOrNull() == id) {
if (router.backstackSize == 1) { if (router.backstackSize == 1) {
val controller = val controller =
@ -225,7 +231,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
// Consume any horizontal insets and pad all content in. There's not much we can do // Consume any horizontal insets and pad all content in. There's not much we can do
// with horizontal insets // with horizontal insets
v.updatePadding( v.updatePadding(
left = insets.systemWindowInsetLeft, right = insets.systemWindowInsetRight left = insets.systemWindowInsetLeft,
right = insets.systemWindowInsetRight
) )
appbar.updatePadding( appbar.updatePadding(
top = insets.systemWindowInsetTop top = insets.systemWindowInsetTop
@ -251,7 +258,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
bottom_nav.visibleIf(!hideBottomNav) bottom_nav.visibleIf(!hideBottomNav)
bottom_nav.alpha = if (hideBottomNav) 0f else 1f bottom_nav.alpha = if (hideBottomNav) 0f else 1f
router.addChangeListener(object : ControllerChangeHandler.ControllerChangeListener { router.addChangeListener(
object : ControllerChangeHandler.ControllerChangeListener {
override fun onChangeStarted( override fun onChangeStarted(
to: Controller?, to: Controller?,
from: Controller?, from: Controller?,
@ -275,7 +283,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
appbar.y = 0f appbar.y = 0f
showDLQueueTutorial() showDLQueueTutorial()
} }
}) }
)
syncActivityViewWithController(router.backstack.lastOrNull()?.controller()) syncActivityViewWithController(router.backstack.lastOrNull()?.controller())
@ -322,7 +331,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
// if in portrait with 2/3 button mode, translucent nav bar // if in portrait with 2/3 button mode, translucent nav bar
else { else {
ColorUtils.setAlphaComponent( ColorUtils.setAlphaComponent(
getResourceColor(R.attr.colorPrimaryVariant), 179 getResourceColor(R.attr.colorPrimaryVariant),
179
) )
} }
} }
@ -335,7 +345,9 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
override fun onSupportActionModeFinished(mode: androidx.appcompat.view.ActionMode) { override fun onSupportActionModeFinished(mode: androidx.appcompat.view.ActionMode) {
launchUI { launchUI {
val scale = Settings.Global.getFloat( val scale = Settings.Global.getFloat(
contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
1.0f
) )
val duration = resources.getInteger(android.R.integer.config_mediumAnimTime) * scale val duration = resources.getInteger(android.R.integer.config_mediumAnimTime) * scale
delay(duration.toLong()) delay(duration.toLong())
@ -372,7 +384,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
) { ) {
val recentsItem = bottom_nav.getItemView(R.id.nav_recents) ?: return val recentsItem = bottom_nav.getItemView(R.id.nav_recents) ?: return
preferences.shownDownloadQueueTutorial().set(true) preferences.shownDownloadQueueTutorial().set(true)
TapTargetView.showFor(this, TapTargetView.showFor(
this,
TapTarget.forView( TapTarget.forView(
recentsItem, recentsItem,
getString(R.string.manage_whats_downloading), getString(R.string.manage_whats_downloading),
@ -387,7 +400,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
super.onTargetClick(view) super.onTargetClick(view)
bottom_nav.selectedItemId = R.id.nav_recents bottom_nav.selectedItemId = R.id.nav_recents
} }
}) }
)
} }
} }
@ -398,7 +412,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
private fun getAppUpdates() { private fun getAppUpdates() {
if (isUpdaterEnabled && if (isUpdaterEnabled &&
Date().time >= preferences.lastAppCheck().get() + TimeUnit.DAYS.toMillis(1)) { Date().time >= preferences.lastAppCheck().get() + TimeUnit.DAYS.toMillis(1)
) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
try { try {
val result = updateChecker.checkForUpdate() val result = updateChecker.checkForUpdate()
@ -441,7 +456,9 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
protected open fun handleIntentAction(intent: Intent): Boolean { protected open fun handleIntentAction(intent: Intent): Boolean {
val notificationId = intent.getIntExtra("notificationId", -1) val notificationId = intent.getIntExtra("notificationId", -1)
if (notificationId > -1) NotificationReceiver.dismissNotification( if (notificationId > -1) NotificationReceiver.dismissNotification(
applicationContext, notificationId, intent.getIntExtra("groupId", 0) applicationContext,
notificationId,
intent.getIntExtra("groupId", 0)
) )
when (intent.action) { when (intent.action) {
SHORTCUT_LIBRARY -> bottom_nav.selectedItemId = R.id.nav_library SHORTCUT_LIBRARY -> bottom_nav.selectedItemId = R.id.nav_library
@ -528,10 +545,9 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
extraViewForUndo?.getGlobalVisibleRect(extRect) extraViewForUndo?.getGlobalVisibleRect(extRect)
// This way the snackbar will only be dismissed if // This way the snackbar will only be dismissed if
// the user clicks outside it. // the user clicks outside it.
if (canDismissSnackBar && !sRect.contains( if (canDismissSnackBar &&
ev.x.toInt(), !sRect.contains(ev.x.toInt(), ev.y.toInt()) &&
ev.y.toInt() (extRect == null || !extRect.contains(ev.x.toInt(), ev.y.toInt()))
) && (extRect == null || !extRect.contains(ev.x.toInt(), ev.y.toInt()))
) { ) {
snackBar?.dismiss() snackBar?.dismiss()
snackBar = null snackBar = null
@ -567,14 +583,17 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
animationSet?.cancel() animationSet?.cancel()
animationSet = AnimatorSet() animationSet = AnimatorSet()
val alphaAnimation = ValueAnimator.ofFloat( val alphaAnimation = ValueAnimator.ofFloat(
bottom_nav.alpha, if (hideBottomNav) 0f else 1f bottom_nav.alpha,
if (hideBottomNav) 0f else 1f
) )
alphaAnimation.addUpdateListener { valueAnimator -> alphaAnimation.addUpdateListener { valueAnimator ->
bottom_nav.alpha = valueAnimator.animatedValue as Float bottom_nav.alpha = valueAnimator.animatedValue as Float
} }
alphaAnimation.addListener(EndAnimatorListener { alphaAnimation.addListener(
EndAnimatorListener {
bottom_nav.visibility = if (hideBottomNav) View.GONE else View.VISIBLE bottom_nav.visibility = if (hideBottomNav) View.GONE else View.VISIBLE
}) }
)
alphaAnimation.duration = 200 alphaAnimation.duration = 200
alphaAnimation.startDelay = 50 alphaAnimation.startDelay = 50
animationSet?.playTogether(alphaAnimation) animationSet?.playTogether(alphaAnimation)
@ -610,9 +629,10 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
if (abs(diffX) <= abs(diffY)) { if (abs(diffX) <= abs(diffY)) {
val sheetRect = Rect() val sheetRect = Rect()
bottom_nav.getGlobalVisibleRect(sheetRect) bottom_nav.getGlobalVisibleRect(sheetRect)
if (sheetRect.contains( if (sheetRect.contains(e1.x.toInt(), e1.y.toInt()) &&
e1.x.toInt(), e1.y.toInt() abs(diffY) > Companion.SWIPE_THRESHOLD &&
) && abs(diffY) > Companion.SWIPE_THRESHOLD && abs(velocityY) > Companion.SWIPE_VELOCITY_THRESHOLD && diffY <= 0 abs(velocityY) > Companion.SWIPE_VELOCITY_THRESHOLD &&
diffY <= 0
) { ) {
val bottomSheetController = val bottomSheetController =
router.backstack.lastOrNull()?.controller() as? BottomSheetController router.backstack.lastOrNull()?.controller() as? BottomSheetController

View File

@ -64,7 +64,9 @@ class SearchActivity : MainActivity() {
override fun handleIntentAction(intent: Intent): Boolean { override fun handleIntentAction(intent: Intent): Boolean {
val notificationId = intent.getIntExtra("notificationId", -1) val notificationId = intent.getIntExtra("notificationId", -1)
if (notificationId > -1) NotificationReceiver.dismissNotification( if (notificationId > -1) NotificationReceiver.dismissNotification(
applicationContext, notificationId, intent.getIntExtra("groupId", 0) applicationContext,
notificationId,
intent.getIntExtra("groupId", 0)
) )
when (intent.action) { when (intent.action) {
Intent.ACTION_SEARCH, "com.google.android.gms.actions.SEARCH_ACTION" -> { Intent.ACTION_SEARCH, "com.google.android.gms.actions.SEARCH_ACTION" -> {
@ -92,7 +94,8 @@ class SearchActivity : MainActivity() {
router.replaceTopController( router.replaceTopController(
RouterTransaction.with(MangaDetailsController(extras)) RouterTransaction.with(MangaDetailsController(extras))
.pushChangeHandler(SimpleSwapChangeHandler()) .pushChangeHandler(SimpleSwapChangeHandler())
.popChangeHandler(FadeChangeHandler())) .popChangeHandler(FadeChangeHandler())
)
} }
else -> return false else -> return false
} }
@ -100,8 +103,11 @@ class SearchActivity : MainActivity() {
} }
companion object { companion object {
fun openMangaIntent(context: Context, id: Long) = Intent(context, SearchActivity::class fun openMangaIntent(context: Context, id: Long) = Intent(
.java) context,
SearchActivity::class
.java
)
.apply { .apply {
action = SHORTCUT_MANGA action = SHORTCUT_MANGA
putExtra(MangaDetailsController.MANGA_EXTRA, id) putExtra(MangaDetailsController.MANGA_EXTRA, id)

View File

@ -33,10 +33,12 @@ class EditMangaDialog : DialogController {
private val infoController private val infoController
get() = targetController as MangaDetailsController get() = targetController as MangaDetailsController
constructor(target: MangaDetailsController, manga: Manga) : super(Bundle() constructor(target: MangaDetailsController, manga: Manga) : super(
Bundle()
.apply { .apply {
putLong(KEY_MANGA, manga.id!!) putLong(KEY_MANGA, manga.id!!)
}) { }
) {
targetController = target targetController = target
this.manga = manga this.manga = manga
} }
@ -99,7 +101,8 @@ class EditMangaDialog : DialogController {
if (manga.originalDescription != null) { if (manga.originalDescription != null) {
view.manga_description.hint = view.manga_description.hint =
"${resources?.getString(R.string.description)}: ${manga.originalDescription?.replace( "${resources?.getString(R.string.description)}: ${manga.originalDescription?.replace(
"\n", " " "\n",
" "
)?.chop(20)}" )?.chop(20)}"
} }
} }
@ -110,9 +113,12 @@ class EditMangaDialog : DialogController {
view.reset_tags.setOnClickListener { resetTags() } view.reset_tags.setOnClickListener { resetTags() }
view.reset_cover.visibleIf(!isLocal) view.reset_cover.visibleIf(!isLocal)
view.reset_cover.setOnClickListener { view.reset_cover.setOnClickListener {
view.manga_cover.loadAny(manga, builder = { view.manga_cover.loadAny(
manga,
builder = {
parameters(Parameters.Builder().set(MangaFetcher.realCover, true).build()) parameters(Parameters.Builder().set(MangaFetcher.realCover, true).build())
}) }
)
willResetCover = true willResetCover = true
} }
} }
@ -136,10 +142,15 @@ class EditMangaDialog : DialogController {
} }
private fun onPositiveButtonClick() { private fun onPositiveButtonClick() {
infoController.presenter.updateManga(dialogView?.title?.text.toString(), infoController.presenter.updateManga(
dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(), dialogView?.title?.text.toString(),
customCoverUri, dialogView?.manga_description?.text.toString(), dialogView?.manga_author?.text.toString(),
dialogView?.manga_genres_tags?.tags, willResetCover) dialogView?.manga_artist?.text.toString(),
customCoverUri,
dialogView?.manga_description?.text.toString(),
dialogView?.manga_genres_tags?.tags,
willResetCover
)
} }
private companion object { private companion object {

View File

@ -26,8 +26,11 @@ class MangaDetailsAdapter(
val delegate: MangaDetailsInterface = controller val delegate: MangaDetailsInterface = controller
val presenter = controller.presenter val presenter = controller.presenter
val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols() val decimalFormat = DecimalFormat(
.apply { decimalSeparator = '.' }) "#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' }
)
fun setChapters(items: List<ChapterItem>?) { fun setChapters(items: List<ChapterItem>?) {
this.items = items ?: emptyList() this.items = items ?: emptyList()
@ -47,10 +50,12 @@ class MangaDetailsAdapter(
if (s.isNullOrBlank()) { if (s.isNullOrBlank()) {
updateDataSet(items) updateDataSet(items)
} else { } else {
updateDataSet(items.filter { updateDataSet(
items.filter {
it.name.contains(s, true) || it.name.contains(s, true) ||
it.scanlator?.contains(s, true) == true it.scanlator?.contains(s, true) == true
}) }
)
} }
} }
@ -71,14 +76,16 @@ class MangaDetailsAdapter(
if (volume != null) { if (volume != null) {
recyclerView.context.getString( recyclerView.context.getString(
if (scrollType == MangaDetailsPresenter.MULTIPLE_SEASONS) R.string.season_ if (scrollType == MangaDetailsPresenter.MULTIPLE_SEASONS) R.string.season_
else R.string.volume_, volume else R.string.volume_,
volume
) )
} else { } else {
getChapterName(chapter) getChapterName(chapter)
} }
} }
MangaDetailsPresenter.TENS_OF_CHAPTERS -> recyclerView.context.getString( MangaDetailsPresenter.TENS_OF_CHAPTERS -> recyclerView.context.getString(
R.string.chapters_, get10sRange( R.string.chapters_,
get10sRange(
chapter.chapter_number chapter.chapter_number
) )
) )
@ -89,7 +96,8 @@ class MangaDetailsAdapter(
private fun getChapterName(item: ChapterItem): String { private fun getChapterName(item: ChapterItem): String {
return if (item.chapter_number > 0) { return if (item.chapter_number > 0) {
recyclerView.context.getString( recyclerView.context.getString(
R.string.chapter_, decimalFormat.format(item.chapter_number) R.string.chapter_,
decimalFormat.format(item.chapter_number)
) )
} else { } else {
item.name item.name

View File

@ -110,7 +110,8 @@ import java.io.IOException
import java.util.Locale import java.util.Locale
import kotlin.math.max import kotlin.math.max
class MangaDetailsController : BaseController, class MangaDetailsController :
BaseController,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
ActionMode.Callback, ActionMode.Callback,
@ -124,12 +125,14 @@ class MangaDetailsController : BaseController,
fromCatalogue: Boolean = false, fromCatalogue: Boolean = false,
smartSearchConfig: SourceController.SmartSearchConfig? = null, smartSearchConfig: SourceController.SmartSearchConfig? = null,
update: Boolean = false update: Boolean = false
) : super(Bundle().apply { ) : super(
Bundle().apply {
putLong(MANGA_EXTRA, manga?.id ?: 0) putLong(MANGA_EXTRA, manga?.id ?: 0)
putBoolean(FROM_CATALOGUE_EXTRA, fromCatalogue) putBoolean(FROM_CATALOGUE_EXTRA, fromCatalogue)
putParcelable(SMART_SEARCH_CONFIG_EXTRA, smartSearchConfig) putParcelable(SMART_SEARCH_CONFIG_EXTRA, smartSearchConfig)
putBoolean(UPDATE_EXTRA, update) putBoolean(UPDATE_EXTRA, update)
}) { }
) {
this.manga = manga this.manga = manga
if (manga != null) { if (manga != null) {
source = Injekt.get<SourceManager>().getOrStub(manga.source) source = Injekt.get<SourceManager>().getOrStub(manga.source)
@ -145,7 +148,9 @@ class MangaDetailsController : BaseController,
val notificationId = bundle.getInt("notificationId", -1) val notificationId = bundle.getInt("notificationId", -1)
val context = applicationContext ?: return val context = applicationContext ?: return
if (notificationId > -1) NotificationReceiver.dismissNotification( if (notificationId > -1) NotificationReceiver.dismissNotification(
context, notificationId, bundle.getInt("groupId", 0) context,
notificationId,
bundle.getInt("groupId", 0)
) )
} }
@ -227,13 +232,20 @@ class MangaDetailsController : BaseController,
swipe_refresh.setDistanceToTriggerSync(70.dpToPx) swipe_refresh.setDistanceToTriggerSync(70.dpToPx)
activity!!.appbar.elevation = 0f activity!!.appbar.elevation = 0f
scrollViewWith(recycler, padBottom = true, customPadding = true, afterInsets = { insets -> scrollViewWith(
recycler,
padBottom = true,
customPadding = true,
afterInsets = { insets ->
setInsets(insets, appbarHeight, offset) setInsets(insets, appbarHeight, offset)
}, liftOnScroll = { },
liftOnScroll = {
colorToolbar(it) colorToolbar(it)
}) }
)
recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { recycler.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
val atTop = !recyclerView.canScrollVertically(-1) val atTop = !recyclerView.canScrollVertically(-1)
@ -246,7 +258,8 @@ class MangaDetailsController : BaseController,
val atTop = !recyclerView.canScrollVertically(-1) val atTop = !recyclerView.canScrollVertically(-1)
if (atTop) getHeader()?.backdrop?.translationY = 0f if (atTop) getHeader()?.backdrop?.translationY = 0f
} }
}) }
)
} }
private fun setInsets(insets: WindowInsets, appbarHeight: Int, offset: Int) { private fun setInsets(insets: WindowInsets, appbarHeight: Int, offset: Int) {
@ -280,15 +293,19 @@ class MangaDetailsController : BaseController,
if (colorAnimator?.isRunning == true) activity?.window?.statusBarColor if (colorAnimator?.isRunning == true) activity?.window?.statusBarColor
?: color ?: color
else ColorUtils.setAlphaComponent( else ColorUtils.setAlphaComponent(
color, if (toolbarIsColored) 0 else 175 color,
if (toolbarIsColored) 0 else 175
) )
val colorTo = ColorUtils.setAlphaComponent( val colorTo = ColorUtils.setAlphaComponent(
color, if (toolbarIsColored) 175 else 0 color,
if (toolbarIsColored) 175 else 0
) )
colorAnimator?.cancel() colorAnimator?.cancel()
if (animate) { if (animate) {
colorAnimator = ValueAnimator.ofObject( colorAnimator = ValueAnimator.ofObject(
android.animation.ArgbEvaluator(), colorFrom, colorTo android.animation.ArgbEvaluator(),
colorFrom,
colorTo
) )
colorAnimator?.duration = 250 // milliseconds colorAnimator?.duration = 250 // milliseconds
colorAnimator?.addUpdateListener { animator -> colorAnimator?.addUpdateListener { animator ->
@ -307,7 +324,8 @@ class MangaDetailsController : BaseController,
val view = view ?: return val view = view ?: return
val request = LoadRequest.Builder(view.context).data(presenter.manga).allowHardware(false) val request = LoadRequest.Builder(view.context).data(presenter.manga).allowHardware(false)
.target(onSuccess = { drawable -> .target(
onSuccess = { drawable ->
val bitmap = (drawable as BitmapDrawable).bitmap val bitmap = (drawable as BitmapDrawable).bitmap
// Generate the Palette on a background thread. // Generate the Palette on a background thread.
Palette.from(bitmap).generate { Palette.from(bitmap).generate {
@ -329,13 +347,15 @@ class MangaDetailsController : BaseController,
} }
manga_cover_full.setImageDrawable(drawable) manga_cover_full.setImageDrawable(drawable)
getHeader()?.updateCover(manga!!) getHeader()?.updateCover(manga!!)
}, onError = { },
onError = {
val file = presenter.coverCache.getCoverFile(manga!!) val file = presenter.coverCache.getCoverFile(manga!!)
if (file.exists()) { if (file.exists()) {
file.delete() file.delete()
setPaletteColor() setPaletteColor()
} }
}).build() }
).build()
Coil.imageLoader(view.context).execute(request) Coil.imageLoader(view.context).execute(request)
} }
@ -343,10 +363,7 @@ class MangaDetailsController : BaseController,
private fun setActionBar(forThis: Boolean) { private fun setActionBar(forThis: Boolean) {
val activity = activity ?: return val activity = activity ?: return
// if the theme is using inverted toolbar color // if the theme is using inverted toolbar color
if (!activity.isInNightMode() && ThemeUtil.isBlueTheme( if (!activity.isInNightMode() && ThemeUtil.isBlueTheme(presenter.preferences.theme())) {
presenter.preferences.theme()
)
) {
if (forThis) (activity as MainActivity).appbar.context.setTheme( if (forThis) (activity as MainActivity).appbar.context.setTheme(
R.style.ThemeOverlay_AppCompat_DayNight_ActionBar R.style.ThemeOverlay_AppCompat_DayNight_ActionBar
) )
@ -481,10 +498,12 @@ class MangaDetailsController : BaseController,
1 -> return 1 -> return
else -> { else -> {
MaterialDialog(context).title(R.string.chapters_removed).message( MaterialDialog(context).title(R.string.chapters_removed).message(
text = context.resources.getQuantityString(R.plurals.deleted_chapters, text = context.resources.getQuantityString(
R.plurals.deleted_chapters,
deletedChapters.size, deletedChapters.size,
deletedChapters.size, deletedChapters.size,
deletedChapters.joinToString("\n") { "${it.name}" }) deletedChapters.joinToString("\n") { "${it.name}" }
)
).positiveButton(R.string.delete) { ).positiveButton(R.string.delete) {
presenter.deleteChapters(deletedChapters, false) presenter.deleteChapters(deletedChapters, false)
if (it.isCheckPromptChecked()) deleteRemovedPref.set(2) if (it.isCheckPromptChecked()) deleteRemovedPref.set(2)
@ -502,7 +521,8 @@ class MangaDetailsController : BaseController,
//region Recycler methods //region Recycler methods
fun updateChapterDownload(download: Download) { fun updateChapterDownload(download: Download) {
getHolder(download.chapter)?.notifyStatus( getHolder(download.chapter)?.notifyStatus(
download.status, presenter.isLockedFromSearch, download.status,
presenter.isLockedFromSearch,
download.progress download.progress
) )
} }
@ -638,7 +658,8 @@ class MangaDetailsController : BaseController,
snack?.dismiss() snack?.dismiss()
snack = view?.snack( snack = view?.snack(
if (bookmarked) R.string.removed_bookmark if (bookmarked) R.string.removed_bookmark
else R.string.bookmarked, Snackbar.LENGTH_INDEFINITE else R.string.bookmarked,
Snackbar.LENGTH_INDEFINITE
) { ) {
setAction(R.string.undo) { setAction(R.string.undo) {
bookmarkChapters(listOf(item), bookmarked) bookmarkChapters(listOf(item), bookmarked)
@ -657,21 +678,24 @@ class MangaDetailsController : BaseController,
snack?.dismiss() snack?.dismiss()
snack = view?.snack( snack = view?.snack(
if (read) R.string.marked_as_unread if (read) R.string.marked_as_unread
else R.string.marked_as_read, Snackbar.LENGTH_INDEFINITE else R.string.marked_as_read,
Snackbar.LENGTH_INDEFINITE
) { ) {
var undoing = false var undoing = false
setAction(R.string.undo) { setAction(R.string.undo) {
presenter.markChaptersRead(listOf(item), read, true, lastRead, pagesLeft) presenter.markChaptersRead(listOf(item), read, true, lastRead, pagesLeft)
undoing = true undoing = true
} }
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { addCallback(
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event) super.onDismissed(transientBottomBar, event)
if (!undoing && !read && presenter.preferences.removeAfterMarkedAsRead()) { if (!undoing && !read && presenter.preferences.removeAfterMarkedAsRead()) {
presenter.deleteChapters(listOf(item)) presenter.deleteChapters(listOf(item))
} }
} }
}) }
)
} }
(activity as? MainActivity)?.setUndoSnackBar(snack) (activity as? MainActivity)?.setUndoSnackBar(snack)
} }
@ -748,7 +772,8 @@ class MangaDetailsController : BaseController,
when (item.itemId) { when (item.itemId) {
R.id.action_edit -> { R.id.action_edit -> {
editMangaDialog = EditMangaDialog( editMangaDialog = EditMangaDialog(
this, presenter.manga this,
presenter.manga
) )
editMangaDialog?.showDialog(router) editMangaDialog?.showDialog(router)
} }
@ -781,11 +806,14 @@ class MangaDetailsController : BaseController,
override fun prepareToShareManga() { override fun prepareToShareManga() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val request = LoadRequest.Builder(activity!!).data(manga).target(onError = { val request = LoadRequest.Builder(activity!!).data(manga).target(
onError = {
shareManga() shareManga()
}, onSuccess = { },
onSuccess = {
presenter.shareManga((it as BitmapDrawable).bitmap) presenter.shareManga((it as BitmapDrawable).bitmap)
}).build() }
).build()
Coil.imageLoader(activity!!).execute(request) Coil.imageLoader(activity!!).execute(request)
} else { } else {
shareManga() shareManga()
@ -825,7 +853,10 @@ class MangaDetailsController : BaseController,
val activity = activity ?: return val activity = activity ?: return
val intent = WebViewActivity.newIntent( val intent = WebViewActivity.newIntent(
activity.applicationContext, source.id, url, presenter.manga activity.applicationContext,
source.id,
url,
presenter.manga
.title .title
) )
startActivity(intent) startActivity(intent)
@ -847,7 +878,9 @@ class MangaDetailsController : BaseController,
val context = view?.context ?: return val context = view?.context ?: return
MaterialDialog(context).message( MaterialDialog(context).message(
text = context.resources.getQuantityString( text = context.resources.getQuantityString(
R.plurals.remove_n_chapters, chapters.size, chapters.size R.plurals.remove_n_chapters,
chapters.size,
chapters.size
) )
).positiveButton(R.string.remove) { ).positiveButton(R.string.remove) {
presenter.deleteChapters(chapters) presenter.deleteChapters(chapters)
@ -901,22 +934,27 @@ class MangaDetailsController : BaseController,
val view = view ?: return val view = view ?: return
presenter.downloadChapters(chapters) presenter.downloadChapters(chapters)
val text = view.context.getString( val text = view.context.getString(
R.string.add_x_to_library, presenter.manga.mangaType R.string.add_x_to_library,
presenter.manga.mangaType
(view.context).toLowerCase(Locale.ROOT) (view.context).toLowerCase(Locale.ROOT)
) )
if (!presenter.manga.favorite && (snack == null || if (!presenter.manga.favorite && (
snack?.getText() != text) snack == null ||
snack?.getText() != text
)
) { ) {
snack = view.snack(text, Snackbar.LENGTH_INDEFINITE) { snack = view.snack(text, Snackbar.LENGTH_INDEFINITE) {
setAction(R.string.add) { setAction(R.string.add) {
presenter.setFavorite(true) presenter.setFavorite(true)
} }
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { addCallback(
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event) super.onDismissed(transientBottomBar, event)
if (snack == transientBottomBar) snack = null if (snack == transientBottomBar) snack = null
} }
}) }
)
} }
(activity as? MainActivity)?.setUndoSnackBar(snack) (activity as? MainActivity)?.setUndoSnackBar(snack)
} }
@ -935,17 +973,18 @@ class MangaDetailsController : BaseController,
val item = presenter.getNextUnreadChapter() val item = presenter.getNextUnreadChapter()
if (item != null) { if (item != null) {
openChapter(item.chapter) openChapter(item.chapter)
} else if (snack == null || snack?.getText() != view?.context?.getString( } else if (snack == null ||
R.string.next_chapter_not_found snack?.getText() != view?.context?.getString(R.string.next_chapter_not_found)
)
) { ) {
snack = view?.snack(R.string.next_chapter_not_found, Snackbar.LENGTH_LONG) { snack = view?.snack(R.string.next_chapter_not_found, Snackbar.LENGTH_LONG) {
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { addCallback(
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event) super.onDismissed(transientBottomBar, event)
if (snack == transientBottomBar) snack = null if (snack == transientBottomBar) snack = null
} }
}) }
)
} }
} }
} }
@ -1023,7 +1062,10 @@ class MangaDetailsController : BaseController,
categories.indexOfFirst { it.id == id }.takeIf { it != -1 } categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
}.toTypedArray() }.toTypedArray()
ChangeMangaCategoriesDialog( ChangeMangaCategoriesDialog(
this, listOf(manga), categories, preselected this,
listOf(manga),
categories,
preselected
).showDialog( ).showDialog(
router router
) )
@ -1080,12 +1122,14 @@ class MangaDetailsController : BaseController,
setAction(R.string.undo) { setAction(R.string.undo) {
presenter.setFavorite(true) presenter.setFavorite(true)
} }
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { addCallback(
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event) super.onDismissed(transientBottomBar, event)
if (!presenter.manga.favorite) presenter.confirmDeletion() if (!presenter.manga.favorite) presenter.confirmDeletion()
} }
}) }
)
} }
val favButton = getHeader()?.favorite_button val favButton = getHeader()?.favorite_button
(activity as? MainActivity)?.setUndoSnackBar(snack, favButton) (activity as? MainActivity)?.setUndoSnackBar(snack, favButton)
@ -1204,7 +1248,9 @@ class MangaDetailsController : BaseController,
if (startingDLChapterPos != null) { if (startingDLChapterPos != null) {
val item = adapter?.getItem(startingDLChapterPos!!) as? ChapterItem val item = adapter?.getItem(startingDLChapterPos!!) as? ChapterItem
(recycler.findViewHolderForAdapterPosition(startingDLChapterPos!!) as? ChapterHolder)?.notifyStatus( (recycler.findViewHolderForAdapterPosition(startingDLChapterPos!!) as? ChapterHolder)?.notifyStatus(
item?.status ?: Download.NOT_DOWNLOADED, false, 0 item?.status ?: Download.NOT_DOWNLOADED,
false,
0
) )
} }
startingDLChapterPos = null startingDLChapterPos = null
@ -1308,7 +1354,8 @@ class MangaDetailsController : BaseController,
) )
duration = shortAnimationDuration.toLong() duration = shortAnimationDuration.toLong()
interpolator = DecelerateInterpolator() interpolator = DecelerateInterpolator()
addListener(object : AnimatorListenerAdapter() { addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
TransitionManager.endTransitions(frame_layout) TransitionManager.endTransitions(frame_layout)
@ -1319,7 +1366,8 @@ class MangaDetailsController : BaseController,
TransitionManager.endTransitions(frame_layout) TransitionManager.endTransitions(frame_layout)
currentAnimator = null currentAnimator = null
} }
}) }
)
start() start()
} }
@ -1351,7 +1399,8 @@ class MangaDetailsController : BaseController,
play(ObjectAnimator.ofFloat(fullBackdrop, View.ALPHA, 0f)) play(ObjectAnimator.ofFloat(fullBackdrop, View.ALPHA, 0f))
duration = shortAnimationDuration.toLong() duration = shortAnimationDuration.toLong()
interpolator = DecelerateInterpolator() interpolator = DecelerateInterpolator()
addListener(object : AnimatorListenerAdapter() { addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
thumbView.alpha = 1f thumbView.alpha = 1f
@ -1368,7 +1417,8 @@ class MangaDetailsController : BaseController,
swipe_refresh.isEnabled = true swipe_refresh.isEnabled = true
currentAnimator = null currentAnimator = null
} }
}) }
)
start() start()
} }
} }

View File

@ -353,8 +353,10 @@ class MangaDetailsPresenter(
val categoriesToDownload = preferences.downloadNewCategories().getOrDefault().map(String::toInt) val categoriesToDownload = preferences.downloadNewCategories().getOrDefault().map(String::toInt)
val shouldDownload = categoriesToDownload.isEmpty() || getMangaCategoryIds().any { it in categoriesToDownload } val shouldDownload = categoriesToDownload.isEmpty() || getMangaCategoryIds().any { it in categoriesToDownload }
if (shouldDownload) { if (shouldDownload) {
downloadChapters(newChapters.first.sortedBy { it.chapter_number } downloadChapters(
.map { it.toModel() }) newChapters.first.sortedBy { it.chapter_number }
.map { it.toModel() }
)
} }
} }
} }
@ -419,9 +421,11 @@ class MangaDetailsPresenter(
} }
private fun trimException(e: java.lang.Exception): String { private fun trimException(e: java.lang.Exception): String {
return (if (e.message?.contains(": ") == true) e.message?.split(": ")?.drop(1) return (
if (e.message?.contains(": ") == true) e.message?.split(": ")?.drop(1)
?.joinToString(": ") ?.joinToString(": ")
else e.message) ?: preferences.context.getString(R.string.unknown_error) else e.message
) ?: preferences.context.getString(R.string.unknown_error)
} }
/** /**

View File

@ -160,11 +160,13 @@ class MangaHeaderHolder(
else expand() else expand()
} }
manga_summary_label.text = itemView.context.getString( manga_summary_label.text = itemView.context.getString(
R.string.about_this_, manga.mangaType(itemView.context) R.string.about_this_,
manga.mangaType(itemView.context)
) )
with(favorite_button) { with(favorite_button) {
icon = ContextCompat.getDrawable( icon = ContextCompat.getDrawable(
itemView.context, when { itemView.context,
when {
item.isLocked -> R.drawable.ic_lock_24dp item.isLocked -> R.drawable.ic_lock_24dp
manga.favorite -> R.drawable.ic_heart_24dp manga.favorite -> R.drawable.ic_heart_24dp
else -> R.drawable.ic_heart_outline_24dp else -> R.drawable.ic_heart_outline_24dp
@ -210,7 +212,8 @@ class MangaHeaderHolder(
val number = adapter.decimalFormat.format(nextChapter.chapter_number.toDouble()) val number = adapter.decimalFormat.format(nextChapter.chapter_number.toDouble())
if (nextChapter.chapter_number > 0) resources.getString( if (nextChapter.chapter_number > 0) resources.getString(
if (nextChapter.last_page_read > 0) R.string.continue_reading_chapter_ if (nextChapter.last_page_read > 0) R.string.continue_reading_chapter_
else R.string.start_reading_chapter_, number else R.string.start_reading_chapter_,
number
) )
else { else {
resources.getString( resources.getString(
@ -231,14 +234,16 @@ class MangaHeaderHolder(
} }
manga_status.visibleIf(manga.status != 0) manga_status.visibleIf(manga.status != 0)
manga_status.text = (itemView.context.getString( manga_status.text = (
itemView.context.getString(
when (manga.status) { when (manga.status) {
SManga.ONGOING -> R.string.ongoing SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed SManga.COMPLETED -> R.string.completed
SManga.LICENSED -> R.string.licensed SManga.LICENSED -> R.string.licensed
else -> R.string.unknown_status else -> R.string.unknown_status
} }
)) )
)
manga_source.text = presenter.source.toString() manga_source.text = presenter.source.toString()
filters_text.text = presenter.currentFilters() filters_text.text = presenter.currentFilters()
@ -256,7 +261,8 @@ class MangaHeaderHolder(
if (checked) { if (checked) {
backgroundTintList = ColorStateList.valueOf( backgroundTintList = ColorStateList.valueOf(
ColorUtils.setAlphaComponent( ColorUtils.setAlphaComponent(
context.getResourceColor(R.attr.colorAccent), 75 context.getResourceColor(R.attr.colorAccent),
75
) )
) )
strokeColor = ColorStateList.valueOf(Color.TRANSPARENT) strokeColor = ColorStateList.valueOf(Color.TRANSPARENT)
@ -287,7 +293,8 @@ class MangaHeaderHolder(
) )
icon = ContextCompat.getDrawable( icon = ContextCompat.getDrawable(
itemView.context, if (tracked) R.drawable itemView.context,
if (tracked) R.drawable
.ic_check_24dp else R.drawable.ic_sync_24dp .ic_check_24dp else R.drawable.ic_sync_24dp
) )
checked(tracked) checked(tracked)
@ -308,16 +315,22 @@ class MangaHeaderHolder(
fun updateCover(manga: Manga) { fun updateCover(manga: Manga) {
if (!manga.initialized) return if (!manga.initialized) return
val drawable = adapter.controller.manga_cover_full?.drawable val drawable = adapter.controller.manga_cover_full?.drawable
manga_cover.loadAny(manga, builder = { manga_cover.loadAny(
manga,
builder = {
placeholder(drawable) placeholder(drawable)
error(drawable) error(drawable)
if (manga.favorite) networkCachePolicy(CachePolicy.DISABLED) if (manga.favorite) networkCachePolicy(CachePolicy.DISABLED)
}) }
backdrop.loadAny(manga, builder = { )
backdrop.loadAny(
manga,
builder = {
placeholder(drawable) placeholder(drawable)
error(drawable) error(drawable)
if (manga.favorite) networkCachePolicy(CachePolicy.DISABLED) if (manga.favorite) networkCachePolicy(CachePolicy.DISABLED)
}) }
)
} }
fun expand() { fun expand() {

View File

@ -44,7 +44,9 @@ class ChapterFilterLayout @JvmOverloads constructor(context: Context, attrs: Att
show_download.isChecked = manga.downloadedFilter == Manga.SHOW_DOWNLOADED show_download.isChecked = manga.downloadedFilter == Manga.SHOW_DOWNLOADED
show_bookmark.isChecked = manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED show_bookmark.isChecked = manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED
show_all.isChecked = !(show_read.isChecked || show_unread.isChecked || show_all.isChecked = !(
show_download.isChecked || show_bookmark.isChecked) show_read.isChecked || show_unread.isChecked ||
show_download.isChecked || show_bookmark.isChecked
)
} }
} }

View File

@ -58,13 +58,16 @@ class ChapterHolder(
if (showPagesLeft && chapter.pages_left > 0) { if (showPagesLeft && chapter.pages_left > 0) {
statuses.add( statuses.add(
itemView.resources.getQuantityString( itemView.resources.getQuantityString(
R.plurals.pages_left, chapter.pages_left, chapter.pages_left R.plurals.pages_left,
chapter.pages_left,
chapter.pages_left
) )
) )
} else if (showPagesLeft) { } else if (showPagesLeft) {
statuses.add( statuses.add(
itemView.context.getString( itemView.context.getString(
R.string.page_, chapter.last_page_read + 1 R.string.page_,
chapter.last_page_read + 1
) )
) )
} }
@ -81,7 +84,10 @@ class ChapterHolder(
} }
// this will color the scanlator the same bookmarks // this will color the scanlator the same bookmarks
ChapterUtil.setTextViewForChapter( ChapterUtil.setTextViewForChapter(
chapter_scanlator, item, showBookmark = false, hideStatus = isLocked chapter_scanlator,
item,
showBookmark = false,
hideStatus = isLocked
) )
chapter_scanlator.text = statuses.joinToString("") chapter_scanlator.text = statuses.joinToString("")
@ -115,9 +121,11 @@ class ChapterHolder(
val anim3 = slideAnimation(-slide, 0f) val anim3 = slideAnimation(-slide, 0f)
anim3.startDelay = 750 anim3.startDelay = 750
animatorSet.playSequentially(anim1, anim2, anim3) animatorSet.playSequentially(anim1, anim2, anim3)
animatorSet.addListener(EndAnimatorListener { animatorSet.addListener(
EndAnimatorListener {
adapter.hasShownSwipeTut.set(true) adapter.hasShownSwipeTut.set(true)
}) }
)
animatorSet.start() animatorSet.start()
} }

View File

@ -37,7 +37,8 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) : BottomSheetD
val height = activity.window.decorView.rootWindowInsets.systemWindowInsetBottom val height = activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
sheetBehavior.peekHeight = 415.dpToPx + height sheetBehavior.peekHeight = 415.dpToPx + height
sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { sheetBehavior.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) { override fun onSlide(bottomSheet: View, progress: Float) {
if (progress.isNaN()) if (progress.isNaN())
pill.alpha = 0f pill.alpha = 0f
@ -50,7 +51,8 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) : BottomSheetD
sheetBehavior.skipCollapsed = true sheetBehavior.skipCollapsed = true
} }
} }
}) }
)
} }
override fun onStart() { override fun onStart() {
@ -89,19 +91,27 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) : BottomSheetD
chapter_filter_layout.setCheckboxes(presenter.manga) chapter_filter_layout.setCheckboxes(presenter.manga)
var defPref = presenter.globalSort() var defPref = presenter.globalSort()
sort_group.check(if (presenter.manga.sortDescending(defPref)) R.id.sort_newest else sort_group.check(
R.id.sort_oldest) if (presenter.manga.sortDescending(defPref)) R.id.sort_newest else
R.id.sort_oldest
)
hide_titles.isChecked = presenter.manga.displayMode != Manga.DISPLAY_NAME hide_titles.isChecked = presenter.manga.displayMode != Manga.DISPLAY_NAME
sort_method_group.check(if (presenter.manga.sorting == Manga.SORTING_SOURCE) R.id.sort_by_source else sort_method_group.check(
R.id.sort_by_number) if (presenter.manga.sorting == Manga.SORTING_SOURCE) R.id.sort_by_source else
R.id.sort_by_number
)
set_as_default_sort.visInvisIf(defPref != presenter.manga.sortDescending() && set_as_default_sort.visInvisIf(
presenter.manga.usesLocalSort()) defPref != presenter.manga.sortDescending() &&
presenter.manga.usesLocalSort()
)
sort_group.setOnCheckedChangeListener { _, checkedId -> sort_group.setOnCheckedChangeListener { _, checkedId ->
presenter.setSortOrder(checkedId == R.id.sort_newest) presenter.setSortOrder(checkedId == R.id.sort_newest)
set_as_default_sort.visInvisIf(defPref != presenter.manga.sortDescending() && set_as_default_sort.visInvisIf(
presenter.manga.usesLocalSort()) defPref != presenter.manga.sortDescending() &&
presenter.manga.usesLocalSort()
)
} }
set_as_default_sort.setOnClickListener { set_as_default_sort.setOnClickListener {

View File

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

View File

@ -18,9 +18,11 @@ class SetTrackScoreDialog<T> : DialogController where T : SetTrackScoreDialog.Li
private val item: TrackItem private val item: TrackItem
private lateinit var listener: Listener private lateinit var listener: Listener
constructor(target: T, item: TrackItem) : super(Bundle().apply { constructor(target: T, item: TrackItem) : super(
Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track) putSerializable(KEY_ITEM_TRACK, item.track)
}) { }
) {
listener = target listener = target
this.item = item this.item = item
} }

View File

@ -17,9 +17,11 @@ class SetTrackStatusDialog<T> : DialogController
private val item: TrackItem private val item: TrackItem
private lateinit var listener: Listener private lateinit var listener: Listener
constructor(target: T, item: TrackItem) : super(Bundle().apply { constructor(target: T, item: TrackItem) : super(
Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track) putSerializable(KEY_ITEM_TRACK, item.track)
}) { }
) {
listener = target listener = target
this.item = item this.item = item
} }
@ -40,8 +42,11 @@ class SetTrackStatusDialog<T> : DialogController
return MaterialDialog(activity!!) return MaterialDialog(activity!!)
.title(R.string.status) .title(R.string.status)
.negativeButton(android.R.string.cancel) .negativeButton(android.R.string.cancel)
.listItemsSingleChoice(items = statusString, initialSelection = selectedIndex, .listItemsSingleChoice(
waitForPositiveButton = false) { dialog, position, _ -> items = statusString,
initialSelection = selectedIndex,
waitForPositiveButton = false
) { dialog, position, _ ->
listener.setStatus(item, position) listener.setStatus(item, position)
dialog.dismiss() dialog.dismiss()
} }

View File

@ -42,10 +42,13 @@ class TrackHolder(view: View, adapter: TrackAdapter) : BaseViewHolder(view) {
R.string.all_chapters_read R.string.all_chapters_read
) )
track.total_chapters > 0 -> context.getString( track.total_chapters > 0 -> context.getString(
R.string.chapter_x_of_y, track.last_chapter_read, track.total_chapters R.string.chapter_x_of_y,
track.last_chapter_read,
track.total_chapters
) )
track.last_chapter_read > 0 -> context.getString( track.last_chapter_read > 0 -> context.getString(
R.string.chapter_, track.last_chapter_read.toString() R.string.chapter_,
track.last_chapter_read.toString()
) )
else -> context.getString(R.string.not_started) else -> context.getString(R.string.not_started)
} }

View File

@ -19,9 +19,11 @@ class TrackRemoveDialog<T> : DialogController
private val item: TrackItem private val item: TrackItem
private lateinit var listener: Listener private lateinit var listener: Listener
constructor(target: T, item: TrackItem) : super(Bundle().apply { constructor(target: T, item: TrackItem) : super(
Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track) putSerializable(KEY_ITEM_TRACK, item.track)
}) { }
) {
listener = target listener = target
this.item = item this.item = item
} }
@ -42,11 +44,15 @@ class TrackRemoveDialog<T> : DialogController
if (item.service.canRemoveFromService()) { if (item.service.canRemoveFromService()) {
dialog.checkBoxPrompt( dialog.checkBoxPrompt(
text = activity!!.getString( text = activity!!.getString(
R.string.remove_tracking_from_, item.service.name R.string.remove_tracking_from_,
), isCheckedDefault = true, onToggle = null item.service.name
),
isCheckedDefault = true,
onToggle = null
).positiveButton(R.string.remove) { ).positiveButton(R.string.remove) {
listener.removeTracker( listener.removeTracker(
item, it.isCheckPromptChecked() item,
it.isCheckPromptChecked()
) )
} }
dialog.getCheckBoxPrompt().textSize = 16f dialog.getCheckBoxPrompt().textSize = 16f

View File

@ -43,10 +43,12 @@ class TrackSearchDialog : DialogController {
private lateinit var presenter: MangaDetailsPresenter private lateinit var presenter: MangaDetailsPresenter
constructor(target: TrackingBottomSheet, service: TrackService, wasTracked: Boolean) : super(Bundle() constructor(target: TrackingBottomSheet, service: TrackService, wasTracked: Boolean) : super(
Bundle()
.apply { .apply {
putInt(KEY_SERVICE, service.id) putInt(KEY_SERVICE, service.id)
}) { }
) {
wasPreviouslyTracked = wasTracked wasPreviouslyTracked = wasTracked
bottomSheet = target bottomSheet = target
presenter = target.presenter presenter = target.presenter

View File

@ -19,7 +19,8 @@ import eu.kanade.tachiyomi.util.view.setEdgeToEdge
import kotlinx.android.synthetic.main.tracking_bottom_sheet.* import kotlinx.android.synthetic.main.tracking_bottom_sheet.*
import timber.log.Timber import timber.log.Timber
class TrackingBottomSheet(private val controller: MangaDetailsController) : BottomSheetDialog class TrackingBottomSheet(private val controller: MangaDetailsController) :
BottomSheetDialog
(controller.activity!!, R.style.BottomSheetDialogTheme), (controller.activity!!, R.style.BottomSheetDialogTheme),
TrackAdapter.OnClickListener, TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener, SetTrackStatusDialog.Listener,
@ -45,7 +46,8 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : Bott
val height = activity.window.decorView.rootWindowInsets.systemWindowInsetBottom val height = activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
sheetBehavior.peekHeight = 380.dpToPx + height sheetBehavior.peekHeight = 380.dpToPx + height
sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { sheetBehavior.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) { } override fun onSlide(bottomSheet: View, progress: Float) { }
override fun onStateChanged(p0: View, state: Int) { override fun onStateChanged(p0: View, state: Int) {
@ -53,7 +55,8 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : Bott
sheetBehavior.skipCollapsed = true sheetBehavior.skipCollapsed = true
} }
} }
}) }
)
} }
override fun onStart() { override fun onStart() {

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