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

@ -26,11 +26,11 @@ import uy.kohesive.injekt.registry.default.DefaultRegistrar
import java.security.Security import java.security.Security
@ReportsCrashes( @ReportsCrashes(
formUri = "https://collector.tracepot.com/e90773ff", formUri = "https://collector.tracepot.com/e90773ff",
reportType = org.acra.sender.HttpSender.Type.JSON, reportType = org.acra.sender.HttpSender.Type.JSON,
httpMethod = org.acra.sender.HttpSender.Method.PUT, httpMethod = org.acra.sender.HttpSender.Method.PUT,
buildConfigClass = BuildConfig::class, buildConfigClass = BuildConfig::class,
excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"] excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
) )
open class App : Application(), LifecycleObserver { open class App : Application(), LifecycleObserver {

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

@ -14,7 +14,7 @@ import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) : class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) { Worker(context, workerParams) {
override fun doWork(): Result { override fun doWork(): Result {
val preferences = Injekt.get<PreferencesHelper>() val preferences = Injekt.get<PreferencesHelper>()
@ -37,10 +37,13 @@ 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,
.addTag(TAG) 10,
.build() TimeUnit.MINUTES
)
.addTag(TAG)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
} else { } else {

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())
@ -176,14 +177,14 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
val numberOfBackups = numberOfBackups() val numberOfBackups = numberOfBackups()
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""") val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
dir.listFiles { _, filename -> backupRegex.matches(filename) } dir.listFiles { _, filename -> backupRegex.matches(filename) }
.orEmpty() .orEmpty()
.sortedByDescending { it.name } .sortedByDescending { it.name }
.drop(numberOfBackups - 1) .drop(numberOfBackups - 1)
.forEach { it.delete() } .forEach { it.delete() }
// Create new file to place backup // Create new file to place backup
val newFile = dir.createFile(Backup.getDefaultFilename()) val newFile = dir.createFile(Backup.getDefaultFilename())
?: throw Exception("Couldn't create backup file") ?: throw Exception("Couldn't create backup file")
newFile.openOutputStream().bufferedWriter().use { newFile.openOutputStream().bufferedWriter().use {
parser.toJson(root, it) parser.toJson(root, it)
@ -192,7 +193,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
return newFile.uri.toString() return newFile.uri.toString()
} else { } else {
val file = UniFile.fromUri(context, uri) val file = UniFile.fromUri(context, uri)
?: throw Exception("Couldn't create backup file") ?: throw Exception("Couldn't create backup file")
file.openOutputStream().bufferedWriter().use { file.openOutputStream().bufferedWriter().use {
parser.toJson(root, it) parser.toJson(root, it)
} }
@ -514,7 +515,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @return [Manga], null if not found * @return [Manga], null if not found
*/ */
internal fun getMangaFromDatabase(manga: Manga): Manga? = internal fun getMangaFromDatabase(manga: Manga): Manga? =
databaseHelper.getManga(manga.url, manga.source).executeAsBlocking() databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
/** /**
* Returns list containing manga from library * Returns list containing manga from library
@ -522,7 +523,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @return [Manga] from library * @return [Manga] from library
*/ */
internal fun getFavoriteManga(): List<Manga> = internal fun getFavoriteManga(): List<Manga> =
databaseHelper.getFavoriteMangas().executeAsBlocking() databaseHelper.getFavoriteMangas().executeAsBlocking()
/** /**
* Inserts manga and returns id * Inserts manga and returns id
@ -530,7 +531,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @return id of [Manga], null if not found * @return id of [Manga], null if not found
*/ */
internal fun insertManga(manga: Manga): Long? = internal fun insertManga(manga: Manga): Long? =
databaseHelper.insertManga(manga).executeAsBlocking().insertedId() databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
/** /**
* Inserts list of chapters * Inserts list of chapters

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))
} }
@ -358,13 +360,13 @@ class BackupRestoreService : Service() {
*/ */
private val progressNotification by lazy { private val progressNotification by lazy {
NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE) NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
.setContentTitle(getString(R.string.app_name)) .setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_tachi) .setSmallIcon(R.drawable.ic_tachi)
.setOngoing(true) .setOngoing(true)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setAutoCancel(false) .setAutoCancel(false)
.setColor(ContextCompat.getColor(this, R.color.colorAccent)) .setColor(ContextCompat.getColor(this, R.color.colorAccent))
.addAction(R.drawable.ic_close_24dp, getString(android.R.string.cancel), cancelIntent) .addAction(R.drawable.ic_close_24dp, getString(android.R.string.cancel), cancelIntent)
} }
/** /**
@ -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")
@ -433,15 +458,21 @@ class BackupRestoreService : Service() {
val restoreString = content.joinToString("\n") val restoreString = content.joinToString("\n")
val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE) val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
.setContentTitle(getString(R.string.restore_completed)) .setContentTitle(getString(R.string.restore_completed))
.setContentText(restoreString) .setContentText(restoreString)
.setStyle(NotificationCompat.BigTextStyle().bigText(restoreString)) .setStyle(NotificationCompat.BigTextStyle().bigText(restoreString))
.setSmallIcon(R.drawable.ic_tachi) .setSmallIcon(R.drawable.ic_tachi)
.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())
} }
@ -451,11 +482,11 @@ class BackupRestoreService : Service() {
*/ */
private fun showErrorNotification(errorMessage: String) { private fun showErrorNotification(errorMessage: String) {
val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE) val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
.setContentTitle(getString(R.string.restore_error)) .setContentTitle(getString(R.string.restore_error))
.setContentText(errorMessage) .setContentText(errorMessage)
.setSmallIcon(R.drawable.ic_error_24dp) .setSmallIcon(R.drawable.ic_error_24dp)
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(ContextCompat.getColor(this, R.color.md_red_500)) .setColor(ContextCompat.getColor(this, R.color.md_red_500))
notificationManager.notify(Notifications.ID_RESTORE_ERROR, resultNotification.build()) notificationManager.notify(Notifications.ID_RESTORE_ERROR, resultNotification.build())
} }
@ -477,7 +508,7 @@ class BackupRestoreService : Service() {
* @return true if the service is running, false otherwise. * @return true if the service is running, false otherwise.
*/ */
fun isRunning(context: Context): Boolean = fun isRunning(context: Context): Boolean =
context.isServiceRunning(BackupRestoreService::class.java) context.isServiceRunning(BackupRestoreService::class.java)
/** /**
* Starts a service to restore a backup from Json * Starts a service to restore a backup from Json

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(
PARAMETER_APP_VERSION, File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
PARAMETER_VALUE_COUNT, PARAMETER_APP_VERSION,
PARAMETER_CACHE_SIZE) PARAMETER_VALUE_COUNT,
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)
@ -39,15 +44,15 @@ MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQuerie
.build() .build()
override val db = DefaultStorIOSQLite.builder() override val db = DefaultStorIOSQLite.builder()
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration)) .sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))
.addTypeMapping(Manga::class.java, MangaTypeMapping()) .addTypeMapping(Manga::class.java, MangaTypeMapping())
.addTypeMapping(Chapter::class.java, ChapterTypeMapping()) .addTypeMapping(Chapter::class.java, ChapterTypeMapping())
.addTypeMapping(Track::class.java, TrackTypeMapping()) .addTypeMapping(Track::class.java, TrackTypeMapping())
.addTypeMapping(Category::class.java, CategoryTypeMapping()) .addTypeMapping(Category::class.java, CategoryTypeMapping())
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping()) .addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping()) .addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
.addTypeMapping(History::class.java, HistoryTypeMapping()) .addTypeMapping(History::class.java, HistoryTypeMapping())
.build() .build()
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block) inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)

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

@ -19,22 +19,22 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
class CategoryTypeMapping : SQLiteTypeMapping<Category>( class CategoryTypeMapping : SQLiteTypeMapping<Category>(
CategoryPutResolver(), CategoryPutResolver(),
CategoryGetResolver(), CategoryGetResolver(),
CategoryDeleteResolver() CategoryDeleteResolver()
) )
class CategoryPutResolver : DefaultPutResolver<Category>() { class CategoryPutResolver : DefaultPutResolver<Category>() {
override fun mapToInsertQuery(obj: Category) = InsertQuery.builder() override fun mapToInsertQuery(obj: Category) = InsertQuery.builder()
.table(TABLE) .table(TABLE)
.build() .build()
override fun mapToUpdateQuery(obj: Category) = UpdateQuery.builder() override fun mapToUpdateQuery(obj: Category) = UpdateQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
override fun mapToContentValues(obj: Category) = ContentValues(4).apply { override fun mapToContentValues(obj: Category) = ContentValues(4).apply {
put(COL_ID, obj.id) put(COL_ID, obj.id)
@ -76,8 +76,8 @@ class CategoryGetResolver : DefaultGetResolver<Category>() {
class CategoryDeleteResolver : DefaultDeleteResolver<Category>() { class CategoryDeleteResolver : DefaultDeleteResolver<Category>() {
override fun mapToDeleteQuery(obj: Category) = DeleteQuery.builder() override fun mapToDeleteQuery(obj: Category) = DeleteQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
} }

View File

@ -27,22 +27,22 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_URL
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>( class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
ChapterPutResolver(), ChapterPutResolver(),
ChapterGetResolver(), ChapterGetResolver(),
ChapterDeleteResolver() ChapterDeleteResolver()
) )
class ChapterPutResolver : DefaultPutResolver<Chapter>() { class ChapterPutResolver : DefaultPutResolver<Chapter>() {
override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder() override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder()
.table(TABLE) .table(TABLE)
.build() .build()
override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder() override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
override fun mapToContentValues(obj: Chapter) = ContentValues(11).apply { override fun mapToContentValues(obj: Chapter) = ContentValues(11).apply {
put(COL_ID, obj.id) put(COL_ID, obj.id)
@ -83,8 +83,8 @@ class ChapterGetResolver : DefaultGetResolver<Chapter>() {
class ChapterDeleteResolver : DefaultDeleteResolver<Chapter>() { class ChapterDeleteResolver : DefaultDeleteResolver<Chapter>() {
override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder() override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
} }

View File

@ -18,22 +18,22 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_TIME_READ
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE
class HistoryTypeMapping : SQLiteTypeMapping<History>( class HistoryTypeMapping : SQLiteTypeMapping<History>(
HistoryPutResolver(), HistoryPutResolver(),
HistoryGetResolver(), HistoryGetResolver(),
HistoryDeleteResolver() HistoryDeleteResolver()
) )
open class HistoryPutResolver : DefaultPutResolver<History>() { open class HistoryPutResolver : DefaultPutResolver<History>() {
override fun mapToInsertQuery(obj: History) = InsertQuery.builder() override fun mapToInsertQuery(obj: History) = InsertQuery.builder()
.table(TABLE) .table(TABLE)
.build() .build()
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder() override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
override fun mapToContentValues(obj: History) = ContentValues(4).apply { override fun mapToContentValues(obj: History) = ContentValues(4).apply {
put(COL_ID, obj.id) put(COL_ID, obj.id)
@ -56,8 +56,8 @@ class HistoryGetResolver : DefaultGetResolver<History>() {
class HistoryDeleteResolver : DefaultDeleteResolver<History>() { class HistoryDeleteResolver : DefaultDeleteResolver<History>() {
override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder() override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
} }

View File

@ -16,22 +16,22 @@ import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_MANGA_ID
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>( class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
MangaCategoryPutResolver(), MangaCategoryPutResolver(),
MangaCategoryGetResolver(), MangaCategoryGetResolver(),
MangaCategoryDeleteResolver() MangaCategoryDeleteResolver()
) )
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() { class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder() override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder()
.table(TABLE) .table(TABLE)
.build() .build()
override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder() override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
override fun mapToContentValues(obj: MangaCategory) = ContentValues(3).apply { override fun mapToContentValues(obj: MangaCategory) = ContentValues(3).apply {
put(COL_ID, obj.id) put(COL_ID, obj.id)
@ -52,8 +52,8 @@ class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
class MangaCategoryDeleteResolver : DefaultDeleteResolver<MangaCategory>() { class MangaCategoryDeleteResolver : DefaultDeleteResolver<MangaCategory>() {
override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder() override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
} }

View File

@ -31,22 +31,22 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VIEWER
import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
class MangaTypeMapping : SQLiteTypeMapping<Manga>( class MangaTypeMapping : SQLiteTypeMapping<Manga>(
MangaPutResolver(), MangaPutResolver(),
MangaGetResolver(), MangaGetResolver(),
MangaDeleteResolver() MangaDeleteResolver()
) )
class MangaPutResolver : DefaultPutResolver<Manga>() { class MangaPutResolver : DefaultPutResolver<Manga>() {
override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder() override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder()
.table(TABLE) .table(TABLE)
.build() .build()
override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder() override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
override fun mapToContentValues(obj: Manga) = ContentValues(15).apply { override fun mapToContentValues(obj: Manga) = ContentValues(15).apply {
put(COL_ID, obj.id) put(COL_ID, obj.id)
@ -101,8 +101,8 @@ open class MangaGetResolver : DefaultGetResolver<Manga>(), BaseMangaGetResolver
class MangaDeleteResolver : DefaultDeleteResolver<Manga>() { class MangaDeleteResolver : DefaultDeleteResolver<Manga>() {
override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder() override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
} }

View File

@ -25,22 +25,22 @@ import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TRACKING_URL
import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE
class TrackTypeMapping : SQLiteTypeMapping<Track>( class TrackTypeMapping : SQLiteTypeMapping<Track>(
TrackPutResolver(), TrackPutResolver(),
TrackGetResolver(), TrackGetResolver(),
TrackDeleteResolver() TrackDeleteResolver()
) )
class TrackPutResolver : DefaultPutResolver<Track>() { class TrackPutResolver : DefaultPutResolver<Track>() {
override fun mapToInsertQuery(obj: Track) = InsertQuery.builder() override fun mapToInsertQuery(obj: Track) = InsertQuery.builder()
.table(TABLE) .table(TABLE)
.build() .build()
override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder() override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
override fun mapToContentValues(obj: Track) = ContentValues(10).apply { override fun mapToContentValues(obj: Track) = ContentValues(10).apply {
put(COL_ID, obj.id) put(COL_ID, obj.id)
@ -77,8 +77,8 @@ class TrackGetResolver : DefaultGetResolver<Track>() {
class TrackDeleteResolver : DefaultDeleteResolver<Track>() { class TrackDeleteResolver : DefaultDeleteResolver<Track>() {
override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder() override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder()
.table(TABLE) .table(TABLE)
.where("$COL_ID = ?") .where("$COL_ID = ?")
.whereArgs(obj.id) .whereArgs(obj.id)
.build() .build()
} }

View File

@ -22,8 +22,8 @@ class LibraryManga : MangaImpl() {
fun createHide(categoryId: Int, title: String): LibraryManga = fun createHide(categoryId: Int, title: String): LibraryManga =
createBlank(categoryId).apply { createBlank(categoryId).apply {
this.title = title this.title = title
status = -1 status = -1
} }
} }
} }

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

@ -10,20 +10,24 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable
interface CategoryQueries : DbProvider { interface CategoryQueries : DbProvider {
fun getCategories() = db.get() fun getCategories() = db.get()
.listOfObjects(Category::class.java) .listOfObjects(Category::class.java)
.withQuery(Query.builder() .withQuery(
.table(CategoryTable.TABLE) Query.builder()
.orderBy(CategoryTable.COL_ORDER) .table(CategoryTable.TABLE)
.build()) .orderBy(CategoryTable.COL_ORDER)
.prepare() .build()
)
.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(
.query(getCategoriesForMangaQuery()) RawQuery.builder()
.args(manga.id) .query(getCategoriesForMangaQuery())
.build()) .args(manga.id)
.prepare() .build()
)
.prepare()
fun insertCategory(category: Category) = db.put().`object`(category).prepare() fun insertCategory(category: Category) = db.put().`object`(category).prepare()

View File

@ -19,68 +19,82 @@ import java.util.Date
interface ChapterQueries : DbProvider { 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(
.table(ChapterTable.TABLE) Query.builder()
.where("${ChapterTable.COL_MANGA_ID} = ?") .table(ChapterTable.TABLE)
.whereArgs(manga.id) .where("${ChapterTable.COL_MANGA_ID} = ?")
.build()) .whereArgs(manga.id)
.prepare() .build()
)
.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(
.query(getRecentsQuery()) RawQuery.builder()
.args(date.time) .query(getRecentsQuery())
.observesTables(ChapterTable.TABLE) .args(date.time)
.build()) .observesTables(ChapterTable.TABLE)
.withGetResolver(MangaChapterGetResolver.INSTANCE) .build()
.prepare() )
.withGetResolver(MangaChapterGetResolver.INSTANCE)
.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(
.query(getRecentsQueryDistinct(search.sqLite, endless)) RawQuery.builder()
.args(date.time) .query(getRecentsQueryDistinct(search.sqLite, endless))
.observesTables(ChapterTable.TABLE) .args(date.time)
.build()) .observesTables(ChapterTable.TABLE)
.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(
.table(ChapterTable.TABLE) Query.builder()
.where("${ChapterTable.COL_ID} = ?") .table(ChapterTable.TABLE)
.whereArgs(id) .where("${ChapterTable.COL_ID} = ?")
.build()) .whereArgs(id)
.prepare() .build()
)
.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(
.table(ChapterTable.TABLE) Query.builder()
.where("${ChapterTable.COL_URL} = ?") .table(ChapterTable.TABLE)
.whereArgs(url) .where("${ChapterTable.COL_URL} = ?")
.build()) .whereArgs(url)
.prepare() .build()
)
.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(
.table(ChapterTable.TABLE) Query.builder()
.where("${ChapterTable.COL_URL} = ?") .table(ChapterTable.TABLE)
.whereArgs(url) .where("${ChapterTable.COL_URL} = ?")
.build()) .whereArgs(url)
.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(
.table(ChapterTable.TABLE) Query.builder()
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?") .table(ChapterTable.TABLE)
.whereArgs(url, mangaId) .where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
.build()) .whereArgs(url, mangaId)
.build()
)
.prepare() .prepare()
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare() fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
@ -92,22 +106,22 @@ interface ChapterQueries : DbProvider {
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare() fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
fun updateChaptersBackup(chapters: List<Chapter>) = db.put() fun updateChaptersBackup(chapters: List<Chapter>) = db.put()
.objects(chapters) .objects(chapters)
.withPutResolver(ChapterBackupPutResolver()) .withPutResolver(ChapterBackupPutResolver())
.prepare() .prepare()
fun updateChapterProgress(chapter: Chapter) = db.put() fun updateChapterProgress(chapter: Chapter) = db.put()
.`object`(chapter) .`object`(chapter)
.withPutResolver(ChapterProgressPutResolver()) .withPutResolver(ChapterProgressPutResolver())
.prepare() .prepare()
fun updateChaptersProgress(chapters: List<Chapter>) = db.put() fun updateChaptersProgress(chapters: List<Chapter>) = db.put()
.objects(chapters) .objects(chapters)
.withPutResolver(ChapterProgressPutResolver()) .withPutResolver(ChapterProgressPutResolver())
.prepare() .prepare()
fun fixChaptersSourceOrder(chapters: List<Chapter>) = db.put() fun fixChaptersSourceOrder(chapters: List<Chapter>) = db.put()
.objects(chapters) .objects(chapters)
.withPutResolver(ChapterSourceOrderPutResolver()) .withPutResolver(ChapterSourceOrderPutResolver())
.prepare() .prepare()
} }

View File

@ -26,14 +26,16 @@ interface HistoryQueries : DbProvider {
* @offset offset the db by * @offset offset the db by
*/ */
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(
.query(getRecentMangasQuery(offset, search.sqLite)) RawQuery.builder()
.args(date.time) .query(getRecentMangasQuery(offset, search.sqLite))
.observesTables(HistoryTable.TABLE) .args(date.time)
.build()) .observesTables(HistoryTable.TABLE)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .build()
.prepare() )
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
/** /**
* Returns history of recent manga containing last read chapter in 25s * Returns history of recent manga containing last read chapter in 25s
@ -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(
.query(getRecentAdditionsQuery(search.sqLite, endless)) RawQuery.builder()
.args(date.time) .query(getRecentAdditionsQuery(search.sqLite, endless))
.observesTables(MangaTable.TABLE) .args(date.time)
.build()) .observesTables(MangaTable.TABLE)
.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(
.query(getRecentMangasLimitQuery(limit, search.sqLite)) RawQuery.builder()
.args(date.time) .query(getRecentMangasLimitQuery(limit, search.sqLite))
.observesTables(HistoryTable.TABLE) .args(date.time)
.build()) .observesTables(HistoryTable.TABLE)
.build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare() .prepare()
@ -72,31 +78,37 @@ 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(
.query(getRecentReadWithUnreadChapters(search.sqLite, endless)) RawQuery.builder()
.args(date.time) .query(getRecentReadWithUnreadChapters(search.sqLite, endless))
.observesTables(HistoryTable.TABLE) .args(date.time)
.build()) .observesTables(HistoryTable.TABLE)
.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(
.query(getHistoryByMangaId()) RawQuery.builder()
.args(mangaId) .query(getHistoryByMangaId())
.observesTables(HistoryTable.TABLE) .args(mangaId)
.build()) .observesTables(HistoryTable.TABLE)
.prepare() .build()
)
.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(
.query(getHistoryByChapterUrl()) RawQuery.builder()
.args(chapterUrl) .query(getHistoryByChapterUrl())
.observesTables(HistoryTable.TABLE) .args(chapterUrl)
.build()) .observesTables(HistoryTable.TABLE)
.prepare() .build()
)
.prepare()
/** /**
* Updates the history last read. * Updates the history last read.
@ -104,9 +116,9 @@ interface HistoryQueries : DbProvider {
* @param history history object * @param history history object
*/ */
fun updateHistoryLastRead(history: History) = db.put() fun updateHistoryLastRead(history: History) = db.put()
.`object`(history) .`object`(history)
.withPutResolver(HistoryLastReadPutResolver()) .withPutResolver(HistoryLastReadPutResolver())
.prepare() .prepare()
/** /**
* Updates the history last read. * Updates the history last read.
@ -114,21 +126,25 @@ interface HistoryQueries : DbProvider {
* @param historyList history object list * @param historyList history object list
*/ */
fun updateHistoryLastRead(historyList: List<History>) = db.put() fun updateHistoryLastRead(historyList: List<History>) = db.put()
.objects(historyList) .objects(historyList)
.withPutResolver(HistoryLastReadPutResolver()) .withPutResolver(HistoryLastReadPutResolver())
.prepare() .prepare()
fun deleteHistory() = db.delete() fun deleteHistory() = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
.table(HistoryTable.TABLE) DeleteQuery.builder()
.build()) .table(HistoryTable.TABLE)
.prepare() .build()
)
.prepare()
fun deleteHistoryNoLastRead() = db.delete() fun deleteHistoryNoLastRead() = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
.table(HistoryTable.TABLE) DeleteQuery.builder()
.where("${HistoryTable.COL_LAST_READ} = ?") .table(HistoryTable.TABLE)
.whereArgs(0) .where("${HistoryTable.COL_LAST_READ} = ?")
.build()) .whereArgs(0)
.prepare() .build()
)
.prepare()
} }

View File

@ -15,12 +15,14 @@ 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(
.table(MangaCategoryTable.TABLE) DeleteQuery.builder()
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})") .table(MangaCategoryTable.TABLE)
.whereArgs(*mangas.map { it.id }.toTypedArray()) .where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
.build()) .whereArgs(*mangas.map { it.id }.toTypedArray())
.prepare() .build()
)
.prepare()
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) { fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
db.inTransaction { db.inTransaction {

View File

@ -22,67 +22,77 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable
interface MangaQueries : DbProvider { interface MangaQueries : DbProvider {
fun getMangas() = db.get() fun getMangas() = db.get()
.listOfObjects(Manga::class.java) .listOfObjects(Manga::class.java)
.withQuery(Query.builder() .withQuery(
.table(MangaTable.TABLE) Query.builder()
.build()) .table(MangaTable.TABLE)
.prepare() .build()
)
.prepare()
fun getLibraryMangas() = db.get() fun getLibraryMangas() = db.get()
.listOfObjects(LibraryManga::class.java) .listOfObjects(LibraryManga::class.java)
.withQuery(RawQuery.builder() .withQuery(
.query(libraryQuery) RawQuery.builder()
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE) .query(libraryQuery)
.build()) .observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
.withGetResolver(LibraryMangaGetResolver.INSTANCE) .build()
.prepare() )
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
.prepare()
fun getFavoriteMangas() = db.get() fun getFavoriteMangas() = db.get()
.listOfObjects(Manga::class.java) .listOfObjects(Manga::class.java)
.withQuery(Query.builder() .withQuery(
.table(MangaTable.TABLE) Query.builder()
.where("${MangaTable.COL_FAVORITE} = ?") .table(MangaTable.TABLE)
.whereArgs(1) .where("${MangaTable.COL_FAVORITE} = ?")
.orderBy(MangaTable.COL_TITLE) .whereArgs(1)
.build()) .orderBy(MangaTable.COL_TITLE)
.prepare() .build()
)
.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(
.table(MangaTable.TABLE) Query.builder()
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?") .table(MangaTable.TABLE)
.whereArgs(url, sourceId) .where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
.build()) .whereArgs(url, sourceId)
.prepare() .build()
)
.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(
.table(MangaTable.TABLE) Query.builder()
.where("${MangaTable.COL_ID} = ?") .table(MangaTable.TABLE)
.whereArgs(id) .where("${MangaTable.COL_ID} = ?")
.build()) .whereArgs(id)
.prepare() .build()
)
.prepare()
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare() fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare() fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
fun updateFlags(manga: Manga) = db.put() fun updateFlags(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaFlagsPutResolver()) .withPutResolver(MangaFlagsPutResolver())
.prepare() .prepare()
fun updateLastUpdated(manga: Manga) = db.put() fun updateLastUpdated(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaLastUpdatedPutResolver()) .withPutResolver(MangaLastUpdatedPutResolver())
.prepare() .prepare()
fun updateMangaFavorite(manga: Manga) = db.put() fun updateMangaFavorite(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaFavoritePutResolver()) .withPutResolver(MangaFavoritePutResolver())
.prepare() .prepare()
fun updateMangaAdded(manga: Manga) = db.put() fun updateMangaAdded(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
@ -90,14 +100,14 @@ interface MangaQueries : DbProvider {
.prepare() .prepare()
fun updateMangaViewer(manga: Manga) = db.put() fun updateMangaViewer(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaViewerPutResolver()) .withPutResolver(MangaViewerPutResolver())
.prepare() .prepare()
fun updateMangaTitle(manga: Manga) = db.put() fun updateMangaTitle(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaTitlePutResolver()) .withPutResolver(MangaTitlePutResolver())
.prepare() .prepare()
fun updateMangaInfo(manga: Manga) = db.put() fun updateMangaInfo(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
@ -114,27 +124,33 @@ 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(
.table(MangaTable.TABLE) DeleteQuery.builder()
.where("${MangaTable.COL_FAVORITE} = ?") .table(MangaTable.TABLE)
.whereArgs(0) .where("${MangaTable.COL_FAVORITE} = ?")
.build()) .whereArgs(0)
.prepare() .build()
)
.prepare()
fun deleteMangas() = db.delete() fun deleteMangas() = db.delete()
.byQuery(DeleteQuery.builder() .byQuery(
.table(MangaTable.TABLE) DeleteQuery.builder()
.build()) .table(MangaTable.TABLE)
.prepare() .build()
)
.prepare()
fun getLastReadManga() = db.get() fun getLastReadManga() = db.get()
.listOfObjects(Manga::class.java) .listOfObjects(Manga::class.java)
.withQuery(RawQuery.builder() .withQuery(
.query(getLastReadMangaQuery()) RawQuery.builder()
.observesTables(MangaTable.TABLE) .query(getLastReadMangaQuery())
.build()) .observesTables(MangaTable.TABLE)
.prepare() .build()
)
.prepare()
fun getTotalChapterManga() = db.get().listOfObjects(Manga::class.java) fun getTotalChapterManga() = db.get().listOfObjects(Manga::class.java)
.withQuery(RawQuery.builder().query(getTotalChapterMangaQuery()).observesTables(MangaTable.TABLE).build()).prepare() .withQuery(RawQuery.builder().query(getTotalChapterMangaQuery()).observesTables(MangaTable.TABLE).build()).prepare()
} }

View File

@ -9,7 +9,8 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
/** /**
* Query to get the manga from the library, with their categories and unread count. * Query to get the manga from the library, with their categories and unread count.
*/ */
val libraryQuery = """ val libraryQuery =
"""
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY} SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
FROM ( FROM (
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, 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(
.table(SearchMetadataTable.TABLE) Query.builder()
.where("${SearchMetadataTable.COL_MANGA_ID} = ?") .table(SearchMetadataTable.TABLE)
.whereArgs(mangaId) .where("${SearchMetadataTable.COL_MANGA_ID} = ?")
.build()) .whereArgs(mangaId)
.build()
)
.prepare() .prepare()
fun getSearchMetadata() = db.get() fun getSearchMetadata() = db.get()
.listOfObjects(SearchMetadata::class.java) .listOfObjects(SearchMetadata::class.java)
.withQuery(Query.builder() .withQuery(
.table(SearchMetadataTable.TABLE) Query.builder()
.build()) .table(SearchMetadataTable.TABLE)
.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(
.table(SearchMetadataTable.TABLE) Query.builder()
.where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?") .table(SearchMetadataTable.TABLE)
.whereArgs(extra) .where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?")
.build()) .whereArgs(extra)
.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(
.table(SearchMetadataTable.TABLE) DeleteQuery.builder()
.build()) .table(SearchMetadataTable.TABLE)
.build()
)
.prepare() .prepare()
} }

View File

@ -11,23 +11,27 @@ import eu.kanade.tachiyomi.data.track.TrackService
interface TrackQueries : DbProvider { 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(
.table(TrackTable.TABLE) Query.builder()
.where("${TrackTable.COL_MANGA_ID} = ?") .table(TrackTable.TABLE)
.whereArgs(manga.id) .where("${TrackTable.COL_MANGA_ID} = ?")
.build()) .whereArgs(manga.id)
.prepare() .build()
)
.prepare()
fun insertTrack(track: Track) = db.put().`object`(track).prepare() fun insertTrack(track: Track) = db.put().`object`(track).prepare()
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(
.table(TrackTable.TABLE) DeleteQuery.builder()
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?") .table(TrackTable.TABLE)
.whereArgs(manga.id, sync.id) .where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
.build()) .whereArgs(manga.id, sync.id)
.prepare() .build()
)
.prepare()
} }

View File

@ -20,10 +20,10 @@ class ChapterBackupPutResolver : PutResolver<Chapter>() {
} }
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder() fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
.table(ChapterTable.TABLE) .table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?") .where("${ChapterTable.COL_URL} = ?")
.whereArgs(chapter.url) .whereArgs(chapter.url)
.build() .build()
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply { fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
put(ChapterTable.COL_READ, chapter.read) put(ChapterTable.COL_READ, chapter.read)

View File

@ -20,10 +20,10 @@ class ChapterProgressPutResolver : PutResolver<Chapter>() {
} }
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder() fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
.table(ChapterTable.TABLE) .table(ChapterTable.TABLE)
.where("${ChapterTable.COL_ID} = ?") .where("${ChapterTable.COL_ID} = ?")
.whereArgs(chapter.id) .whereArgs(chapter.id)
.build() .build()
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply { fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
put(ChapterTable.COL_READ, chapter.read) put(ChapterTable.COL_READ, chapter.read)

View File

@ -20,10 +20,10 @@ class ChapterSourceOrderPutResolver : PutResolver<Chapter>() {
} }
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder() fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.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(chapter.url, chapter.manga_id) .whereArgs(chapter.url, chapter.manga_id)
.build() .build()
fun mapToContentValues(chapter: Chapter) = ContentValues(1).apply { fun mapToContentValues(chapter: Chapter) = ContentValues(1).apply {
put(ChapterTable.COL_SOURCE_ORDER, chapter.source_order) put(ChapterTable.COL_SOURCE_ORDER, chapter.source_order)

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
@ -48,10 +50,10 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
* @param obj history object * @param obj history object
*/ */
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder() override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
.table(HistoryTable.TABLE) .table(HistoryTable.TABLE)
.where("${HistoryTable.COL_CHAPTER_ID} = ?") .where("${HistoryTable.COL_CHAPTER_ID} = ?")
.whereArgs(obj.chapter_id) .whereArgs(obj.chapter_id)
.build() .build()
/** /**
* Create content query * Create content query

View File

@ -42,8 +42,8 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>()
val chapter = val chapter =
if (!cursor.isNull(cursor.getColumnIndex(ChapterTable.COL_MANGA_ID))) chapterResolver if (!cursor.isNull(cursor.getColumnIndex(ChapterTable.COL_MANGA_ID))) chapterResolver
.mapFromCursor( .mapFromCursor(
cursor cursor
) else ChapterImpl() ) else ChapterImpl()
// Get history object // Get history object
val history = val history =

View File

@ -20,10 +20,10 @@ class MangaFavoritePutResolver : PutResolver<Manga>() {
} }
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?") .where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id) .whereArgs(manga.id)
.build() .build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply { fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_FAVORITE, manga.favorite) put(MangaTable.COL_FAVORITE, manga.favorite)

View File

@ -20,10 +20,10 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
} }
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?") .where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id) .whereArgs(manga.id)
.build() .build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply { fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags) put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)

View File

@ -20,10 +20,10 @@ class MangaLastUpdatedPutResolver : PutResolver<Manga>() {
} }
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?") .where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id) .whereArgs(manga.id)
.build() .build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply { fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_LAST_UPDATE, manga.last_update) put(MangaTable.COL_LAST_UPDATE, manga.last_update)

View File

@ -20,10 +20,10 @@ class MangaTitlePutResolver : PutResolver<Manga>() {
} }
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?") .where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id) .whereArgs(manga.id)
.build() .build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply { fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_TITLE, manga.title) put(MangaTable.COL_TITLE, manga.title)

View File

@ -20,10 +20,10 @@ class MangaViewerPutResolver : PutResolver<Manga>() {
} }
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?") .where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id) .whereArgs(manga.id)
.build() .build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply { fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_VIEWER, manga.viewer) put(MangaTable.COL_VIEWER, manga.viewer)

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,
@ -54,7 +55,7 @@ object ChapterTable {
val createUnreadChaptersIndexQuery: String val createUnreadChaptersIndexQuery: String
get() = "CREATE INDEX ${TABLE}_unread_by_manga_index ON $TABLE($COL_MANGA_ID, $COL_READ) " + get() = "CREATE INDEX ${TABLE}_unread_by_manga_index ON $TABLE($COL_MANGA_ID, $COL_READ) " +
"WHERE $COL_READ = 0" "WHERE $COL_READ = 0"
val sourceOrderUpdateQuery: String val sourceOrderUpdateQuery: String
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SOURCE_ORDER INTEGER DEFAULT 0" get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SOURCE_ORDER INTEGER DEFAULT 0"

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,
@ -71,7 +72,7 @@ object MangaTable {
val createLibraryIndexQuery: String val createLibraryIndexQuery: String
get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " + get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " +
"WHERE $COL_FAVORITE = 1" "WHERE $COL_FAVORITE = 1"
val addHideTitle: String val addHideTitle: String
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_HIDE_TITLE INTEGER DEFAULT 0" get() = "ALTER TABLE $TABLE ADD COLUMN $COL_HIDE_TITLE INTEGER DEFAULT 0"

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

@ -24,7 +24,7 @@ internal class DownloadNotifier(private val context: Context) {
*/ */
private val notification by lazy { private val notification by lazy {
NotificationCompat.Builder(context, Notifications.CHANNEL_DOWNLOADER) NotificationCompat.Builder(context, Notifications.CHANNEL_DOWNLOADER)
.setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)) .setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
} }
/** /**
@ -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(
context.getString(R.string.pause), R.drawable.ic_pause_24dp,
NotificationReceiver.pauseDownloadsPendingBroadcast(context)) context.getString(R.string.pause),
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

@ -161,13 +161,17 @@ class DownloadService : Service() {
*/ */
private fun listenNetworkChanges() { private fun listenNetworkChanges() {
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

@ -80,9 +80,9 @@ class DownloadStore(
*/ */
fun restore(): List<Download> { fun restore(): List<Download> {
val objs = preferences.all val objs = preferences.all
.mapNotNull { it.value as? String } .mapNotNull { it.value as? String }
.mapNotNull { deserialize(it) } .mapNotNull { deserialize(it) }
.sortedBy { it.order } .sortedBy { it.order }
val downloads = mutableListOf<Download>() val downloads = mutableListOf<Download>()
if (objs.isNotEmpty()) { if (objs.isNotEmpty()) {

View File

@ -288,23 +288,23 @@ class Downloader(
val pageListObservable = if (download.pages == null) { val pageListObservable = if (download.pages == null) {
// Pull page list from network and add them to download object // Pull page list from network and add them to download object
download.source.fetchPageList(download.chapter).doOnNext { pages -> download.source.fetchPageList(download.chapter).doOnNext { pages ->
if (pages.isEmpty()) { if (pages.isEmpty()) {
throw Exception("Page list is empty") throw Exception("Page list is empty")
}
download.pages = pages
} }
download.pages = pages
}
} else { } else {
// Or if the page list already exists, start from the file // Or if the page list already exists, start from the file
Observable.just(download.pages!!) Observable.just(download.pages!!)
} }
pageListObservable.doOnNext { _ -> pageListObservable.doOnNext { _ ->
// Delete all temporary (unfinished) files // Delete all temporary (unfinished) files
tmpDir.listFiles()?.filter { it.name!!.endsWith(".tmp") }?.forEach { it.delete() } tmpDir.listFiles()?.filter { it.name!!.endsWith(".tmp") }?.forEach { it.delete() }
download.downloadedImages = 0 download.downloadedImages = 0
download.status = Download.DOWNLOADING download.status = Download.DOWNLOADING
} }
// Get all the URLs to the source images, fetch pages if necessary // Get all the URLs to the source images, fetch pages if necessary
.flatMap { download.source.fetchAllImageUrlsFromPageList(it) } .flatMap { download.source.fetchAllImageUrlsFromPageList(it) }
// Start downloading images, consider we can have downloaded images already // Start downloading images, consider we can have downloaded images already
@ -447,7 +447,7 @@ class Downloader(
private fun getImageExtension(response: Response, file: UniFile): String { private fun getImageExtension(response: Response, file: UniFile): String {
// Read content type if available. // Read content type if available.
val mime = response.body?.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" } val mime = response.body?.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" }
// Else guess from the uri. // Else guess from the uri.
?: context.contentResolver.getType(file.uri) ?: context.contentResolver.getType(file.uri)
// Else read magic numbers. // Else read magic numbers.
?: ImageUtil.findImageType { file.openInputStream() }?.mime ?: ImageUtil.findImageType { file.openInputStream() }?.mime

View File

@ -13,7 +13,7 @@ class DownloadQueue(
private val store: DownloadStore, private val store: DownloadStore,
private val queue: MutableList<Download> = CopyOnWriteArrayList<Download>() private val queue: MutableList<Download> = CopyOnWriteArrayList<Download>()
) : ) :
List<Download> by queue { List<Download> by queue {
private val statusSubject = PublishSubject.create<Download>() private val statusSubject = PublishSubject.create<Download>()
@ -80,8 +80,8 @@ List<Download> by queue {
fun getStatusObservable(): Observable<Download> = statusSubject.onBackpressureBuffer() fun getStatusObservable(): Observable<Download> = statusSubject.onBackpressureBuffer()
fun getUpdatedObservable(): Observable<List<Download>> = updatedRelay.onBackpressureBuffer() fun getUpdatedObservable(): Observable<List<Download>> = updatedRelay.onBackpressureBuffer()
.startWith(Unit) .startWith(Unit)
.map { this } .map { this }
private fun setPagesFor(download: Download) { private fun setPagesFor(download: Download) {
if (download.status == Download.DOWNLOADING) { if (download.status == Download.DOWNLOADING) {
@ -105,23 +105,23 @@ List<Download> by queue {
fun getProgressObservable(): Observable<Download> { fun getProgressObservable(): Observable<Download> {
return statusSubject.onBackpressureBuffer() return statusSubject.onBackpressureBuffer()
.startWith(getActiveDownloads()) .startWith(getActiveDownloads())
.flatMap { download -> .flatMap { download ->
if (download.status == Download.DOWNLOADING) { if (download.status == Download.DOWNLOADING) {
val pageStatusSubject = PublishSubject.create<Int>() val pageStatusSubject = PublishSubject.create<Int>()
setPagesSubject(download.pages, pageStatusSubject) setPagesSubject(download.pages, pageStatusSubject)
downloadListeners.forEach { it.updateDownload(download) } downloadListeners.forEach { it.updateDownload(download) }
return@flatMap pageStatusSubject return@flatMap pageStatusSubject
.onBackpressureBuffer() .onBackpressureBuffer()
.filter { it == Page.READY } .filter { it == Page.READY }
.map { download } .map { download }
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) { } else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
setPagesSubject(download.pages, null) setPagesSubject(download.pages, null)
downloadListeners.forEach { it.updateDownload(download) } downloadListeners.forEach { it.updateDownload(download) }
}
Observable.just(download)
} }
.filter { it.status == Download.DOWNLOADING } Observable.just(download)
}
.filter { it.status == Download.DOWNLOADING }
} }
private fun setPagesSubject(pages: List<Page>?, subject: PublishSubject<Int>?) { private fun setPagesSubject(pages: List<Page>?, subject: PublishSubject<Int>?) {

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

@ -15,7 +15,7 @@ import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) : class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) { Worker(context, workerParams) {
override fun doWork(): Result { override fun doWork(): Result {
LibraryUpdateService.start(context) LibraryUpdateService.start(context)
@ -37,16 +37,19 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
NetworkType.CONNECTED NetworkType.CONNECTED
val constraints = Constraints.Builder() val constraints = Constraints.Builder()
.setRequiredNetworkType(wifiRestriction) .setRequiredNetworkType(wifiRestriction)
.setRequiresCharging(acRestriction) .setRequiresCharging(acRestriction)
.build() .build()
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>( val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
interval.toLong(), TimeUnit.HOURS, interval.toLong(),
10, TimeUnit.MINUTES) TimeUnit.HOURS,
.addTag(TAG) 10,
.setConstraints(constraints) TimeUnit.MINUTES
.build() )
.addTag(TAG)
.setConstraints(constraints)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
} else { } else {

View File

@ -131,60 +131,73 @@ 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(
setSmallIcon(R.drawable.ic_tachi) Pair(
try { context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
val request = GetRequest.Builder(context).data(manga) setSmallIcon(R.drawable.ic_tachi)
.networkCachePolicy(CachePolicy.DISABLED) try {
.transformations(CircleCropTransformation()) val request = GetRequest.Builder(context).data(manga)
.size(width = ICON_SIZE, height = ICON_SIZE).build() .networkCachePolicy(CachePolicy.DISABLED)
.transformations(CircleCropTransformation())
.size(width = ICON_SIZE, height = ICON_SIZE).build()
Coil.imageLoader(context).execute(request).drawable?.let { drawable -> Coil.imageLoader(context).execute(request).drawable?.let { drawable ->
setLargeIcon((drawable as BitmapDrawable).bitmap) setLargeIcon((drawable as BitmapDrawable).bitmap)
}
} catch (e: Exception) {
} }
} catch (e: Exception) { setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
} setContentTitle(manga.title)
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) color = ContextCompat.getColor(context, R.color.colorAccent)
setContentTitle(manga.title) val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
color = ContextCompat.getColor(context, R.color.colorAccent) "${chapterNames.take(MAX_CHAPTERS - 1).joinToString(", ")}, " +
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) { context.resources.getQuantityString(
"${chapterNames.take(MAX_CHAPTERS - 1) R.plurals.notification_and_n_more,
.joinToString(", ")}, " + context.resources.getQuantityString( (chapterNames.size - (MAX_CHAPTERS - 1)),
R.plurals.notification_and_n_more, (chapterNames.size - (MAX_CHAPTERS - 1))
(chapterNames.size - (MAX_CHAPTERS - 1)), )
(chapterNames.size - (MAX_CHAPTERS - 1)) } else chapterNames.joinToString(", ")
) setContentText(chaptersNames)
} else chapterNames.joinToString(", ") setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
setContentText(chaptersNames) priority = NotificationCompat.PRIORITY_HIGH
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames)) setGroup(Notifications.GROUP_NEW_CHAPTERS)
priority = NotificationCompat.PRIORITY_HIGH setContentIntent(
setGroup(Notifications.GROUP_NEW_CHAPTERS) NotificationReceiver.openChapterPendingActivity(
setContentIntent( context,
NotificationReceiver.openChapterPendingActivity( manga,
context, manga, chapters.first() chapters.first()
) )
)
addAction(
R.drawable.ic_eye_24dp,
context.getString(R.string.mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast(
context,
manga,
chapters,
Notifications.ID_NEW_CHAPTERS
)
)
addAction(
R.drawable.ic_book_24dp,
context.getString(R.string.view_chapters),
NotificationReceiver.openChapterPendingActivity(
context,
manga,
Notifications.ID_NEW_CHAPTERS
)
)
setAutoCancel(true)
},
manga.id.hashCode()
) )
addAction( )
R.drawable.ic_eye_24dp,
context.getString(R.string.mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast(
context, manga, chapters, Notifications.ID_NEW_CHAPTERS
)
)
addAction(
R.drawable.ic_book_24dp,
context.getString(R.string.view_chapters),
NotificationReceiver.openChapterPendingActivity(
context, manga, Notifications.ID_NEW_CHAPTERS
)
)
setAutoCancel(true)
}, 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(
it.title.chop(45) updates.keys.joinToString("\n") {
}) 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

@ -8,8 +8,9 @@ import eu.kanade.tachiyomi.data.database.models.Manga
object LibraryUpdateRanker { 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.
@ -22,7 +23,7 @@ object LibraryUpdateRanker {
*/ */
fun latestFirstRanking(): Comparator<Manga> { fun latestFirstRanking(): Comparator<Manga> {
return Comparator { mangaFirst: Manga, return Comparator { mangaFirst: Manga,
mangaSecond: Manga -> mangaSecond: Manga ->
compareValues(mangaSecond.last_update, mangaFirst.last_update) compareValues(mangaSecond.last_update, mangaFirst.last_update)
} }
} }
@ -35,7 +36,7 @@ object LibraryUpdateRanker {
*/ */
fun lexicographicRanking(): Comparator<Manga> { fun lexicographicRanking(): Comparator<Manga> {
return Comparator { mangaFirst: Manga, return Comparator { mangaFirst: Manga,
mangaSecond: Manga -> mangaSecond: Manga ->
compareValues(mangaFirst.title, mangaSecond.title) compareValues(mangaFirst.title, mangaSecond.title)
} }
} }

View File

@ -134,16 +134,18 @@ 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 = (
val mangas = db.getLibraryMangas().executeAsBlocking().filter { if (savedMangasList != null) {
it.id in savedMangasList val mangas = db.getLibraryMangas().executeAsBlocking().filter {
}.distinctBy { it.id } it.id in savedMangasList
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1) }.distinctBy { it.id }
if (categoryId > -1) categoryIds.add(categoryId) val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
mangas if (categoryId > -1) categoryIds.add(categoryId)
} else { mangas
getMangaToUpdate(intent, target) } else {
}).sortedWith(rankingScheme[selectedScheme]) getMangaToUpdate(intent, target)
}
).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++
@ -372,47 +379,47 @@ class LibraryUpdateService(
shouldDownload: Boolean shouldDownload: Boolean
): ):
Boolean { Boolean {
try { try {
var hasDownloads = false var hasDownloads = false
if (job?.isCancelled == true) { if (job?.isCancelled == true) {
return false
}
notifier.showProgressNotification(manga, progress, mangaToUpdate.size)
val source = sourceManager.get(manga.source) as? HttpSource ?: return false
val fetchedChapters = withContext(Dispatchers.IO) {
source.fetchChapterList(manga).toBlocking().single()
} ?: emptyList()
if (fetchedChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
if (newChapters.first.isNotEmpty()) {
if (shouldDownload) {
downloadChapters(manga, newChapters.first.sortedBy { it.chapter_number })
hasDownloads = true
}
newUpdates[manga] =
newChapters.first.sortedBy { it.chapter_number }.toTypedArray()
}
if (deleteRemoved && newChapters.second.isNotEmpty()) {
val removedChapters = newChapters.second.filter {
downloadManager.isChapterDownloaded(it, manga)
}
if (removedChapters.isNotEmpty()) {
downloadManager.deleteChapters(removedChapters, manga, source)
}
}
if (newChapters.first.size + newChapters.second.size > 0) listener?.onUpdateManga(
manga
)
}
return hasDownloads
} catch (e: Exception) {
if (e !is CancellationException) {
failedUpdates[manga] = e.message
Timber.e("Failed updating: ${manga.title}: $e")
}
return false return false
} }
notifier.showProgressNotification(manga, progress, mangaToUpdate.size)
val source = sourceManager.get(manga.source) as? HttpSource ?: return false
val fetchedChapters = withContext(Dispatchers.IO) {
source.fetchChapterList(manga).toBlocking().single()
} ?: emptyList()
if (fetchedChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
if (newChapters.first.isNotEmpty()) {
if (shouldDownload) {
downloadChapters(manga, newChapters.first.sortedBy { it.chapter_number })
hasDownloads = true
}
newUpdates[manga] =
newChapters.first.sortedBy { it.chapter_number }.toTypedArray()
}
if (deleteRemoved && newChapters.second.isNotEmpty()) {
val removedChapters = newChapters.second.filter {
downloadManager.isChapterDownloaded(it, manga)
}
if (removedChapters.isNotEmpty()) {
downloadManager.deleteChapters(removedChapters, manga, source)
}
}
if (newChapters.first.size + newChapters.second.size > 0) listener?.onUpdateManga(
manga
)
}
return hasDownloads
} catch (e: Exception) {
if (e !is CancellationException) {
failedUpdates[manga] = e.message
Timber.e("Failed updating: ${manga.title}: $e")
}
return false
} }
}
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) { private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
// We don't want to start downloading while the library is updating, because websites // We don't want to start downloading while the library is updating, because websites

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)
@ -342,7 +354,7 @@ class NotificationReceiver : BroadcastReceiver() {
context: Context, context: Context,
notificationId: Int, notificationId: Int,
groupId: Int? = groupId: Int? =
null null
) { ) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val groupKey = context.notificationManager.activeNotifications.find { val groupKey = context.notificationManager.activeNotifications.find {
@ -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
)
} }
/** /**
@ -409,11 +426,16 @@ class NotificationReceiver : BroadcastReceiver() {
context: Context, context: Context,
manga: Manga, manga: Manga,
chapter: chapter:
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
)
} }
/** /**
@ -424,16 +446,19 @@ class NotificationReceiver : BroadcastReceiver() {
*/ */
internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int): internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int):
PendingIntent { PendingIntent {
val newIntent = val newIntent =
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA) Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
.putExtra(MangaDetailsController.MANGA_EXTRA, manga.id) .putExtra(MangaDetailsController.MANGA_EXTRA, manga.id)
.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
)
}
/** /**
* Returns [PendingIntent] that opens the error log file in an external viewer * Returns [PendingIntent] that opens the error log file in an external viewer
@ -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
) )
} }
@ -473,7 +501,7 @@ class NotificationReceiver : BroadcastReceiver() {
val toLaunch = Intent(Intent.ACTION_VIEW).apply { val toLaunch = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "text/plain") setDataAndType(uri, "text/plain")
flags = Intent.FLAG_ACTIVITY_NEW_TASK or flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION
} }
return PendingIntent.getActivity(context, 0, toLaunch, 0) return PendingIntent.getActivity(context, 0, toLaunch, 0)
} }
@ -488,19 +516,19 @@ class NotificationReceiver : BroadcastReceiver() {
context: Context, context: Context,
manga: Manga, manga: Manga,
chapters: chapters:
Array<Chapter>, Array<Chapter>,
groupId: Int groupId: Int
): ):
PendingIntent { PendingIntent {
val newIntent = Intent(context, NotificationReceiver::class.java).apply { val newIntent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_MARK_AS_READ action = ACTION_MARK_AS_READ
putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray()) putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())
putExtra(EXTRA_MANGA_ID, manga.id) putExtra(EXTRA_MANGA_ID, manga.id)
putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode()) putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode())
putExtra(EXTRA_GROUP_ID, groupId) putExtra(EXTRA_GROUP_ID, groupId)
}
return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/** /**
* Returns [PendingIntent] that starts a service which stops the library update * Returns [PendingIntent] that starts a service which stops the library update

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(
CHANNEL_COMMON, NotificationChannel(
context.getString(R.string.common), CHANNEL_COMMON,
NotificationManager.IMPORTANCE_LOW context.getString(R.string.common),
), NotificationChannel( NotificationManager.IMPORTANCE_LOW
CHANNEL_LIBRARY, ),
context.getString(R.string.updating_library), NotificationChannel(
NotificationManager.IMPORTANCE_LOW CHANNEL_LIBRARY,
).apply { context.getString(R.string.updating_library),
setShowBadge(false) NotificationManager.IMPORTANCE_LOW
}, NotificationChannel( ).apply {
CHANNEL_DOWNLOADER, setShowBadge(false)
context.getString(R.string.downloads), },
NotificationManager.IMPORTANCE_LOW NotificationChannel(
).apply { CHANNEL_DOWNLOADER,
setShowBadge(false) context.getString(R.string.downloads),
}, NotificationChannel( NotificationManager.IMPORTANCE_LOW
CHANNEL_UPDATES_TO_EXTS, ).apply {
context.getString(R.string.extension_updates), setShowBadge(false)
NotificationManager.IMPORTANCE_DEFAULT },
), NotificationChannel( NotificationChannel(
CHANNEL_NEW_CHAPTERS, CHANNEL_UPDATES_TO_EXTS,
context.getString(R.string.new_chapters), context.getString(R.string.extension_updates),
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
), NotificationChannel( ),
CHANNEL_BACKUP_RESTORE, NotificationChannel(
context.getString(R.string.restoring_backup), CHANNEL_NEW_CHAPTERS,
NotificationManager.IMPORTANCE_LOW context.getString(R.string.new_chapters),
).apply { NotificationManager.IMPORTANCE_DEFAULT
setShowBadge(false) ),
}) NotificationChannel(
CHANNEL_BACKUP_RESTORE,
context.getString(R.string.restoring_backup),
NotificationManager.IMPORTANCE_LOW
).apply {
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)
@ -130,9 +138,9 @@ class PreferencesHelper(val context: Context) {
fun setTrackCredentials(sync: TrackService, username: String, password: String) { fun setTrackCredentials(sync: TrackService, username: String, password: String) {
prefs.edit() prefs.edit()
.putString(Keys.trackUsername(sync.id), username) .putString(Keys.trackUsername(sync.id), username)
.putString(Keys.trackPassword(sync.id), password) .putString(Keys.trackPassword(sync.id), password)
.apply() .apply()
} }
fun trackToken(sync: TrackService) = rxPrefs.getString(Keys.trackToken(sync.id), "") fun trackToken(sync: TrackService) = rxPrefs.getString(Keys.trackToken(sync.id), "")

View File

@ -60,7 +60,7 @@ abstract class TrackService(val id: Int) {
open val isLogged: Boolean open val isLogged: Boolean
get() = getUsername().isNotEmpty() && get() = getUsername().isNotEmpty() &&
getPassword().isNotEmpty() getPassword().isNotEmpty()
fun getUsername() = preferences.trackUsername(this)!! fun getUsername() = preferences.trackUsername(this)!!

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

@ -38,8 +38,8 @@ class AnilistInterceptor(private val anilist: Anilist, private var token: String
// Add the authorization header to the original request. // Add the authorization header to the original request.
val authRequest = originalRequest.newBuilder() val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}") .addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.build() .build()
return chain.proceed(authRequest) return chain.proceed(authRequest)
} }

View File

@ -36,25 +36,28 @@ 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()
.build() else originalRequest.newBuilder() .addQueryParameter("access_token", currAuth.access_token).build()
.post(addTocken(currAuth.access_token, originalRequest.body as FormBody)) )
.header("User-Agent", "Tachiyomi") .build() else originalRequest.newBuilder()
.build() .post(addTocken(currAuth.access_token, originalRequest.body as FormBody))
.header("User-Agent", "Tachiyomi")
.build()
return chain.proceed(authRequest) return chain.proceed(authRequest)
} }
fun newAuth(oauth: OAuth) { fun newAuth(oauth: OAuth) {
this.oauth = OAuth( this.oauth = OAuth(
oauth.access_token, oauth.access_token,
oauth.token_type, oauth.token_type,
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

@ -20,8 +20,8 @@ data class OAuth(
val refresh_token: String?, val refresh_token: String?,
val user_id: Long? val user_id: Long?
) { ) {
// Access token refresh before expired // Access token refresh before expired
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600) fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
} }
data class Status( data class Status(

View File

@ -30,10 +30,10 @@ class KitsuInterceptor(val kitsu: Kitsu, val gson: Gson) : Interceptor {
// Add the authorization header to the original request. // Add the authorization header to the original request.
val authRequest = originalRequest.newBuilder() val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}") .addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.header("Accept", "application/vnd.api+json") .header("Accept", "application/vnd.api+json")
.header("Content-Type", "application/vnd.api+json") .header("Content-Type", "application/vnd.api+json")
.build() .build()
return chain.proceed(authRequest) return chain.proceed(authRequest)
} }

View File

@ -103,7 +103,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
val request = Request.Builder().url(url.toString()).get().build() val request = Request.Builder().url(url.toString()).get().build()
val urlMangas = "$apiUrl/mangas".toUri().buildUpon().appendPath(track.media_id.toString()) val urlMangas = "$apiUrl/mangas".toUri().buildUpon().appendPath(track.media_id.toString())
.build() .build()
val requestMangas = Request.Builder().url(urlMangas.toString()).get().build() val requestMangas = Request.Builder().url(urlMangas.toString()).get().build()
val requestMangasResponse = authClient.newCall(requestMangas).execute() val requestMangasResponse = authClient.newCall(requestMangas).execute()

View File

@ -29,9 +29,9 @@ class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Intercept
} }
// Add the authorization header to the original request. // Add the authorization header to the original request.
val authRequest = originalRequest.newBuilder() val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}") .addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.header("User-Agent", "Tachiyomi") .header("User-Agent", "Tachiyomi")
.build() .build()
return chain.proceed(authRequest) return chain.proceed(authRequest)
} }

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
) )
) )
} }
@ -62,15 +65,18 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
fun setupTask() { fun setupTask() {
val constraints = Constraints.Builder() val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.build() .build()
val request = PeriodicWorkRequestBuilder<UpdaterJob>( val request = PeriodicWorkRequestBuilder<UpdaterJob>(
1, TimeUnit.DAYS, 1,
1, TimeUnit.HOURS) TimeUnit.DAYS,
.addTag(TAG) 1,
.setConstraints(constraints) TimeUnit.HOURS
.build() )
.addTag(TAG)
.setConstraints(constraints)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
} }

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(
context.getString(R.string.install), R.drawable.ic_system_update_24dp,
NotificationHandler.installApkPendingActivity(context, uri)) context.getString(R.string.install),
NotificationHandler.installApkPendingActivity(context, uri)
)
// Cancel action // Cancel action
addAction(R.drawable.ic_close_24dp, addAction(
context.getString(R.string.cancel), R.drawable.ic_close_24dp,
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)) context.getString(R.string.cancel),
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(
context.getString(R.string.retry), R.drawable.ic_refresh_24dp,
UpdaterService.downloadApkPendingService(context, url)) context.getString(R.string.retry),
UpdaterService.downloadApkPendingService(context, url)
)
// Cancel action // Cancel action
addAction(R.drawable.ic_close_24dp, addAction(
context.getString(R.string.cancel), R.drawable.ic_close_24dp,
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)) context.getString(R.string.cancel),
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

@ -15,10 +15,10 @@ interface GithubService {
companion object { companion object {
fun create(): GithubService { fun create(): GithubService {
val restAdapter = Retrofit.Builder() val restAdapter = Retrofit.Builder()
.baseUrl("https://api.github.com") .baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.client(Injekt.get<NetworkHelper>().client) .client(Injekt.get<NetworkHelper>().client)
.build() .build()
return restAdapter.create(GithubService::class.java) return restAdapter.create(GithubService::class.java)
} }

View File

@ -4,7 +4,7 @@ import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.UpdateChecker import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult import eu.kanade.tachiyomi.data.updater.UpdateResult
class GithubUpdateChecker : UpdateChecker() { class GithubUpdateChecker : UpdateChecker() {
private val service: GithubService = GithubService.create() private val service: GithubService = GithubService.create()

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
} }
/** /**
@ -136,16 +135,16 @@ class ExtensionManager(
val extensions = ExtensionLoader.loadExtensions(context) val extensions = ExtensionLoader.loadExtensions(context)
installedExtensions = extensions installedExtensions = extensions
.filterIsInstance<LoadResult.Success>() .filterIsInstance<LoadResult.Success>()
.map { it.extension } .map { it.extension }
installedExtensions installedExtensions
.flatMap { it.sources } .flatMap { it.sources }
// overwrite is needed until the bundled sources are removed // overwrite is needed until the bundled sources are removed
.forEach { sourceManager.registerSource(it, true) } .forEach { sourceManager.registerSource(it, true) }
untrustedExtensions = extensions untrustedExtensions = extensions
.filterIsInstance<LoadResult.Untrusted>() .filterIsInstance<LoadResult.Untrusted>()
.map { it.extension } .map { it.extension }
} }
/** /**
@ -251,7 +250,7 @@ class ExtensionManager(
*/ */
fun updateExtension(extension: Extension.Installed): Observable<InstallStep> { fun updateExtension(extension: Extension.Installed): Observable<InstallStep> {
val availableExt = availableExtensions.find { it.pkgName == extension.pkgName } val availableExt = availableExtensions.find { it.pkgName == extension.pkgName }
?: return Observable.empty() ?: return Observable.empty()
return installExtension(availableExt) return installExtension(availableExt)
} }
@ -296,10 +295,10 @@ class ExtensionManager(
nowTrustedExtensions.map { extension -> nowTrustedExtensions.map { extension ->
async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) } async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) }
}.map { it.await() }.forEach { result -> }.map { it.await() }.forEach { result ->
if (result is LoadResult.Success) { if (result is LoadResult.Success) {
registerNewExtension(result.extension) registerNewExtension(result.extension)
}
} }
}
} }
} }

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

@ -18,9 +18,9 @@ class ExtensionInstallActivity : Activity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE) val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
.setDataAndType(intent.data, intent.type) .setDataAndType(intent.data, intent.type)
.putExtra(Intent.EXTRA_RETURN_RESULT, true) .putExtra(Intent.EXTRA_RETURN_RESULT, true)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try { try {
startActivityForResult(installIntent, INSTALL_REQUEST_CODE) startActivityForResult(installIntent, INSTALL_REQUEST_CODE)

View File

@ -19,7 +19,7 @@ import kotlinx.coroutines.async
* @param listener The listener that should be notified of extension installation events. * @param listener The listener that should be notified of extension installation events.
*/ */
internal class ExtensionInstallReceiver(private val listener: Listener) : internal class ExtensionInstallReceiver(private val listener: Listener) :
BroadcastReceiver() { BroadcastReceiver() {
/** /**
* Registers this broadcast receiver * Registers this broadcast receiver
@ -94,7 +94,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
*/ */
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult { private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent) val pkgName = getPackageNameFromIntent(intent)
?: return LoadResult.Error("Package name not found") ?: return LoadResult.Error("Package name not found")
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await() return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await()
} }

View File

@ -144,7 +144,7 @@ internal class ExtensionInstaller(private val context: Context) {
fun uninstallApk(pkgName: String) { fun uninstallApk(pkgName: String) {
val packageUri = "package:$pkgName".toUri() val packageUri = "package:$pkgName".toUri()
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri) val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent) context.startActivity(intent)
} }

View File

@ -36,9 +36,9 @@ internal object ExtensionLoader {
* List of the trusted signatures. * List of the trusted signatures.
*/ */
var trustedSignatures = mutableSetOf<String>() + var trustedSignatures = mutableSetOf<String>() +
Injekt.get<PreferencesHelper>().trustedSignatures().getOrDefault() + Injekt.get<PreferencesHelper>().trustedSignatures().getOrDefault() +
// inorichi's key // inorichi's key
"7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
/** /**
* Return a list of all the installed extensions initialized concurrently. * Return a list of all the installed extensions initialized concurrently.
@ -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)
} }
@ -122,30 +124,30 @@ internal object ExtensionLoader {
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader) val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!! val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
.split(";") .split(";")
.map { .map {
val sourceClass = it.trim() val sourceClass = it.trim()
if (sourceClass.startsWith(".")) if (sourceClass.startsWith("."))
pkgInfo.packageName + sourceClass pkgInfo.packageName + sourceClass
else else
sourceClass sourceClass
} }
.flatMap { .flatMap {
try { try {
val obj = Class.forName(it, false, classLoader).newInstance() val obj = Class.forName(it, false, classLoader).newInstance()
when (obj) { when (obj) {
is Source -> listOf(obj) is Source -> listOf(obj)
is SourceFactory -> obj.createSources() is SourceFactory -> obj.createSources()
else -> throw Exception("Unknown source class type! ${obj.javaClass}") else -> throw Exception("Unknown source class type! ${obj.javaClass}")
}
} catch (e: Throwable) {
Timber.e(e, "Extension load error: $extName.")
return LoadResult.Error(e)
} }
} catch (e: Throwable) {
Timber.e(e, "Extension load error: $extName.")
return LoadResult.Error(e)
} }
}
val langs = sources.filterIsInstance<CatalogueSource>() val langs = sources.filterIsInstance<CatalogueSource>()
.map { it.lang } .map { it.lang }
.toSet() .toSet()
val lang = when (langs.size) { val lang = when (langs.size) {
0 -> "" 0 -> ""

View File

@ -58,17 +58,19 @@ 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(
override fun onResponse(call: Call, response: Response) { object : Callback {
continuation.resume(response) override fun onResponse(call: Call, response: Response) {
} continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) { override fun onFailure(call: Call, e: IOException) {
// Don't bother with resuming the continuation if it is already cancelled. // Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return if (continuation.isCancelled) return
continuation.resumeWithException(e) continuation.resumeWithException(e)
}
} }
}) )
continuation.invokeOnCancellation { continuation.invokeOnCancellation {
try { try {
@ -91,14 +93,14 @@ fun Call.asObservableSuccess(): Observable<Response> {
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call { fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
val progressClient = newBuilder() val progressClient = newBuilder()
.cache(null) .cache(null)
.addNetworkInterceptor { chain -> .addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request()) val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder() originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body!!, listener)) .body(ProgressResponseBody(originalResponse.body!!, listener))
.build() .build()
} }
.build() .build()
return progressClient.newCall(request) return progressClient.newCall(request)
} }

View File

@ -18,10 +18,10 @@ fun GET(
): Request { ): Request {
return Request.Builder() return Request.Builder()
.url(url) .url(url)
.headers(headers) .headers(headers)
.cacheControl(cache) .cacheControl(cache)
.build() .build()
} }
fun POST( fun POST(
@ -32,9 +32,9 @@ fun POST(
): Request { ): Request {
return Request.Builder() return Request.Builder()
.url(url) .url(url)
.post(body) .post(body)
.headers(headers) .headers(headers)
.cacheControl(cache) .cacheControl(cache)
.build() .build()
} }

View File

@ -10,10 +10,10 @@ class UserAgentInterceptor : Interceptor {
return if (originalRequest.header("User-Agent").isNullOrEmpty()) { return if (originalRequest.header("User-Agent").isNullOrEmpty()) {
val newRequest = originalRequest val newRequest = originalRequest
.newBuilder() .newBuilder()
.removeHeader("User-Agent") .removeHeader("User-Agent")
.addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT) .addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT)
.build() .build()
chain.proceed(newRequest) chain.proceed(newRequest)
} else { } else {
chain.proceed(originalRequest) chain.proceed(originalRequest)

View File

@ -143,7 +143,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
val baseDirs = getBaseDirectories(context) val baseDirs = getBaseDirectories(context)
baseDirs baseDirs
.mapNotNull { File(it, manga.url).listFiles()?.toList() } .mapNotNull { File(it, manga.url).listFiles()?.toList() }
.flatten() .flatten()
.filter { it.extension == "json" }.firstOrNull()?.apply { .filter { it.extension == "json" }.firstOrNull()?.apply {
@ -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(
val c = c2.chapter_number.compareTo(c1.chapter_number) Comparator { c1, c2 ->
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c val c = c2.chapter_number.compareTo(c1.chapter_number)
}) 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(
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) Comparator<File> { f1, f2 ->
}) 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(
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) Comparator<ZipEntry> { f1, f2 ->
}).find { f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
}
).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(
f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) Comparator<FileHeader> { f1, f2 ->
}).find { f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString)
}
).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

@ -90,10 +90,10 @@ abstract class HttpSource : CatalogueSource {
*/ */
override fun fetchPopularManga(page: Int): Observable<MangasPage> { override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return client.newCall(popularMangaRequest(page)) return client.newCall(popularMangaRequest(page))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
popularMangaParse(response) popularMangaParse(response)
} }
} }
/** /**
@ -120,10 +120,10 @@ abstract class HttpSource : CatalogueSource {
*/ */
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(searchMangaRequest(page, query, filters)) return client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
searchMangaParse(response) searchMangaParse(response)
} }
} }
/** /**
@ -149,10 +149,10 @@ abstract class HttpSource : CatalogueSource {
*/ */
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return client.newCall(latestUpdatesRequest(page)) return client.newCall(latestUpdatesRequest(page))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
latestUpdatesParse(response) latestUpdatesParse(response)
} }
} }
/** /**
@ -177,10 +177,10 @@ abstract class HttpSource : CatalogueSource {
*/ */
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga)) return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
mangaDetailsParse(response).apply { initialized = true } mangaDetailsParse(response).apply { initialized = true }
} }
} }
/** /**
@ -209,10 +209,10 @@ abstract class HttpSource : CatalogueSource {
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return if (manga.status != SManga.LICENSED) { return if (manga.status != SManga.LICENSED) {
client.newCall(chapterListRequest(manga)) client.newCall(chapterListRequest(manga))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
chapterListParse(response) chapterListParse(response)
} }
} else { } else {
Observable.error(Exception("Licensed - No chapters to show")) Observable.error(Exception("Licensed - No chapters to show"))
} }
@ -242,10 +242,10 @@ abstract class HttpSource : CatalogueSource {
*/ */
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return client.newCall(pageListRequest(chapter)) return client.newCall(pageListRequest(chapter))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
pageListParse(response) pageListParse(response)
} }
} }
/** /**
@ -273,8 +273,8 @@ abstract class HttpSource : CatalogueSource {
*/ */
open fun fetchImageUrl(page: Page): Observable<String> { open fun fetchImageUrl(page: Page): Observable<String> {
return client.newCall(imageUrlRequest(page)) return client.newCall(imageUrlRequest(page))
.asObservableSuccess() .asObservableSuccess()
.map { imageUrlParse(it) } .map { imageUrlParse(it) }
} }
/** /**
@ -301,7 +301,7 @@ abstract class HttpSource : CatalogueSource {
*/ */
fun fetchImage(page: Page): Observable<Response> { fun fetchImage(page: Page): Observable<Response> {
return client.newCallWithProgress(imageRequest(page), page) return client.newCallWithProgress(imageRequest(page), page)
.asObservableSuccess() .asObservableSuccess()
} }
/** /**

View File

@ -14,12 +14,12 @@ fun HttpSource.getImageUrl(page: Page): Observable<Page> {
fun HttpSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> { fun HttpSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
return Observable.from(pages) return Observable.from(pages)
.filter { !it.imageUrl.isNullOrEmpty() } .filter { !it.imageUrl.isNullOrEmpty() }
.mergeWith(fetchRemainingImageUrlsFromPageList(pages)) .mergeWith(fetchRemainingImageUrlsFromPageList(pages))
} }
fun HttpSource.fetchRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> { fun HttpSource.fetchRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
return Observable.from(pages) return Observable.from(pages)
.filter { it.imageUrl.isNullOrEmpty() } .filter { it.imageUrl.isNullOrEmpty() }
.concatMap { getImageUrl(it) } .concatMap { getImageUrl(it) }
} }

View File

@ -21,7 +21,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
open class FoolSlide(override val domainName: String, private val urlModifier: String = "") : open class FoolSlide(override val domainName: String, private val urlModifier: String = "") :
DelegatedHttpSource DelegatedHttpSource
() { () {
override fun canOpenUrl(uri: Uri): Boolean = true override fun canOpenUrl(uri: Uri): Boolean = true
@ -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(
.add("adult", "true") url,
.build()) body = FormBody.Builder()
.add("adult", "true")
.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(
this.title = trimmedTitle trueChapter,
}, chapters.orEmpty()) manga.apply {
this.title = trimmedTitle
},
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(
if (down) { R.drawable.ic_blank_24dp,
R.drawable.ic_arrow_drop_down_24dp 0,
} else { if (down) {
R.drawable.ic_arrow_drop_up_24dp R.drawable.ic_arrow_drop_down_24dp
}, 0) } else {
R.drawable.ic_arrow_drop_up_24dp
},
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,31 +14,34 @@ 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) :
LayoutContainer { RestoreViewOnCreateController(bundle),
LayoutContainer {
init { init {
addLifecycleListener(object : LifecycleListener() { addLifecycleListener(
override fun postCreateView(controller: Controller, view: View) { object : LifecycleListener() {
onViewCreated(view) override fun postCreateView(controller: Controller, view: View) {
} onViewCreated(view)
}
override fun preCreateView(controller: Controller) { override fun preCreateView(controller: Controller) {
Timber.d("Create view for ${controller.instance()}") Timber.d("Create view for ${controller.instance()}")
} }
override fun preAttach(controller: Controller, view: View) { override fun preAttach(controller: Controller, view: View) {
Timber.d("Attach view for ${controller.instance()}") Timber.d("Attach view for ${controller.instance()}")
} }
override fun preDetach(controller: Controller, view: View) { override fun preDetach(controller: Controller, view: View) {
Timber.d("Detach view for ${controller.instance()}") Timber.d("Detach view for ${controller.instance()}")
} }
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,17 +99,19 @@ 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(
override fun onMenuItemActionExpand(item: MenuItem): Boolean { object : MenuItem.OnActionExpandListener {
return onExpand?.invoke(item) ?: true override fun onMenuItemActionExpand(item: MenuItem): Boolean {
} return onExpand?.invoke(item) ?: true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean { override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
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,8 +7,9 @@ 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) :
PresenterFactory<P> { RxController(bundle),
PresenterFactory<P> {
private val delegate = NucleusConductorDelegate(this) private val delegate = NucleusConductorDelegate(this)

View File

@ -10,7 +10,7 @@ abstract class BaseFlexibleViewHolder(
adapter: FlexibleAdapter<*>, adapter: FlexibleAdapter<*>,
stickyHeader: Boolean = false stickyHeader: Boolean = false
) : ) :
FlexibleViewHolder(view, adapter, stickyHeader), LayoutContainer { FlexibleViewHolder(view, adapter, stickyHeader), LayoutContainer {
override val containerView: View? override val containerView: View?
get() = itemView get() = itemView

View File

@ -24,7 +24,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onError function to execute when the observable throws an error. * @param onError function to execute when the observable throws an error.
*/ */
fun <T> Observable<T>.subscribeFirst(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = fun <T> Observable<T>.subscribeFirst(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
compose(deliverFirst<T>()).subscribe(split(onNext, onError)).apply { add(this) } compose(deliverFirst<T>()).subscribe(split(onNext, onError)).apply { add(this) }
/** /**
* Subscribes an observable with [deliverLatestCache] and adds it to the presenter's lifecycle * Subscribes an observable with [deliverLatestCache] and adds it to the presenter's lifecycle
@ -34,7 +34,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onError function to execute when the observable throws an error. * @param onError function to execute when the observable throws an error.
*/ */
fun <T> Observable<T>.subscribeLatestCache(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = fun <T> Observable<T>.subscribeLatestCache(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
compose(deliverLatestCache<T>()).subscribe(split(onNext, onError)).apply { add(this) } compose(deliverLatestCache<T>()).subscribe(split(onNext, onError)).apply { add(this) }
/** /**
* Subscribes an observable with [deliverReplay] and adds it to the presenter's lifecycle * Subscribes an observable with [deliverReplay] and adds it to the presenter's lifecycle
@ -44,7 +44,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onError function to execute when the observable throws an error. * @param onError function to execute when the observable throws an error.
*/ */
fun <T> Observable<T>.subscribeReplay(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = fun <T> Observable<T>.subscribeReplay(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
compose(deliverReplay<T>()).subscribe(split(onNext, onError)).apply { add(this) } compose(deliverReplay<T>()).subscribe(split(onNext, onError)).apply { add(this) }
/** /**
* Subscribes an observable with [DeliverWithView] and adds it to the presenter's lifecycle * Subscribes an observable with [DeliverWithView] and adds it to the presenter's lifecycle
@ -54,7 +54,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onError function to execute when the observable throws an error. * @param onError function to execute when the observable throws an error.
*/ */
fun <T> Observable<T>.subscribeWithView(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = fun <T> Observable<T>.subscribeWithView(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
compose(DeliverWithView<V, T>(view())).subscribe(split(onNext, onError)).apply { add(this) } compose(DeliverWithView<V, T>(view())).subscribe(split(onNext, onError)).apply { add(this) }
/** /**
* A deliverable that only emits to the view if attached, otherwise the event is ignored. * A deliverable that only emits to the view if attached, otherwise the event is ignored.
@ -63,11 +63,11 @@ open class BasePresenter<V> : RxPresenter<V>() {
override fun call(observable: Observable<T>): Observable<Delivery<View, T>> { override fun call(observable: Observable<T>): Observable<Delivery<View, T>> {
return observable return observable
.materialize() .materialize()
.filter { notification -> !notification.isOnCompleted } .filter { notification -> !notification.isOnCompleted }
.flatMap { notification -> .flatMap { notification ->
view.take(1).filter { it != null }.map { Delivery(it, notification) } view.take(1).filter { it != null }.map { Delivery(it, notification) }
} }
} }
} }
} }

View File

@ -8,7 +8,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
* @param controller The containing controller. * @param controller The containing controller.
*/ */
class CategoryAdapter(controller: CategoryController) : class CategoryAdapter(controller: CategoryController) :
FlexibleAdapter<CategoryItem>(null, controller, true) { FlexibleAdapter<CategoryItem>(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

@ -22,10 +22,11 @@ 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) :
FlexibleAdapter.OnItemClickListener, BaseController(bundle),
FlexibleAdapter.OnItemMoveListener, FlexibleAdapter.OnItemClickListener,
CategoryAdapter.CategoryItemListener { FlexibleAdapter.OnItemMoveListener,
CategoryAdapter.CategoryItemListener {
/** /**
* Adapter containing category items. * Adapter containing category items.
@ -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(
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
super.onDismissed(transientBottomBar, event) override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
if (!undoing) confirmDelete() super.onDismissed(transientBottomBar, event)
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)

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