mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 11:11:51 +01:00
Update to Kotlin 1.4.10, coroutines 1.3.9, Kotlinter 3.0.2 (#594)
This commit is contained in:
parent
f10f8c6b64
commit
9f15f25f43
@ -26,11 +26,11 @@ import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
||||
import java.security.Security
|
||||
|
||||
@ReportsCrashes(
|
||||
formUri = "https://collector.tracepot.com/e90773ff",
|
||||
reportType = org.acra.sender.HttpSender.Type.JSON,
|
||||
httpMethod = org.acra.sender.HttpSender.Method.PUT,
|
||||
buildConfigClass = BuildConfig::class,
|
||||
excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
|
||||
formUri = "https://collector.tracepot.com/e90773ff",
|
||||
reportType = org.acra.sender.HttpSender.Type.JSON,
|
||||
httpMethod = org.acra.sender.HttpSender.Method.PUT,
|
||||
buildConfigClass = BuildConfig::class,
|
||||
excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
|
||||
)
|
||||
open class App : Application(), LifecycleObserver {
|
||||
|
||||
|
@ -75,7 +75,8 @@ class BackupCreateService : Service() {
|
||||
startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
|
||||
|
||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock"
|
||||
PowerManager.PARTIAL_WAKE_LOCK,
|
||||
"${javaClass.name}:WakeLock"
|
||||
)
|
||||
wakeLock.acquire()
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
Worker(context, workerParams) {
|
||||
Worker(context, workerParams) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
val preferences = Injekt.get<PreferencesHelper>()
|
||||
@ -37,10 +37,13 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
|
||||
val interval = prefInterval ?: preferences.backupInterval().getOrDefault()
|
||||
if (interval > 0) {
|
||||
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
||||
interval.toLong(), TimeUnit.HOURS,
|
||||
10, TimeUnit.MINUTES)
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
interval.toLong(),
|
||||
TimeUnit.HOURS,
|
||||
10,
|
||||
TimeUnit.MINUTES
|
||||
)
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||||
} else {
|
||||
|
@ -103,7 +103,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
|
||||
private fun initParser(): Gson = when (version) {
|
||||
1 -> GsonBuilder().create()
|
||||
2 -> GsonBuilder()
|
||||
2 ->
|
||||
GsonBuilder()
|
||||
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
|
||||
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
|
||||
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
||||
@ -176,14 +177,14 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
val numberOfBackups = numberOfBackups()
|
||||
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
|
||||
dir.listFiles { _, filename -> backupRegex.matches(filename) }
|
||||
.orEmpty()
|
||||
.sortedByDescending { it.name }
|
||||
.drop(numberOfBackups - 1)
|
||||
.forEach { it.delete() }
|
||||
.orEmpty()
|
||||
.sortedByDescending { it.name }
|
||||
.drop(numberOfBackups - 1)
|
||||
.forEach { it.delete() }
|
||||
|
||||
// Create new file to place backup
|
||||
val newFile = dir.createFile(Backup.getDefaultFilename())
|
||||
?: throw Exception("Couldn't create backup file")
|
||||
?: throw Exception("Couldn't create backup file")
|
||||
|
||||
newFile.openOutputStream().bufferedWriter().use {
|
||||
parser.toJson(root, it)
|
||||
@ -192,7 +193,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
return newFile.uri.toString()
|
||||
} else {
|
||||
val file = UniFile.fromUri(context, uri)
|
||||
?: throw Exception("Couldn't create backup file")
|
||||
?: throw Exception("Couldn't create backup file")
|
||||
file.openOutputStream().bufferedWriter().use {
|
||||
parser.toJson(root, it)
|
||||
}
|
||||
@ -514,7 +515,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
* @return [Manga], null if not found
|
||||
*/
|
||||
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
|
||||
@ -522,7 +523,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
* @return [Manga] from library
|
||||
*/
|
||||
internal fun getFavoriteManga(): List<Manga> =
|
||||
databaseHelper.getFavoriteMangas().executeAsBlocking()
|
||||
databaseHelper.getFavoriteMangas().executeAsBlocking()
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
internal fun insertManga(manga: Manga): Long? =
|
||||
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
|
||||
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
|
||||
|
||||
/**
|
||||
* Inserts list of chapters
|
||||
|
@ -121,7 +121,9 @@ class BackupRestoreService : Service() {
|
||||
super.onCreate()
|
||||
startForeground(Notifications.ID_RESTORE_PROGRESS, progressNotification.build())
|
||||
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))
|
||||
}
|
||||
|
||||
@ -358,13 +360,13 @@ class BackupRestoreService : Service() {
|
||||
*/
|
||||
private val progressNotification by lazy {
|
||||
NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setSmallIcon(R.drawable.ic_tachi)
|
||||
.setOngoing(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setAutoCancel(false)
|
||||
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
|
||||
.addAction(R.drawable.ic_close_24dp, getString(android.R.string.cancel), cancelIntent)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setSmallIcon(R.drawable.ic_tachi)
|
||||
.setOngoing(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setAutoCancel(false)
|
||||
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
|
||||
.addAction(R.drawable.ic_close_24dp, getString(android.R.string.cancel), cancelIntent)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -382,12 +384,20 @@ class BackupRestoreService : Service() {
|
||||
* @param total the total progress.
|
||||
*/
|
||||
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))
|
||||
.setContentText(getString(R.string.restoring_progress, restoreProgress,
|
||||
totalAmount))
|
||||
.setContentText(
|
||||
getString(
|
||||
R.string.restoring_progress,
|
||||
restoreProgress,
|
||||
totalAmount
|
||||
)
|
||||
)
|
||||
.setProgress(total, current, false)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -395,8 +405,14 @@ class BackupRestoreService : Service() {
|
||||
*/
|
||||
private fun showResultNotification(path: String?, file: String?) {
|
||||
|
||||
val content = mutableListOf(getString(R.string.restore_completed_content, restoreProgress
|
||||
.toString(), errors.size.toString()))
|
||||
val content = mutableListOf(
|
||||
getString(
|
||||
R.string.restore_completed_content,
|
||||
restoreProgress
|
||||
.toString(),
|
||||
errors.size.toString()
|
||||
)
|
||||
)
|
||||
val sourceMissingCount = sourcesMissing.distinct().size
|
||||
if (sourceMissingCount > 0) {
|
||||
val sources = sourcesMissing.distinct().filter { it.toLongOrNull() == null }
|
||||
@ -408,20 +424,29 @@ class BackupRestoreService : Service() {
|
||||
if (sources.isEmpty()) {
|
||||
content.add(
|
||||
resources.getQuantityString(
|
||||
R.plurals.sources_missing, sourceMissingCount, sourceMissingCount
|
||||
R.plurals.sources_missing,
|
||||
sourceMissingCount,
|
||||
sourceMissingCount
|
||||
)
|
||||
)
|
||||
} else {
|
||||
content.add(
|
||||
resources.getQuantityString(
|
||||
R.plurals.sources_missing, sourceMissingCount, sourceMissingCount
|
||||
R.plurals.sources_missing,
|
||||
sourceMissingCount,
|
||||
sourceMissingCount
|
||||
) + ": " + missingSourcesString
|
||||
)
|
||||
}
|
||||
}
|
||||
if (lincensedManga > 0)
|
||||
content.add(resources.getQuantityString(R.plurals.licensed_manga, lincensedManga,
|
||||
lincensedManga))
|
||||
content.add(
|
||||
resources.getQuantityString(
|
||||
R.plurals.licensed_manga,
|
||||
lincensedManga,
|
||||
lincensedManga
|
||||
)
|
||||
)
|
||||
val trackingErrors = trackingErrors.distinct()
|
||||
if (trackingErrors.isNotEmpty()) {
|
||||
val trackingErrorsString = trackingErrors.distinct().joinToString("\n")
|
||||
@ -433,15 +458,21 @@ class BackupRestoreService : Service() {
|
||||
val restoreString = content.joinToString("\n")
|
||||
|
||||
val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
|
||||
.setContentTitle(getString(R.string.restore_completed))
|
||||
.setContentText(restoreString)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(restoreString))
|
||||
.setSmallIcon(R.drawable.ic_tachi)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
|
||||
.setContentTitle(getString(R.string.restore_completed))
|
||||
.setContentText(restoreString)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(restoreString))
|
||||
.setSmallIcon(R.drawable.ic_tachi)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
|
||||
if (errors.size > 0 && !path.isNullOrEmpty() && !file.isNullOrEmpty()) {
|
||||
resultNotification.addAction(R.drawable.ic_close_24dp, getString(R.string
|
||||
.view_all_errors), getErrorLogIntent(path, file))
|
||||
resultNotification.addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
getString(
|
||||
R.string
|
||||
.view_all_errors
|
||||
),
|
||||
getErrorLogIntent(path, file)
|
||||
)
|
||||
}
|
||||
notificationManager.notify(Notifications.ID_RESTORE_COMPLETE, resultNotification.build())
|
||||
}
|
||||
@ -451,11 +482,11 @@ class BackupRestoreService : Service() {
|
||||
*/
|
||||
private fun showErrorNotification(errorMessage: String) {
|
||||
val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
|
||||
.setContentTitle(getString(R.string.restore_error))
|
||||
.setContentText(errorMessage)
|
||||
.setSmallIcon(R.drawable.ic_error_24dp)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(ContextCompat.getColor(this, R.color.md_red_500))
|
||||
.setContentTitle(getString(R.string.restore_error))
|
||||
.setContentText(errorMessage)
|
||||
.setSmallIcon(R.drawable.ic_error_24dp)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(ContextCompat.getColor(this, R.color.md_red_500))
|
||||
notificationManager.notify(Notifications.ID_RESTORE_ERROR, resultNotification.build())
|
||||
}
|
||||
|
||||
@ -477,7 +508,7 @@ class BackupRestoreService : Service() {
|
||||
* @return true if the service is running, false otherwise.
|
||||
*/
|
||||
fun isRunning(context: Context): Boolean =
|
||||
context.isServiceRunning(BackupRestoreService::class.java)
|
||||
context.isServiceRunning(BackupRestoreService::class.java)
|
||||
|
||||
/**
|
||||
* Starts a service to restore a backup from Json
|
||||
|
@ -46,10 +46,12 @@ class ChapterCache(private val context: Context) {
|
||||
private val gson: Gson by injectLazy()
|
||||
|
||||
/** Cache class used for cache management. */
|
||||
private val diskCache = DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||
PARAMETER_APP_VERSION,
|
||||
PARAMETER_VALUE_COUNT,
|
||||
PARAMETER_CACHE_SIZE)
|
||||
private val diskCache = DiskLruCache.open(
|
||||
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||
PARAMETER_APP_VERSION,
|
||||
PARAMETER_VALUE_COUNT,
|
||||
PARAMETER_CACHE_SIZE
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns directory of cache.
|
||||
|
@ -83,7 +83,8 @@ class CoverCache(val context: Context) {
|
||||
withContext(Dispatchers.Main) {
|
||||
context.toast(
|
||||
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) {
|
||||
context.toast(
|
||||
context.getString(
|
||||
R.string.deleted_, Formatter.formatFileSize(context, deletedSize)
|
||||
R.string.deleted_,
|
||||
Formatter.formatFileSize(context, deletedSize)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -30,8 +30,13 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||
* This class provides operations to manage the database through its interfaces.
|
||||
*/
|
||||
open class DatabaseHelper(context: Context) :
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries,
|
||||
HistoryQueries, SearchMetadataQueries {
|
||||
MangaQueries,
|
||||
ChapterQueries,
|
||||
TrackQueries,
|
||||
CategoryQueries,
|
||||
MangaCategoryQueries,
|
||||
HistoryQueries,
|
||||
SearchMetadataQueries {
|
||||
|
||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||
.name(DbOpenCallback.DATABASE_NAME)
|
||||
@ -39,15 +44,15 @@ MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQuerie
|
||||
.build()
|
||||
|
||||
override val db = DefaultStorIOSQLite.builder()
|
||||
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))
|
||||
.addTypeMapping(Manga::class.java, MangaTypeMapping())
|
||||
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
|
||||
.addTypeMapping(Track::class.java, TrackTypeMapping())
|
||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
||||
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
||||
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
|
||||
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
||||
.build()
|
||||
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))
|
||||
.addTypeMapping(Manga::class.java, MangaTypeMapping())
|
||||
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
|
||||
.addTypeMapping(Track::class.java, TrackTypeMapping())
|
||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
||||
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
||||
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
|
||||
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
||||
.build()
|
||||
|
||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||
|
||||
|
@ -44,8 +44,10 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
||||
|
||||
// Fix kissmanga covers after supporting cloudflare
|
||||
db.execSQL("""UPDATE mangas SET thumbnail_url =
|
||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""")
|
||||
db.execSQL(
|
||||
"""UPDATE mangas SET thumbnail_url =
|
||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4"""
|
||||
)
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
// Initialize history tables
|
||||
|
@ -19,22 +19,22 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
|
||||
|
||||
class CategoryTypeMapping : SQLiteTypeMapping<Category>(
|
||||
CategoryPutResolver(),
|
||||
CategoryGetResolver(),
|
||||
CategoryDeleteResolver()
|
||||
CategoryPutResolver(),
|
||||
CategoryGetResolver(),
|
||||
CategoryDeleteResolver()
|
||||
)
|
||||
|
||||
class CategoryPutResolver : DefaultPutResolver<Category>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: Category) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: Category) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: Category) = ContentValues(4).apply {
|
||||
put(COL_ID, obj.id)
|
||||
@ -76,8 +76,8 @@ class CategoryGetResolver : DefaultGetResolver<Category>() {
|
||||
class CategoryDeleteResolver : DefaultDeleteResolver<Category>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: Category) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
||||
|
@ -27,22 +27,22 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_URL
|
||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE
|
||||
|
||||
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
|
||||
ChapterPutResolver(),
|
||||
ChapterGetResolver(),
|
||||
ChapterDeleteResolver()
|
||||
ChapterPutResolver(),
|
||||
ChapterGetResolver(),
|
||||
ChapterDeleteResolver()
|
||||
)
|
||||
|
||||
class ChapterPutResolver : DefaultPutResolver<Chapter>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: Chapter) = ContentValues(11).apply {
|
||||
put(COL_ID, obj.id)
|
||||
@ -83,8 +83,8 @@ class ChapterGetResolver : DefaultGetResolver<Chapter>() {
|
||||
class ChapterDeleteResolver : DefaultDeleteResolver<Chapter>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
||||
|
@ -18,22 +18,22 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_TIME_READ
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE
|
||||
|
||||
class HistoryTypeMapping : SQLiteTypeMapping<History>(
|
||||
HistoryPutResolver(),
|
||||
HistoryGetResolver(),
|
||||
HistoryDeleteResolver()
|
||||
HistoryPutResolver(),
|
||||
HistoryGetResolver(),
|
||||
HistoryDeleteResolver()
|
||||
)
|
||||
|
||||
open class HistoryPutResolver : DefaultPutResolver<History>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: History) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: History) = ContentValues(4).apply {
|
||||
put(COL_ID, obj.id)
|
||||
@ -56,8 +56,8 @@ class HistoryGetResolver : DefaultGetResolver<History>() {
|
||||
class HistoryDeleteResolver : DefaultDeleteResolver<History>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
||||
|
@ -16,22 +16,22 @@ import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_MANGA_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE
|
||||
|
||||
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
|
||||
MangaCategoryPutResolver(),
|
||||
MangaCategoryGetResolver(),
|
||||
MangaCategoryDeleteResolver()
|
||||
MangaCategoryPutResolver(),
|
||||
MangaCategoryGetResolver(),
|
||||
MangaCategoryDeleteResolver()
|
||||
)
|
||||
|
||||
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: MangaCategory) = ContentValues(3).apply {
|
||||
put(COL_ID, obj.id)
|
||||
@ -52,8 +52,8 @@ class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
|
||||
class MangaCategoryDeleteResolver : DefaultDeleteResolver<MangaCategory>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
||||
|
@ -31,22 +31,22 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VIEWER
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
|
||||
|
||||
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
|
||||
MangaPutResolver(),
|
||||
MangaGetResolver(),
|
||||
MangaDeleteResolver()
|
||||
MangaPutResolver(),
|
||||
MangaGetResolver(),
|
||||
MangaDeleteResolver()
|
||||
)
|
||||
|
||||
class MangaPutResolver : DefaultPutResolver<Manga>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: Manga) = ContentValues(15).apply {
|
||||
put(COL_ID, obj.id)
|
||||
@ -101,8 +101,8 @@ open class MangaGetResolver : DefaultGetResolver<Manga>(), BaseMangaGetResolver
|
||||
class MangaDeleteResolver : DefaultDeleteResolver<Manga>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
||||
|
@ -25,22 +25,22 @@ import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TRACKING_URL
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE
|
||||
|
||||
class TrackTypeMapping : SQLiteTypeMapping<Track>(
|
||||
TrackPutResolver(),
|
||||
TrackGetResolver(),
|
||||
TrackDeleteResolver()
|
||||
TrackPutResolver(),
|
||||
TrackGetResolver(),
|
||||
TrackDeleteResolver()
|
||||
)
|
||||
|
||||
class TrackPutResolver : DefaultPutResolver<Track>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: Track) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: Track) = ContentValues(10).apply {
|
||||
put(COL_ID, obj.id)
|
||||
@ -77,8 +77,8 @@ class TrackGetResolver : DefaultGetResolver<Track>() {
|
||||
class TrackDeleteResolver : DefaultDeleteResolver<Track>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ class LibraryManga : MangaImpl() {
|
||||
|
||||
fun createHide(categoryId: Int, title: String): LibraryManga =
|
||||
createBlank(categoryId).apply {
|
||||
this.title = title
|
||||
status = -1
|
||||
}
|
||||
this.title = title
|
||||
status = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,13 +73,14 @@ interface Manga : SManga {
|
||||
genre?.split(",")?.map { it.trim().toLowerCase(Locale.US) } ?: emptyList()
|
||||
return if (currentTags.any { tag -> tag.startsWith("japanese") || isMangaTag(tag) }) {
|
||||
TYPE_MANGA
|
||||
} else if (currentTags.any { tag -> tag.startsWith("english") || isComicTag(tag) } || isComicSource(
|
||||
sourceName
|
||||
)) {
|
||||
} else if (currentTags.any { tag -> tag.startsWith("english") || isComicTag(tag) } ||
|
||||
isComicSource(sourceName)
|
||||
) {
|
||||
TYPE_COMIC
|
||||
} else if (currentTags.any { tag ->
|
||||
tag.startsWith("chinese") || isManhuaTag(tag)
|
||||
} || sourceName.contains("manhua", true)) {
|
||||
tag.startsWith("chinese") || isManhuaTag(tag)
|
||||
} || sourceName.contains("manhua", true)
|
||||
) {
|
||||
TYPE_MANHUA
|
||||
} else if (currentTags.any { tag -> isManhwaTag(tag) } || isWebtoonSource(sourceName)) {
|
||||
TYPE_MANHWA
|
||||
|
@ -74,7 +74,8 @@ open class MangaImpl : Manga {
|
||||
|
||||
override fun copyFrom(other: SManga) {
|
||||
if (other is MangaImpl && other::ogTitle.isInitialized &&
|
||||
!other.title.isBlank() && other.ogTitle != ogTitle) {
|
||||
!other.title.isBlank() && other.ogTitle != ogTitle
|
||||
) {
|
||||
val oldTitle = ogTitle
|
||||
title = other.ogTitle
|
||||
val db: DownloadManager by injectLazy()
|
||||
|
@ -10,20 +10,24 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||
interface CategoryQueries : DbProvider {
|
||||
|
||||
fun getCategories() = db.get()
|
||||
.listOfObjects(Category::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(CategoryTable.TABLE)
|
||||
.orderBy(CategoryTable.COL_ORDER)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(Category::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(CategoryTable.TABLE)
|
||||
.orderBy(CategoryTable.COL_ORDER)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getCategoriesForManga(manga: Manga) = db.get()
|
||||
.listOfObjects(Category::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getCategoriesForMangaQuery())
|
||||
.args(manga.id)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(Category::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getCategoriesForMangaQuery())
|
||||
.args(manga.id)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertCategory(category: Category) = db.put().`object`(category).prepare()
|
||||
|
||||
|
@ -19,68 +19,82 @@ import java.util.Date
|
||||
interface ChapterQueries : DbProvider {
|
||||
|
||||
fun getChapters(manga: Manga) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getRecentChapters(date: Date) = db.get()
|
||||
.listOfObjects(MangaChapter::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getRecentsQuery())
|
||||
.args(date.time)
|
||||
.observesTables(ChapterTable.TABLE)
|
||||
.build())
|
||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
.listOfObjects(MangaChapter::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentsQuery())
|
||||
.args(date.time)
|
||||
.observesTables(ChapterTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getUpdatedManga(date: Date, search: String = "", endless: Boolean) = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getRecentsQueryDistinct(search.sqLite, endless))
|
||||
.args(date.time)
|
||||
.observesTables(ChapterTable.TABLE)
|
||||
.build())
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentsQueryDistinct(search.sqLite, endless))
|
||||
.args(date.time)
|
||||
.observesTables(ChapterTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getChapter(id: Long) = db.get()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_ID} = ?")
|
||||
.whereArgs(id)
|
||||
.build())
|
||||
.prepare()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_ID} = ?")
|
||||
.whereArgs(id)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChapter(url: String) = db.get()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(url)
|
||||
.build())
|
||||
.prepare()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(url)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChapters(url: String) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(url)
|
||||
.build())
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(url)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChapter(url: String, mangaId: Long) = db.get()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(url, mangaId)
|
||||
.build())
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(url, mangaId)
|
||||
.build()
|
||||
)
|
||||
.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 updateChaptersBackup(chapters: List<Chapter>) = db.put()
|
||||
.objects(chapters)
|
||||
.withPutResolver(ChapterBackupPutResolver())
|
||||
.prepare()
|
||||
.objects(chapters)
|
||||
.withPutResolver(ChapterBackupPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateChapterProgress(chapter: Chapter) = db.put()
|
||||
.`object`(chapter)
|
||||
.withPutResolver(ChapterProgressPutResolver())
|
||||
.prepare()
|
||||
.`object`(chapter)
|
||||
.withPutResolver(ChapterProgressPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateChaptersProgress(chapters: List<Chapter>) = db.put()
|
||||
.objects(chapters)
|
||||
.withPutResolver(ChapterProgressPutResolver())
|
||||
.prepare()
|
||||
.objects(chapters)
|
||||
.withPutResolver(ChapterProgressPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun fixChaptersSourceOrder(chapters: List<Chapter>) = db.put()
|
||||
.objects(chapters)
|
||||
.withPutResolver(ChapterSourceOrderPutResolver())
|
||||
.prepare()
|
||||
.objects(chapters)
|
||||
.withPutResolver(ChapterSourceOrderPutResolver())
|
||||
.prepare()
|
||||
}
|
||||
|
@ -26,14 +26,16 @@ interface HistoryQueries : DbProvider {
|
||||
* @offset offset the db by
|
||||
*/
|
||||
fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getRecentMangasQuery(offset, search.sqLite))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentMangasQuery(offset, search.sqLite))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
/**
|
||||
* 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()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getRecentAdditionsQuery(search.sqLite, endless))
|
||||
.args(date.time)
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build())
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentAdditionsQuery(search.sqLite, endless))
|
||||
.args(date.time)
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
@ -57,11 +61,13 @@ interface HistoryQueries : DbProvider {
|
||||
*/
|
||||
fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getRecentMangasLimitQuery(limit, search.sqLite))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentMangasLimitQuery(limit, search.sqLite))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
@ -72,31 +78,37 @@ interface HistoryQueries : DbProvider {
|
||||
*/
|
||||
fun getRecentsWithUnread(date: Date, search: String = "", endless: Boolean) = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getRecentReadWithUnreadChapters(search.sqLite, endless))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentReadWithUnreadChapters(search.sqLite, endless))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getHistoryByMangaId(mangaId: Long) = db.get()
|
||||
.listOfObjects(History::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getHistoryByMangaId())
|
||||
.args(mangaId)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(History::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getHistoryByMangaId())
|
||||
.args(mangaId)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getHistoryByChapterUrl(chapterUrl: String) = db.get()
|
||||
.`object`(History::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getHistoryByChapterUrl())
|
||||
.args(chapterUrl)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.prepare()
|
||||
.`object`(History::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getHistoryByChapterUrl())
|
||||
.args(chapterUrl)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
/**
|
||||
* Updates the history last read.
|
||||
@ -104,9 +116,9 @@ interface HistoryQueries : DbProvider {
|
||||
* @param history history object
|
||||
*/
|
||||
fun updateHistoryLastRead(history: History) = db.put()
|
||||
.`object`(history)
|
||||
.withPutResolver(HistoryLastReadPutResolver())
|
||||
.prepare()
|
||||
.`object`(history)
|
||||
.withPutResolver(HistoryLastReadPutResolver())
|
||||
.prepare()
|
||||
|
||||
/**
|
||||
* Updates the history last read.
|
||||
@ -114,21 +126,25 @@ interface HistoryQueries : DbProvider {
|
||||
* @param historyList history object list
|
||||
*/
|
||||
fun updateHistoryLastRead(historyList: List<History>) = db.put()
|
||||
.objects(historyList)
|
||||
.withPutResolver(HistoryLastReadPutResolver())
|
||||
.prepare()
|
||||
.objects(historyList)
|
||||
.withPutResolver(HistoryLastReadPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun deleteHistory() = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.table(HistoryTable.TABLE)
|
||||
.build())
|
||||
.prepare()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(HistoryTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun deleteHistoryNoLastRead() = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.table(HistoryTable.TABLE)
|
||||
.where("${HistoryTable.COL_LAST_READ} = ?")
|
||||
.whereArgs(0)
|
||||
.build())
|
||||
.prepare()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(HistoryTable.TABLE)
|
||||
.where("${HistoryTable.COL_LAST_READ} = ?")
|
||||
.whereArgs(0)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
@ -15,12 +15,14 @@ interface MangaCategoryQueries : DbProvider {
|
||||
fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare()
|
||||
|
||||
fun deleteOldMangasCategories(mangas: List<Manga>) = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.table(MangaCategoryTable.TABLE)
|
||||
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
|
||||
.whereArgs(*mangas.map { it.id }.toTypedArray())
|
||||
.build())
|
||||
.prepare()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(MangaCategoryTable.TABLE)
|
||||
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
|
||||
.whereArgs(*mangas.map { it.id }.toTypedArray())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
|
||||
db.inTransaction {
|
||||
|
@ -22,67 +22,77 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
interface MangaQueries : DbProvider {
|
||||
|
||||
fun getMangas() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getLibraryMangas() = db.get()
|
||||
.listOfObjects(LibraryManga::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(libraryQuery)
|
||||
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
|
||||
.build())
|
||||
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
.listOfObjects(LibraryManga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(libraryQuery)
|
||||
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getFavoriteMangas() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||
.whereArgs(1)
|
||||
.orderBy(MangaTable.COL_TITLE)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||
.whereArgs(1)
|
||||
.orderBy(MangaTable.COL_TITLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getManga(url: String, sourceId: Long) = db.get()
|
||||
.`object`(Manga::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
|
||||
.whereArgs(url, sourceId)
|
||||
.build())
|
||||
.prepare()
|
||||
.`object`(Manga::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
|
||||
.whereArgs(url, sourceId)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getManga(id: Long) = db.get()
|
||||
.`object`(Manga::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(id)
|
||||
.build())
|
||||
.prepare()
|
||||
.`object`(Manga::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(id)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
||||
|
||||
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
||||
|
||||
fun updateFlags(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaFlagsPutResolver())
|
||||
.prepare()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaFlagsPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateLastUpdated(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaLastUpdatedPutResolver())
|
||||
.prepare()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaLastUpdatedPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateMangaFavorite(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaFavoritePutResolver())
|
||||
.prepare()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaFavoritePutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateMangaAdded(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
@ -90,14 +100,14 @@ interface MangaQueries : DbProvider {
|
||||
.prepare()
|
||||
|
||||
fun updateMangaViewer(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaViewerPutResolver())
|
||||
.prepare()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaViewerPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateMangaTitle(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaTitlePutResolver())
|
||||
.prepare()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaTitlePutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateMangaInfo(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
@ -114,27 +124,33 @@ interface MangaQueries : DbProvider {
|
||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
||||
|
||||
fun deleteMangasNotInLibrary() = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||
.whereArgs(0)
|
||||
.build())
|
||||
.prepare()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||
.whereArgs(0)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun deleteMangas() = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.build())
|
||||
.prepare()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getLastReadManga() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getLastReadMangaQuery())
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getLastReadMangaQuery())
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
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()
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
|
||||
/**
|
||||
* Query to get the manga from the library, with their categories and unread count.
|
||||
*/
|
||||
val libraryQuery = """
|
||||
val libraryQuery =
|
||||
"""
|
||||
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
|
||||
FROM (
|
||||
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, 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.
|
||||
*/
|
||||
fun getRecentsQuery() = """
|
||||
fun getRecentsQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1
|
||||
@ -52,7 +54,8 @@ fun getRecentsQuery() = """
|
||||
/**
|
||||
* 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}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1
|
||||
AND ${Manga.COL_DATE_ADDED} > ?
|
||||
@ -64,7 +67,8 @@ fun getRecentAdditionsQuery(search: String, endless: Boolean) = """
|
||||
/**
|
||||
* 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}.*
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
@ -92,7 +96,8 @@ fun getRecentsQueryDistinct(search: String, endless: Boolean) = """
|
||||
* and are read after the given time period
|
||||
* @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}.*
|
||||
FROM ${Manga.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
|
||||
* 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}.*
|
||||
FROM ${Manga.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
|
||||
* 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}.*
|
||||
FROM (
|
||||
SELECT ${Manga.TABLE}.*
|
||||
@ -178,7 +185,8 @@ fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) = """
|
||||
${if (endless) "" else "LIMIT 8"}
|
||||
"""
|
||||
|
||||
fun getHistoryByMangaId() = """
|
||||
fun getHistoryByMangaId() =
|
||||
"""
|
||||
SELECT ${History.TABLE}.*
|
||||
FROM ${History.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}
|
||||
"""
|
||||
|
||||
fun getHistoryByChapterUrl() = """
|
||||
fun getHistoryByChapterUrl() =
|
||||
"""
|
||||
SELECT ${History.TABLE}.*
|
||||
FROM ${History.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}
|
||||
"""
|
||||
|
||||
fun getLastReadMangaQuery() = """
|
||||
fun getLastReadMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
@ -206,7 +216,8 @@ fun getLastReadMangaQuery() = """
|
||||
ORDER BY max DESC
|
||||
"""
|
||||
|
||||
fun getTotalChapterMangaQuery() = """
|
||||
fun getTotalChapterMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
@ -218,7 +229,8 @@ fun getTotalChapterMangaQuery() = """
|
||||
/**
|
||||
* Query to get the categories for a manga.
|
||||
*/
|
||||
fun getCategoriesForMangaQuery() = """
|
||||
fun getCategoriesForMangaQuery() =
|
||||
"""
|
||||
SELECT ${Category.TABLE}.* FROM ${Category.TABLE}
|
||||
JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} =
|
||||
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}
|
||||
|
@ -10,35 +10,43 @@ interface SearchMetadataQueries : DbProvider {
|
||||
|
||||
fun getSearchMetadataForManga(mangaId: Long) = db.get()
|
||||
.`object`(SearchMetadata::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.where("${SearchMetadataTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(mangaId)
|
||||
.build())
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.where("${SearchMetadataTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(mangaId)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getSearchMetadata() = db.get()
|
||||
.listOfObjects(SearchMetadata::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.build())
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getSearchMetadataByIndexedExtra(extra: String) = db.get()
|
||||
.listOfObjects(SearchMetadata::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?")
|
||||
.whereArgs(extra)
|
||||
.build())
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?")
|
||||
.whereArgs(extra)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertSearchMetadata(metadata: SearchMetadata) = db.put().`object`(metadata).prepare()
|
||||
|
||||
fun deleteSearchMetadata(metadata: SearchMetadata) = db.delete().`object`(metadata).prepare()
|
||||
|
||||
fun deleteAllSearchMetadata() = db.delete().byQuery(DeleteQuery.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.build())
|
||||
fun deleteAllSearchMetadata() = db.delete().byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
@ -11,23 +11,27 @@ import eu.kanade.tachiyomi.data.track.TrackService
|
||||
interface TrackQueries : DbProvider {
|
||||
|
||||
fun getTracks(manga: Manga) = db.get()
|
||||
.listOfObjects(Track::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(TrackTable.TABLE)
|
||||
.where("${TrackTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(Track::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(TrackTable.TABLE)
|
||||
.where("${TrackTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertTrack(track: Track) = db.put().`object`(track).prepare()
|
||||
|
||||
fun insertTracks(tracks: List<Track>) = db.put().objects(tracks).prepare()
|
||||
|
||||
fun deleteTrackForManga(manga: Manga, sync: TrackService) = db.delete()
|
||||
.byQuery(DeleteQuery.builder()
|
||||
.table(TrackTable.TABLE)
|
||||
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
|
||||
.whereArgs(manga.id, sync.id)
|
||||
.build())
|
||||
.prepare()
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(TrackTable.TABLE)
|
||||
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
|
||||
.whereArgs(manga.id, sync.id)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
@ -20,10 +20,10 @@ class ChapterBackupPutResolver : PutResolver<Chapter>() {
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(chapter.url)
|
||||
.build()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(chapter.url)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
|
||||
put(ChapterTable.COL_READ, chapter.read)
|
||||
|
@ -20,10 +20,10 @@ class ChapterProgressPutResolver : PutResolver<Chapter>() {
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_ID} = ?")
|
||||
.whereArgs(chapter.id)
|
||||
.build()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_ID} = ?")
|
||||
.whereArgs(chapter.id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
|
||||
put(ChapterTable.COL_READ, chapter.read)
|
||||
|
@ -20,10 +20,10 @@ class ChapterSourceOrderPutResolver : PutResolver<Chapter>() {
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(chapter.url, chapter.manga_id)
|
||||
.build()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(chapter.url, chapter.manga_id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(chapter: Chapter) = ContentValues(1).apply {
|
||||
put(ChapterTable.COL_SOURCE_ORDER, chapter.source_order)
|
||||
|
@ -19,11 +19,13 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
|
||||
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
|
||||
val updateQuery = mapToUpdateQuery(history)
|
||||
|
||||
val cursor = db.lowLevel().query(Query.builder()
|
||||
val cursor = db.lowLevel().query(
|
||||
Query.builder()
|
||||
.table(updateQuery.table())
|
||||
.where(updateQuery.where())
|
||||
.whereArgs(updateQuery.whereArgs())
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
|
||||
val putResult: PutResult
|
||||
|
||||
@ -48,10 +50,10 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
|
||||
* @param obj history object
|
||||
*/
|
||||
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
||||
.table(HistoryTable.TABLE)
|
||||
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
|
||||
.whereArgs(obj.chapter_id)
|
||||
.build()
|
||||
.table(HistoryTable.TABLE)
|
||||
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
|
||||
.whereArgs(obj.chapter_id)
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Create content query
|
||||
|
@ -42,8 +42,8 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>()
|
||||
val chapter =
|
||||
if (!cursor.isNull(cursor.getColumnIndex(ChapterTable.COL_MANGA_ID))) chapterResolver
|
||||
.mapFromCursor(
|
||||
cursor
|
||||
) else ChapterImpl()
|
||||
cursor
|
||||
) else ChapterImpl()
|
||||
|
||||
// Get history object
|
||||
val history =
|
||||
|
@ -20,10 +20,10 @@ class MangaFavoritePutResolver : PutResolver<Manga>() {
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
put(MangaTable.COL_FAVORITE, manga.favorite)
|
||||
|
@ -20,10 +20,10 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)
|
||||
|
@ -20,10 +20,10 @@ class MangaLastUpdatedPutResolver : PutResolver<Manga>() {
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
put(MangaTable.COL_LAST_UPDATE, manga.last_update)
|
||||
|
@ -20,10 +20,10 @@ class MangaTitlePutResolver : PutResolver<Manga>() {
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
put(MangaTable.COL_TITLE, manga.title)
|
||||
|
@ -20,10 +20,10 @@ class MangaViewerPutResolver : PutResolver<Manga>() {
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
put(MangaTable.COL_VIEWER, manga.viewer)
|
||||
|
@ -15,7 +15,8 @@ object CategoryTable {
|
||||
const val COL_MANGA_ORDER = "manga_order"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_NAME TEXT NOT NULL,
|
||||
$COL_ORDER INTEGER NOT NULL,
|
||||
|
@ -31,7 +31,8 @@ object ChapterTable {
|
||||
const val COL_SOURCE_ORDER = "source_order"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_MANGA_ID INTEGER NOT NULL,
|
||||
$COL_URL TEXT NOT NULL,
|
||||
@ -54,7 +55,7 @@ object ChapterTable {
|
||||
|
||||
val createUnreadChaptersIndexQuery: String
|
||||
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
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SOURCE_ORDER INTEGER DEFAULT 0"
|
||||
|
@ -31,7 +31,8 @@ object HistoryTable {
|
||||
* query to create history table
|
||||
*/
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_CHAPTER_ID INTEGER NOT NULL UNIQUE,
|
||||
$COL_LAST_READ LONG,
|
||||
|
@ -11,7 +11,8 @@ object MangaCategoryTable {
|
||||
const val COL_CATEGORY_ID = "category_id"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_MANGA_ID INTEGER NOT NULL,
|
||||
$COL_CATEGORY_ID INTEGER NOT NULL,
|
||||
|
@ -45,7 +45,8 @@ object MangaTable {
|
||||
const val COL_DATE_ADDED = "date_added"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_SOURCE INTEGER NOT NULL,
|
||||
$COL_URL TEXT NOT NULL,
|
||||
@ -71,7 +72,7 @@ object MangaTable {
|
||||
|
||||
val createLibraryIndexQuery: String
|
||||
get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " +
|
||||
"WHERE $COL_FAVORITE = 1"
|
||||
"WHERE $COL_FAVORITE = 1"
|
||||
|
||||
val addHideTitle: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_HIDE_TITLE INTEGER DEFAULT 0"
|
||||
|
@ -15,7 +15,8 @@ object SearchMetadataTable {
|
||||
|
||||
// Insane foreign, primary key to avoid touch manga table
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_UPLOADER TEXT,
|
||||
$COL_EXTRA TEXT NOT NULL,
|
||||
|
@ -27,7 +27,8 @@ object TrackTable {
|
||||
const val COL_TRACKING_URL = "remote_url"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_MANGA_ID INTEGER NOT NULL,
|
||||
$COL_SYNC_ID INTEGER NOT NULL,
|
||||
|
@ -251,7 +251,9 @@ class DownloadManager(val context: Context) {
|
||||
queue.remove(chapters)
|
||||
val chapterDirs =
|
||||
provider.findChapterDirs(chapters, manga, source) + provider.findTempChapterDirs(
|
||||
chapters, manga, source
|
||||
chapters,
|
||||
manga,
|
||||
source
|
||||
)
|
||||
chapterDirs.forEach { it.delete() }
|
||||
cache.removeChapters(chapters, manga)
|
||||
|
@ -24,7 +24,7 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
*/
|
||||
private val notification by lazy {
|
||||
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))
|
||||
isDownloading = true
|
||||
// Pause action
|
||||
addAction(R.drawable.ic_pause_24dp,
|
||||
addAction(
|
||||
R.drawable.ic_pause_24dp,
|
||||
context.getString(R.string.pause),
|
||||
NotificationReceiver.pauseDownloadsPendingBroadcast(context))
|
||||
NotificationReceiver.pauseDownloadsPendingBroadcast(context)
|
||||
)
|
||||
}
|
||||
|
||||
if (download != null) {
|
||||
val title = download.manga.title.chop(15)
|
||||
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))
|
||||
setContentText(
|
||||
context.getString(R.string.downloading)
|
||||
@ -124,17 +129,21 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||
isDownloading = true
|
||||
// Pause action
|
||||
addAction(R.drawable.ic_pause_24dp,
|
||||
context.getString(R.string.pause),
|
||||
NotificationReceiver.pauseDownloadsPendingBroadcast(context))
|
||||
addAction(
|
||||
R.drawable.ic_pause_24dp,
|
||||
context.getString(R.string.pause),
|
||||
NotificationReceiver.pauseDownloadsPendingBroadcast(context)
|
||||
)
|
||||
}
|
||||
|
||||
val title = download.manga.title.chop(15)
|
||||
val quotedTitle = Pattern.quote(title)
|
||||
val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), "")
|
||||
setContentTitle("$title - $chapter".chop(30))
|
||||
setContentText(context.getString(R.string.downloading_progress)
|
||||
.format(download.downloadedImages, download.pages!!.size))
|
||||
setContentText(
|
||||
context.getString(R.string.downloading_progress)
|
||||
.format(download.downloadedImages, download.pages!!.size)
|
||||
)
|
||||
setStyle(null)
|
||||
setProgress(download.pages!!.size, download.downloadedImages, false)
|
||||
}
|
||||
|
@ -161,13 +161,17 @@ class DownloadService : Service() {
|
||||
*/
|
||||
private fun listenNetworkChanges() {
|
||||
subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ state -> onNetworkStateChanged(state)
|
||||
}, {
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ state ->
|
||||
onNetworkStateChanged(state)
|
||||
},
|
||||
{
|
||||
toast(R.string.could_not_download_chapter_can_try_again)
|
||||
stopSelf()
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,9 +80,9 @@ class DownloadStore(
|
||||
*/
|
||||
fun restore(): List<Download> {
|
||||
val objs = preferences.all
|
||||
.mapNotNull { it.value as? String }
|
||||
.mapNotNull { deserialize(it) }
|
||||
.sortedBy { it.order }
|
||||
.mapNotNull { it.value as? String }
|
||||
.mapNotNull { deserialize(it) }
|
||||
.sortedBy { it.order }
|
||||
|
||||
val downloads = mutableListOf<Download>()
|
||||
if (objs.isNotEmpty()) {
|
||||
|
@ -288,23 +288,23 @@ class Downloader(
|
||||
val pageListObservable = if (download.pages == null) {
|
||||
// Pull page list from network and add them to download object
|
||||
download.source.fetchPageList(download.chapter).doOnNext { pages ->
|
||||
if (pages.isEmpty()) {
|
||||
throw Exception("Page list is empty")
|
||||
}
|
||||
download.pages = pages
|
||||
if (pages.isEmpty()) {
|
||||
throw Exception("Page list is empty")
|
||||
}
|
||||
download.pages = pages
|
||||
}
|
||||
} else {
|
||||
// Or if the page list already exists, start from the file
|
||||
Observable.just(download.pages!!)
|
||||
}
|
||||
|
||||
pageListObservable.doOnNext { _ ->
|
||||
// Delete all temporary (unfinished) files
|
||||
tmpDir.listFiles()?.filter { it.name!!.endsWith(".tmp") }?.forEach { it.delete() }
|
||||
// Delete all temporary (unfinished) files
|
||||
tmpDir.listFiles()?.filter { it.name!!.endsWith(".tmp") }?.forEach { it.delete() }
|
||||
|
||||
download.downloadedImages = 0
|
||||
download.status = Download.DOWNLOADING
|
||||
}
|
||||
download.downloadedImages = 0
|
||||
download.status = Download.DOWNLOADING
|
||||
}
|
||||
// Get all the URLs to the source images, fetch pages if necessary
|
||||
.flatMap { download.source.fetchAllImageUrlsFromPageList(it) }
|
||||
// Start downloading images, consider we can have downloaded images already
|
||||
@ -447,7 +447,7 @@ class Downloader(
|
||||
private fun getImageExtension(response: Response, file: UniFile): String {
|
||||
// Read content type if available.
|
||||
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)
|
||||
// Else read magic numbers.
|
||||
?: ImageUtil.findImageType { file.openInputStream() }?.mime
|
||||
|
@ -13,7 +13,7 @@ class DownloadQueue(
|
||||
private val store: DownloadStore,
|
||||
private val queue: MutableList<Download> = CopyOnWriteArrayList<Download>()
|
||||
) :
|
||||
List<Download> by queue {
|
||||
List<Download> by queue {
|
||||
|
||||
private val statusSubject = PublishSubject.create<Download>()
|
||||
|
||||
@ -80,8 +80,8 @@ List<Download> by queue {
|
||||
fun getStatusObservable(): Observable<Download> = statusSubject.onBackpressureBuffer()
|
||||
|
||||
fun getUpdatedObservable(): Observable<List<Download>> = updatedRelay.onBackpressureBuffer()
|
||||
.startWith(Unit)
|
||||
.map { this }
|
||||
.startWith(Unit)
|
||||
.map { this }
|
||||
|
||||
private fun setPagesFor(download: Download) {
|
||||
if (download.status == Download.DOWNLOADING) {
|
||||
@ -105,23 +105,23 @@ List<Download> by queue {
|
||||
|
||||
fun getProgressObservable(): Observable<Download> {
|
||||
return statusSubject.onBackpressureBuffer()
|
||||
.startWith(getActiveDownloads())
|
||||
.flatMap { download ->
|
||||
if (download.status == Download.DOWNLOADING) {
|
||||
val pageStatusSubject = PublishSubject.create<Int>()
|
||||
setPagesSubject(download.pages, pageStatusSubject)
|
||||
downloadListeners.forEach { it.updateDownload(download) }
|
||||
return@flatMap pageStatusSubject
|
||||
.onBackpressureBuffer()
|
||||
.filter { it == Page.READY }
|
||||
.map { download }
|
||||
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
|
||||
setPagesSubject(download.pages, null)
|
||||
downloadListeners.forEach { it.updateDownload(download) }
|
||||
}
|
||||
Observable.just(download)
|
||||
.startWith(getActiveDownloads())
|
||||
.flatMap { download ->
|
||||
if (download.status == Download.DOWNLOADING) {
|
||||
val pageStatusSubject = PublishSubject.create<Int>()
|
||||
setPagesSubject(download.pages, pageStatusSubject)
|
||||
downloadListeners.forEach { it.updateDownload(download) }
|
||||
return@flatMap pageStatusSubject
|
||||
.onBackpressureBuffer()
|
||||
.filter { it == Page.READY }
|
||||
.map { download }
|
||||
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
|
||||
setPagesSubject(download.pages, null)
|
||||
downloadListeners.forEach { it.updateDownload(download) }
|
||||
}
|
||||
.filter { it.status == Download.DOWNLOADING }
|
||||
Observable.just(download)
|
||||
}
|
||||
.filter { it.status == Download.DOWNLOADING }
|
||||
}
|
||||
|
||||
private fun setPagesSubject(pages: List<Page>?, subject: PublishSubject<Int>?) {
|
||||
|
@ -20,7 +20,9 @@ class CoverViewTarget(
|
||||
progress?.gone()
|
||||
view.scaleType = ImageView.ScaleType.CENTER
|
||||
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))
|
||||
view.setImageDrawable(vector)
|
||||
|
@ -70,7 +70,8 @@ class MangaFetcher : Fetcher<Manga> {
|
||||
return fileLoader(coverFile)
|
||||
}
|
||||
val (_, body) = awaitGetCall(
|
||||
manga, if (manga.favorite) {
|
||||
manga,
|
||||
if (manga.favorite) {
|
||||
!options.networkCachePolicy.readEnabled
|
||||
} else {
|
||||
false
|
||||
|
@ -29,7 +29,8 @@ class CustomMangaManager(val context: Context) {
|
||||
|
||||
val json = try {
|
||||
Gson().fromJson(
|
||||
Scanner(editJson).useDelimiter("\\Z").next(), JsonObject::class.java
|
||||
Scanner(editJson).useDelimiter("\\Z").next(),
|
||||
JsonObject::class.java
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
@ -83,7 +84,12 @@ class CustomMangaManager(val context: Context) {
|
||||
|
||||
fun Manga.toJson(): MangaJson {
|
||||
return MangaJson(
|
||||
id!!, title, author, artist, description, genre?.split(", ")?.toTypedArray()
|
||||
id!!,
|
||||
title,
|
||||
author,
|
||||
artist,
|
||||
description,
|
||||
genre?.split(", ")?.toTypedArray()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
Worker(context, workerParams) {
|
||||
Worker(context, workerParams) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
LibraryUpdateService.start(context)
|
||||
@ -37,16 +37,19 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
NetworkType.CONNECTED
|
||||
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(wifiRestriction)
|
||||
.setRequiresCharging(acRestriction)
|
||||
.build()
|
||||
.setRequiredNetworkType(wifiRestriction)
|
||||
.setRequiresCharging(acRestriction)
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
||||
interval.toLong(), TimeUnit.HOURS,
|
||||
10, TimeUnit.MINUTES)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
interval.toLong(),
|
||||
TimeUnit.HOURS,
|
||||
10,
|
||||
TimeUnit.MINUTES
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||||
} else {
|
||||
|
@ -131,60 +131,73 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
val manga = it.key
|
||||
val chapters = it.value
|
||||
val chapterNames = chapters.map { chapter -> chapter.name }
|
||||
notifications.add(Pair(context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||
setSmallIcon(R.drawable.ic_tachi)
|
||||
try {
|
||||
val request = GetRequest.Builder(context).data(manga)
|
||||
.networkCachePolicy(CachePolicy.DISABLED)
|
||||
.transformations(CircleCropTransformation())
|
||||
.size(width = ICON_SIZE, height = ICON_SIZE).build()
|
||||
notifications.add(
|
||||
Pair(
|
||||
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||
setSmallIcon(R.drawable.ic_tachi)
|
||||
try {
|
||||
val request = GetRequest.Builder(context).data(manga)
|
||||
.networkCachePolicy(CachePolicy.DISABLED)
|
||||
.transformations(CircleCropTransformation())
|
||||
.size(width = ICON_SIZE, height = ICON_SIZE).build()
|
||||
|
||||
Coil.imageLoader(context).execute(request).drawable?.let { drawable ->
|
||||
setLargeIcon((drawable as BitmapDrawable).bitmap)
|
||||
Coil.imageLoader(context).execute(request).drawable?.let { drawable ->
|
||||
setLargeIcon((drawable as BitmapDrawable).bitmap)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||
setContentTitle(manga.title)
|
||||
color = ContextCompat.getColor(context, R.color.colorAccent)
|
||||
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
|
||||
"${chapterNames.take(MAX_CHAPTERS - 1)
|
||||
.joinToString(", ")}, " + context.resources.getQuantityString(
|
||||
R.plurals.notification_and_n_more,
|
||||
(chapterNames.size - (MAX_CHAPTERS - 1)),
|
||||
(chapterNames.size - (MAX_CHAPTERS - 1))
|
||||
)
|
||||
} else chapterNames.joinToString(", ")
|
||||
setContentText(chaptersNames)
|
||||
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
|
||||
priority = NotificationCompat.PRIORITY_HIGH
|
||||
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||
setContentIntent(
|
||||
NotificationReceiver.openChapterPendingActivity(
|
||||
context, manga, chapters.first()
|
||||
)
|
||||
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||
setContentTitle(manga.title)
|
||||
color = ContextCompat.getColor(context, R.color.colorAccent)
|
||||
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
|
||||
"${chapterNames.take(MAX_CHAPTERS - 1).joinToString(", ")}, " +
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.notification_and_n_more,
|
||||
(chapterNames.size - (MAX_CHAPTERS - 1)),
|
||||
(chapterNames.size - (MAX_CHAPTERS - 1))
|
||||
)
|
||||
} else chapterNames.joinToString(", ")
|
||||
setContentText(chaptersNames)
|
||||
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
|
||||
priority = NotificationCompat.PRIORITY_HIGH
|
||||
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||
setContentIntent(
|
||||
NotificationReceiver.openChapterPendingActivity(
|
||||
context,
|
||||
manga,
|
||||
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 {
|
||||
|
||||
notify(Notifications.ID_NEW_CHAPTERS,
|
||||
notify(
|
||||
Notifications.ID_NEW_CHAPTERS,
|
||||
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||
setSmallIcon(R.drawable.ic_tachi)
|
||||
setLargeIcon(notificationBitmap)
|
||||
@ -193,14 +206,18 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
if (updates.size > 1) {
|
||||
setContentText(
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.for_n_titles, updates.size, updates.size
|
||||
R.plurals.for_n_titles,
|
||||
updates.size,
|
||||
updates.size
|
||||
)
|
||||
)
|
||||
setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.bigText(updates.keys.joinToString("\n") {
|
||||
it.title.chop(45)
|
||||
})
|
||||
.bigText(
|
||||
updates.keys.joinToString("\n") {
|
||||
it.title.chop(45)
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
setContentText(updates.keys.first().title.chop(45))
|
||||
@ -211,7 +228,8 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
setGroupSummary(true)
|
||||
setContentIntent(getNotificationIntent())
|
||||
setAutoCancel(true)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
notifications.forEach {
|
||||
notify(it.second, it.first)
|
||||
|
@ -8,8 +8,9 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
object LibraryUpdateRanker {
|
||||
|
||||
val rankingScheme = listOf(
|
||||
(this::lexicographicRanking)(),
|
||||
(this::latestFirstRanking)())
|
||||
(this::lexicographicRanking)(),
|
||||
(this::latestFirstRanking)()
|
||||
)
|
||||
|
||||
/**
|
||||
* Provides a total ordering over all the Mangas.
|
||||
@ -22,7 +23,7 @@ object LibraryUpdateRanker {
|
||||
*/
|
||||
fun latestFirstRanking(): Comparator<Manga> {
|
||||
return Comparator { mangaFirst: Manga,
|
||||
mangaSecond: Manga ->
|
||||
mangaSecond: Manga ->
|
||||
compareValues(mangaSecond.last_update, mangaFirst.last_update)
|
||||
}
|
||||
}
|
||||
@ -35,7 +36,7 @@ object LibraryUpdateRanker {
|
||||
*/
|
||||
fun lexicographicRanking(): Comparator<Manga> {
|
||||
return Comparator { mangaFirst: Manga,
|
||||
mangaSecond: Manga ->
|
||||
mangaSecond: Manga ->
|
||||
compareValues(mangaFirst.title, mangaSecond.title)
|
||||
}
|
||||
}
|
||||
|
@ -134,16 +134,18 @@ class LibraryUpdateService(
|
||||
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
|
||||
val savedMangasList = intent.getLongArrayExtra(KEY_MANGAS)?.asList()
|
||||
|
||||
val mangaList = (if (savedMangasList != null) {
|
||||
val mangas = db.getLibraryMangas().executeAsBlocking().filter {
|
||||
it.id in savedMangasList
|
||||
}.distinctBy { it.id }
|
||||
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
|
||||
if (categoryId > -1) categoryIds.add(categoryId)
|
||||
mangas
|
||||
} else {
|
||||
getMangaToUpdate(intent, target)
|
||||
}).sortedWith(rankingScheme[selectedScheme])
|
||||
val mangaList = (
|
||||
if (savedMangasList != null) {
|
||||
val mangas = db.getLibraryMangas().executeAsBlocking().filter {
|
||||
it.id in savedMangasList
|
||||
}.distinctBy { it.id }
|
||||
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
|
||||
if (categoryId > -1) categoryIds.add(categoryId)
|
||||
mangas
|
||||
} else {
|
||||
getMangaToUpdate(intent, target)
|
||||
}
|
||||
).sortedWith(rankingScheme[selectedScheme])
|
||||
// Update favorite manga. Destroy service when completed or in case of an error.
|
||||
launchTarget(target, mangaList, startId)
|
||||
return START_REDELIVER_INTENT
|
||||
@ -157,7 +159,8 @@ class LibraryUpdateService(
|
||||
super.onCreate()
|
||||
notifier = LibraryUpdateNotifier(this)
|
||||
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))
|
||||
startForeground(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build())
|
||||
@ -247,7 +250,9 @@ class LibraryUpdateService(
|
||||
val hasDLs = try {
|
||||
requestSemaphore.withPermit {
|
||||
updateMangaInSource(
|
||||
it.key, downloadNew, categoriesToDownload
|
||||
it.key,
|
||||
downloadNew,
|
||||
categoriesToDownload
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@ -351,13 +356,15 @@ class LibraryUpdateService(
|
||||
var hasDownloads = false
|
||||
while (count < mangaToUpdateMap[source]!!.size) {
|
||||
val shouldDownload =
|
||||
(downloadNew && (categoriesToDownload.isEmpty() || mangaToUpdateMap[source]!![count].category in categoriesToDownload || db.getCategoriesForManga(
|
||||
mangaToUpdateMap[source]!![count]
|
||||
).executeOnIO().any { (it.id ?: -1) in categoriesToDownload }))
|
||||
if (updateMangaChapters(
|
||||
mangaToUpdateMap[source]!![count], this.count.andIncrement, shouldDownload
|
||||
)
|
||||
) {
|
||||
(
|
||||
downloadNew && (
|
||||
categoriesToDownload.isEmpty() ||
|
||||
mangaToUpdateMap[source]!![count].category in categoriesToDownload ||
|
||||
db.getCategoriesForManga(mangaToUpdateMap[source]!![count])
|
||||
.executeOnIO().any { (it.id ?: -1) in categoriesToDownload }
|
||||
)
|
||||
)
|
||||
if (updateMangaChapters(mangaToUpdateMap[source]!![count], this.count.andIncrement, shouldDownload)) {
|
||||
hasDownloads = true
|
||||
}
|
||||
count++
|
||||
@ -372,47 +379,47 @@ class LibraryUpdateService(
|
||||
shouldDownload: Boolean
|
||||
):
|
||||
Boolean {
|
||||
try {
|
||||
var hasDownloads = false
|
||||
if (job?.isCancelled == true) {
|
||||
try {
|
||||
var hasDownloads = false
|
||||
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
|
||||
}
|
||||
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>) {
|
||||
// We don't want to start downloading while the library is updating, because websites
|
||||
|
@ -58,29 +58,41 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
// Clear the download queue
|
||||
ACTION_CLEAR_DOWNLOADS -> downloadManager.clearQueue(true)
|
||||
// Launch share activity and dismiss notification
|
||||
ACTION_SHARE_IMAGE -> shareImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
|
||||
ACTION_SHARE_IMAGE -> shareImage(
|
||||
context,
|
||||
intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Delete image from path and dismiss notification
|
||||
ACTION_DELETE_IMAGE -> deleteImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
|
||||
ACTION_DELETE_IMAGE -> deleteImage(
|
||||
context,
|
||||
intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Cancel library update and dismiss notification
|
||||
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context)
|
||||
ACTION_CANCEL_RESTORE -> cancelRestoreUpdate(context)
|
||||
// Share backup file
|
||||
ACTION_SHARE_BACKUP ->
|
||||
shareBackup(
|
||||
context, intent.getParcelableExtra(EXTRA_URI),
|
||||
context,
|
||||
intent.getParcelableExtra(EXTRA_URI),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Open reader activity
|
||||
ACTION_OPEN_CHAPTER -> {
|
||||
openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
||||
intent.getLongExtra(EXTRA_CHAPTER_ID, -1))
|
||||
openChapter(
|
||||
context,
|
||||
intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
||||
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
|
||||
)
|
||||
}
|
||||
ACTION_MARK_AS_READ -> {
|
||||
val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
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 mangaId = intent.getLongExtra(EXTRA_MANGA_ID, -1)
|
||||
@ -342,7 +354,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
context: Context,
|
||||
notificationId: Int,
|
||||
groupId: Int? =
|
||||
null
|
||||
null
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val groupKey = context.notificationManager.activeNotifications.find {
|
||||
@ -377,8 +389,13 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
clipData = ClipData.newRawUri(null, uri)
|
||||
type = "image/*"
|
||||
}
|
||||
return PendingIntent.getActivity(context, 0, shareIntent, PendingIntent
|
||||
.FLAG_CANCEL_CURRENT)
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
shareIntent,
|
||||
PendingIntent
|
||||
.FLAG_CANCEL_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -409,11 +426,16 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
context: Context,
|
||||
manga: Manga,
|
||||
chapter:
|
||||
Chapter
|
||||
Chapter
|
||||
): PendingIntent {
|
||||
val newIntent = ReaderActivity.newIntent(context, manga, chapter)
|
||||
return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent
|
||||
.FLAG_UPDATE_CURRENT)
|
||||
return PendingIntent.getActivity(
|
||||
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):
|
||||
PendingIntent {
|
||||
val newIntent =
|
||||
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
.putExtra(MangaDetailsController.MANGA_EXTRA, manga.id)
|
||||
.putExtra("notificationId", manga.id.hashCode())
|
||||
.putExtra("groupId", groupId)
|
||||
return PendingIntent.getActivity(
|
||||
context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
val newIntent =
|
||||
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
.putExtra(MangaDetailsController.MANGA_EXTRA, manga.id)
|
||||
.putExtra("notificationId", manga.id.hashCode())
|
||||
.putExtra("groupId", groupId)
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
manga.id.hashCode(),
|
||||
newIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
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 {
|
||||
setDataAndType(uri, "text/plain")
|
||||
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)
|
||||
}
|
||||
@ -488,19 +516,19 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
context: Context,
|
||||
manga: Manga,
|
||||
chapters:
|
||||
Array<Chapter>,
|
||||
Array<Chapter>,
|
||||
groupId: Int
|
||||
):
|
||||
PendingIntent {
|
||||
val newIntent = Intent(context, NotificationReceiver::class.java).apply {
|
||||
action = ACTION_MARK_AS_READ
|
||||
putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())
|
||||
putExtra(EXTRA_MANGA_ID, manga.id)
|
||||
putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode())
|
||||
putExtra(EXTRA_GROUP_ID, groupId)
|
||||
val newIntent = Intent(context, NotificationReceiver::class.java).apply {
|
||||
action = ACTION_MARK_AS_READ
|
||||
putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())
|
||||
putExtra(EXTRA_MANGA_ID, manga.id)
|
||||
putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode())
|
||||
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
|
||||
|
@ -61,37 +61,44 @@ object Notifications {
|
||||
fun createChannels(context: Context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
|
||||
val channels = listOf(NotificationChannel(
|
||||
CHANNEL_COMMON,
|
||||
context.getString(R.string.common),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
), NotificationChannel(
|
||||
CHANNEL_LIBRARY,
|
||||
context.getString(R.string.updating_library),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
}, NotificationChannel(
|
||||
CHANNEL_DOWNLOADER,
|
||||
context.getString(R.string.downloads),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
}, NotificationChannel(
|
||||
CHANNEL_UPDATES_TO_EXTS,
|
||||
context.getString(R.string.extension_updates),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
), NotificationChannel(
|
||||
CHANNEL_NEW_CHAPTERS,
|
||||
context.getString(R.string.new_chapters),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
), NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE,
|
||||
context.getString(R.string.restoring_backup),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
})
|
||||
val channels = listOf(
|
||||
NotificationChannel(
|
||||
CHANNEL_COMMON,
|
||||
context.getString(R.string.common),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_LIBRARY,
|
||||
context.getString(R.string.updating_library),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER,
|
||||
context.getString(R.string.downloads),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_UPDATES_TO_EXTS,
|
||||
context.getString(R.string.extension_updates),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_NEW_CHAPTERS,
|
||||
context.getString(R.string.new_chapters),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE,
|
||||
context.getString(R.string.restoring_backup),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
}
|
||||
)
|
||||
context.notificationManager.createNotificationChannels(channels)
|
||||
}
|
||||
}
|
||||
|
@ -43,12 +43,20 @@ class PreferencesHelper(val context: Context) {
|
||||
private val flowPrefs = FlowSharedPreferences(prefs)
|
||||
|
||||
private val defaultDownloadsDir = Uri.fromFile(
|
||||
File(Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name), "downloads"))
|
||||
File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name),
|
||||
"downloads"
|
||||
)
|
||||
)
|
||||
|
||||
private val defaultBackupDir = Uri.fromFile(
|
||||
File(Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name), "backup"))
|
||||
File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
context.getString(R.string.app_name),
|
||||
"backup"
|
||||
)
|
||||
)
|
||||
|
||||
fun getInt(key: String, default: Int?) = rxPrefs.getInteger(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) {
|
||||
prefs.edit()
|
||||
.putString(Keys.trackUsername(sync.id), username)
|
||||
.putString(Keys.trackPassword(sync.id), password)
|
||||
.apply()
|
||||
.putString(Keys.trackUsername(sync.id), username)
|
||||
.putString(Keys.trackPassword(sync.id), password)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun trackToken(sync: TrackService) = rxPrefs.getString(Keys.trackToken(sync.id), "")
|
||||
|
@ -60,7 +60,7 @@ abstract class TrackService(val id: Int) {
|
||||
|
||||
open val isLogged: Boolean
|
||||
get() = getUsername().isNotEmpty() &&
|
||||
getPassword().isNotEmpty()
|
||||
getPassword().isNotEmpty()
|
||||
|
||||
fun getUsername() = preferences.trackUsername(this)!!
|
||||
|
||||
|
@ -237,7 +237,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
.appendQueryParameter("response_type", "token")
|
||||
.build()!!
|
||||
|
||||
fun addToLibraryQuery() = """
|
||||
fun addToLibraryQuery() =
|
||||
"""
|
||||
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|
||||
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
|
||||
| id
|
||||
@ -246,7 +247,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
|}
|
||||
|""".trimMargin()
|
||||
|
||||
fun deleteFromLibraryQuery() = """
|
||||
fun deleteFromLibraryQuery() =
|
||||
"""
|
||||
|mutation DeleteManga(${'$'}listId: Int) {
|
||||
|DeleteMediaListEntry (id: ${'$'}listId) {
|
||||
|deleted
|
||||
@ -254,7 +256,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
|}
|
||||
|}""".trimMargin()
|
||||
|
||||
fun updateInLibraryQuery() = """
|
||||
fun updateInLibraryQuery() =
|
||||
"""
|
||||
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|
||||
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|
||||
|id
|
||||
@ -264,7 +267,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
|}
|
||||
|""".trimMargin()
|
||||
|
||||
fun searchQuery() = """
|
||||
fun searchQuery() =
|
||||
"""
|
||||
|query Search(${'$'}query: String) {
|
||||
|Page (perPage: 50) {
|
||||
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|
||||
@ -289,7 +293,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
|}
|
||||
|""".trimMargin()
|
||||
|
||||
fun findLibraryMangaQuery() = """
|
||||
fun findLibraryMangaQuery() =
|
||||
"""
|
||||
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|
||||
|Page {
|
||||
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
|
||||
@ -320,7 +325,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
|}
|
||||
|""".trimMargin()
|
||||
|
||||
fun currentUserQuery() = """
|
||||
fun currentUserQuery() =
|
||||
"""
|
||||
|query User {
|
||||
|Viewer {
|
||||
|id
|
||||
|
@ -38,8 +38,8 @@ class AnilistInterceptor(private val anilist: Anilist, private var token: String
|
||||
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.build()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
}
|
||||
|
@ -36,25 +36,28 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
|
||||
}
|
||||
|
||||
val authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder()
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.url(originalRequest.url.newBuilder()
|
||||
.addQueryParameter("access_token", currAuth.access_token).build())
|
||||
.build() else originalRequest.newBuilder()
|
||||
.post(addTocken(currAuth.access_token, originalRequest.body as FormBody))
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.build()
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.url(
|
||||
originalRequest.url.newBuilder()
|
||||
.addQueryParameter("access_token", currAuth.access_token).build()
|
||||
)
|
||||
.build() else originalRequest.newBuilder()
|
||||
.post(addTocken(currAuth.access_token, originalRequest.body as FormBody))
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
}
|
||||
|
||||
fun newAuth(oauth: OAuth) {
|
||||
this.oauth = OAuth(
|
||||
oauth.access_token,
|
||||
oauth.token_type,
|
||||
System.currentTimeMillis() / 1000,
|
||||
oauth.expires_in,
|
||||
oauth.refresh_token,
|
||||
this.oauth?.user_id)
|
||||
oauth.access_token,
|
||||
oauth.token_type,
|
||||
System.currentTimeMillis() / 1000,
|
||||
oauth.expires_in,
|
||||
oauth.refresh_token,
|
||||
this.oauth?.user_id
|
||||
)
|
||||
|
||||
bangumi.saveToken(oauth)
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ data class OAuth(
|
||||
val refresh_token: String?,
|
||||
val user_id: Long?
|
||||
) {
|
||||
// Access token refresh before expired
|
||||
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
|
||||
// Access token refresh before expired
|
||||
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
|
||||
}
|
||||
|
||||
data class Status(
|
||||
|
@ -30,10 +30,10 @@ class KitsuInterceptor(val kitsu: Kitsu, val gson: Gson) : Interceptor {
|
||||
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.build()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
||||
val request = Request.Builder().url(url.toString()).get().build()
|
||||
|
||||
val urlMangas = "$apiUrl/mangas".toUri().buildUpon().appendPath(track.media_id.toString())
|
||||
.build()
|
||||
.build()
|
||||
val requestMangas = Request.Builder().url(urlMangas.toString()).get().build()
|
||||
|
||||
val requestMangasResponse = authClient.newCall(requestMangas).execute()
|
||||
|
@ -29,9 +29,9 @@ class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Intercept
|
||||
}
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.build()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
}
|
||||
|
@ -44,7 +44,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
android.R.drawable.stat_sys_download_done,
|
||||
context.getString(R.string.download),
|
||||
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() {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<UpdaterJob>(
|
||||
1, TimeUnit.DAYS,
|
||||
1, TimeUnit.HOURS)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
1,
|
||||
TimeUnit.DAYS,
|
||||
1,
|
||||
TimeUnit.HOURS
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||||
}
|
||||
|
@ -75,13 +75,17 @@ internal class UpdaterNotifier(private val context: Context) {
|
||||
setProgress(0, 0, false)
|
||||
// Install action
|
||||
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
|
||||
addAction(R.drawable.ic_system_update_24dp,
|
||||
context.getString(R.string.install),
|
||||
NotificationHandler.installApkPendingActivity(context, uri))
|
||||
addAction(
|
||||
R.drawable.ic_system_update_24dp,
|
||||
context.getString(R.string.install),
|
||||
NotificationHandler.installApkPendingActivity(context, uri)
|
||||
)
|
||||
// Cancel action
|
||||
addAction(R.drawable.ic_close_24dp,
|
||||
context.getString(R.string.cancel),
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
|
||||
addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
context.getString(R.string.cancel),
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
|
||||
)
|
||||
}
|
||||
notification.show()
|
||||
}
|
||||
@ -99,13 +103,17 @@ internal class UpdaterNotifier(private val context: Context) {
|
||||
setProgress(0, 0, false)
|
||||
color = ContextCompat.getColor(context, R.color.colorAccent)
|
||||
// Retry action
|
||||
addAction(R.drawable.ic_refresh_24dp,
|
||||
context.getString(R.string.retry),
|
||||
UpdaterService.downloadApkPendingService(context, url))
|
||||
addAction(
|
||||
R.drawable.ic_refresh_24dp,
|
||||
context.getString(R.string.retry),
|
||||
UpdaterService.downloadApkPendingService(context, url)
|
||||
)
|
||||
// Cancel action
|
||||
addAction(R.drawable.ic_close_24dp,
|
||||
context.getString(R.string.cancel),
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
|
||||
addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
context.getString(R.string.cancel),
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
|
||||
)
|
||||
}
|
||||
notification.show(Notifications.ID_UPDATER)
|
||||
}
|
||||
|
@ -43,7 +43,8 @@ class UpdaterService : Service() {
|
||||
startForeground(Notifications.ID_UPDATER, notifier.onDownloadStarted(getString(R.string.app_name)).build())
|
||||
|
||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock"
|
||||
PowerManager.PARTIAL_WAKE_LOCK,
|
||||
"${javaClass.name}:WakeLock"
|
||||
)
|
||||
wakeLock.acquire()
|
||||
}
|
||||
|
@ -15,10 +15,10 @@ interface GithubService {
|
||||
companion object {
|
||||
fun create(): GithubService {
|
||||
val restAdapter = Retrofit.Builder()
|
||||
.baseUrl("https://api.github.com")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(Injekt.get<NetworkHelper>().client)
|
||||
.build()
|
||||
.baseUrl("https://api.github.com")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(Injekt.get<NetworkHelper>().client)
|
||||
.build()
|
||||
|
||||
return restAdapter.create(GithubService::class.java)
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateChecker
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateResult
|
||||
|
||||
class GithubUpdateChecker : UpdateChecker() {
|
||||
class GithubUpdateChecker : UpdateChecker() {
|
||||
|
||||
private val service: GithubService = GithubService.create()
|
||||
|
||||
|
@ -80,8 +80,7 @@ class ExtensionManager(
|
||||
context.packageManager.getApplicationIcon(pkgName)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
else null
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,16 +135,16 @@ class ExtensionManager(
|
||||
val extensions = ExtensionLoader.loadExtensions(context)
|
||||
|
||||
installedExtensions = extensions
|
||||
.filterIsInstance<LoadResult.Success>()
|
||||
.map { it.extension }
|
||||
.filterIsInstance<LoadResult.Success>()
|
||||
.map { it.extension }
|
||||
installedExtensions
|
||||
.flatMap { it.sources }
|
||||
// overwrite is needed until the bundled sources are removed
|
||||
.forEach { sourceManager.registerSource(it, true) }
|
||||
.flatMap { it.sources }
|
||||
// overwrite is needed until the bundled sources are removed
|
||||
.forEach { sourceManager.registerSource(it, true) }
|
||||
|
||||
untrustedExtensions = extensions
|
||||
.filterIsInstance<LoadResult.Untrusted>()
|
||||
.map { it.extension }
|
||||
.filterIsInstance<LoadResult.Untrusted>()
|
||||
.map { it.extension }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -251,7 +250,7 @@ class ExtensionManager(
|
||||
*/
|
||||
fun updateExtension(extension: Extension.Installed): Observable<InstallStep> {
|
||||
val availableExt = availableExtensions.find { it.pkgName == extension.pkgName }
|
||||
?: return Observable.empty()
|
||||
?: return Observable.empty()
|
||||
return installExtension(availableExt)
|
||||
}
|
||||
|
||||
@ -296,10 +295,10 @@ class ExtensionManager(
|
||||
nowTrustedExtensions.map { extension ->
|
||||
async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) }
|
||||
}.map { it.await() }.forEach { result ->
|
||||
if (result is LoadResult.Success) {
|
||||
registerNewExtension(result.extension)
|
||||
}
|
||||
if (result is LoadResult.Success) {
|
||||
registerNewExtension(result.extension)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,12 +45,15 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
val preferences: PreferencesHelper by injectLazy()
|
||||
preferences.extensionUpdatesCount().set(names.size)
|
||||
NotificationManagerCompat.from(context).apply {
|
||||
notify(Notifications.ID_UPDATES_TO_EXTS,
|
||||
notify(
|
||||
Notifications.ID_UPDATES_TO_EXTS,
|
||||
context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) {
|
||||
setContentTitle(
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.extension_updates_available, names
|
||||
.size, names.size
|
||||
R.plurals.extension_updates_available,
|
||||
names
|
||||
.size,
|
||||
names.size
|
||||
)
|
||||
)
|
||||
val extNames = names.joinToString(", ")
|
||||
@ -64,7 +67,8 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
)
|
||||
)
|
||||
setAutoCancel(true)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,8 +84,11 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
|
||||
12, TimeUnit.HOURS,
|
||||
1, TimeUnit.HOURS)
|
||||
12,
|
||||
TimeUnit.HOURS,
|
||||
1,
|
||||
TimeUnit.HOURS
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
@ -18,9 +18,9 @@ class ExtensionInstallActivity : Activity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||
.setDataAndType(intent.data, intent.type)
|
||||
.putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
||||
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
.setDataAndType(intent.data, intent.type)
|
||||
.putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
||||
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
try {
|
||||
startActivityForResult(installIntent, INSTALL_REQUEST_CODE)
|
||||
|
@ -19,7 +19,7 @@ import kotlinx.coroutines.async
|
||||
* @param listener The listener that should be notified of extension installation events.
|
||||
*/
|
||||
internal class ExtensionInstallReceiver(private val listener: Listener) :
|
||||
BroadcastReceiver() {
|
||||
BroadcastReceiver() {
|
||||
|
||||
/**
|
||||
* Registers this broadcast receiver
|
||||
@ -94,7 +94,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
|
||||
*/
|
||||
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ internal class ExtensionInstaller(private val context: Context) {
|
||||
fun uninstallApk(pkgName: String) {
|
||||
val packageUri = "package:$pkgName".toUri()
|
||||
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
@ -36,9 +36,9 @@ internal object ExtensionLoader {
|
||||
* List of the trusted signatures.
|
||||
*/
|
||||
var trustedSignatures = mutableSetOf<String>() +
|
||||
Injekt.get<PreferencesHelper>().trustedSignatures().getOrDefault() +
|
||||
// inorichi's key
|
||||
"7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
||||
Injekt.get<PreferencesHelper>().trustedSignatures().getOrDefault() +
|
||||
// inorichi's key
|
||||
"7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
||||
|
||||
/**
|
||||
* Return a list of all the installed extensions initialized concurrently.
|
||||
@ -103,8 +103,10 @@ internal object ExtensionLoader {
|
||||
// Validate lib version
|
||||
val majorLibVersion = versionName.substringBefore('.').toInt()
|
||||
if (majorLibVersion < LIB_VERSION_MIN || majorLibVersion > LIB_VERSION_MAX) {
|
||||
val exception = Exception("Lib version is $majorLibVersion, while only versions " +
|
||||
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed")
|
||||
val exception = Exception(
|
||||
"Lib version is $majorLibVersion, while only versions " +
|
||||
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
|
||||
)
|
||||
Timber.w(exception)
|
||||
return LoadResult.Error(exception)
|
||||
}
|
||||
@ -122,30 +124,30 @@ internal object ExtensionLoader {
|
||||
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
|
||||
|
||||
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
|
||||
.split(";")
|
||||
.map {
|
||||
val sourceClass = it.trim()
|
||||
if (sourceClass.startsWith("."))
|
||||
pkgInfo.packageName + sourceClass
|
||||
else
|
||||
sourceClass
|
||||
}
|
||||
.flatMap {
|
||||
try {
|
||||
val obj = Class.forName(it, false, classLoader).newInstance()
|
||||
when (obj) {
|
||||
is Source -> listOf(obj)
|
||||
is SourceFactory -> obj.createSources()
|
||||
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Extension load error: $extName.")
|
||||
return LoadResult.Error(e)
|
||||
.split(";")
|
||||
.map {
|
||||
val sourceClass = it.trim()
|
||||
if (sourceClass.startsWith("."))
|
||||
pkgInfo.packageName + sourceClass
|
||||
else
|
||||
sourceClass
|
||||
}
|
||||
.flatMap {
|
||||
try {
|
||||
val obj = Class.forName(it, false, classLoader).newInstance()
|
||||
when (obj) {
|
||||
is Source -> listOf(obj)
|
||||
is SourceFactory -> obj.createSources()
|
||||
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Extension load error: $extName.")
|
||||
return LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
val langs = sources.filterIsInstance<CatalogueSource>()
|
||||
.map { it.lang }
|
||||
.toSet()
|
||||
.map { it.lang }
|
||||
.toSet()
|
||||
|
||||
val lang = when (langs.size) {
|
||||
0 -> ""
|
||||
|
@ -58,17 +58,19 @@ fun Call.asObservable(): Observable<Response> {
|
||||
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
|
||||
suspend fun Call.await(): Response {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
enqueue(object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
continuation.resume(response)
|
||||
}
|
||||
enqueue(
|
||||
object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
continuation.resume(response)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
// Don't bother with resuming the continuation if it is already cancelled.
|
||||
if (continuation.isCancelled) return
|
||||
continuation.resumeWithException(e)
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
// Don't bother with resuming the continuation if it is already cancelled.
|
||||
if (continuation.isCancelled) return
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
try {
|
||||
@ -91,14 +93,14 @@ fun Call.asObservableSuccess(): Observable<Response> {
|
||||
|
||||
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
|
||||
val progressClient = newBuilder()
|
||||
.cache(null)
|
||||
.addNetworkInterceptor { chain ->
|
||||
val originalResponse = chain.proceed(chain.request())
|
||||
originalResponse.newBuilder()
|
||||
.body(ProgressResponseBody(originalResponse.body!!, listener))
|
||||
.build()
|
||||
}
|
||||
.build()
|
||||
.cache(null)
|
||||
.addNetworkInterceptor { chain ->
|
||||
val originalResponse = chain.proceed(chain.request())
|
||||
originalResponse.newBuilder()
|
||||
.body(ProgressResponseBody(originalResponse.body!!, listener))
|
||||
.build()
|
||||
}
|
||||
.build()
|
||||
|
||||
return progressClient.newCall(request)
|
||||
}
|
||||
|
@ -18,10 +18,10 @@ fun GET(
|
||||
): Request {
|
||||
|
||||
return Request.Builder()
|
||||
.url(url)
|
||||
.headers(headers)
|
||||
.cacheControl(cache)
|
||||
.build()
|
||||
.url(url)
|
||||
.headers(headers)
|
||||
.cacheControl(cache)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun POST(
|
||||
@ -32,9 +32,9 @@ fun POST(
|
||||
): Request {
|
||||
|
||||
return Request.Builder()
|
||||
.url(url)
|
||||
.post(body)
|
||||
.headers(headers)
|
||||
.cacheControl(cache)
|
||||
.build()
|
||||
.url(url)
|
||||
.post(body)
|
||||
.headers(headers)
|
||||
.cacheControl(cache)
|
||||
.build()
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ class UserAgentInterceptor : Interceptor {
|
||||
|
||||
return if (originalRequest.header("User-Agent").isNullOrEmpty()) {
|
||||
val newRequest = originalRequest
|
||||
.newBuilder()
|
||||
.removeHeader("User-Agent")
|
||||
.addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT)
|
||||
.build()
|
||||
.newBuilder()
|
||||
.removeHeader("User-Agent")
|
||||
.addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT)
|
||||
.build()
|
||||
chain.proceed(newRequest)
|
||||
} else {
|
||||
chain.proceed(originalRequest)
|
||||
|
@ -143,7 +143,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
val baseDirs = getBaseDirectories(context)
|
||||
baseDirs
|
||||
baseDirs
|
||||
.mapNotNull { File(it, manga.url).listFiles()?.toList() }
|
||||
.flatten()
|
||||
.filter { it.extension == "json" }.firstOrNull()?.apply {
|
||||
@ -241,10 +241,12 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||
date_upload = chapterFile.lastModified()
|
||||
ChapterRecognition.parseChapterNumber(this, manga)
|
||||
}
|
||||
}.sortedWith(Comparator { c1, c2 ->
|
||||
val c = c2.chapter_number.compareTo(c1.chapter_number)
|
||||
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
|
||||
})
|
||||
}.sortedWith(
|
||||
Comparator { c1, c2 ->
|
||||
val c = c2.chapter_number.compareTo(c1.chapter_number)
|
||||
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
|
||||
}
|
||||
)
|
||||
|
||||
return Observable.just(chapters)
|
||||
}
|
||||
@ -289,18 +291,22 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||
return when (format) {
|
||||
is Format.Directory -> {
|
||||
val entry = format.file.listFiles()
|
||||
?.sortedWith(Comparator<File> { f1, f2 ->
|
||||
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
|
||||
})
|
||||
?.sortedWith(
|
||||
Comparator<File> { f1, f2 ->
|
||||
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
|
||||
}
|
||||
)
|
||||
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
||||
|
||||
entry?.let { updateCover(context, manga, it.inputStream()) }
|
||||
}
|
||||
is Format.Zip -> {
|
||||
ZipFile(format.file).use { zip ->
|
||||
val entry = zip.entries().toList().sortedWith(Comparator<ZipEntry> { f1, f2 ->
|
||||
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
|
||||
}).find {
|
||||
val entry = zip.entries().toList().sortedWith(
|
||||
Comparator<ZipEntry> { f1, f2 ->
|
||||
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
|
||||
}
|
||||
).find {
|
||||
!it.isDirectory && ImageUtil.isImage(it.name) {
|
||||
zip.getInputStream(it)
|
||||
}
|
||||
@ -311,9 +317,11 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||
}
|
||||
is Format.Rar -> {
|
||||
Archive(format.file).use { archive ->
|
||||
val entry = archive.fileHeaders.sortedWith(Comparator<FileHeader> { f1, f2 ->
|
||||
f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString)
|
||||
}).find {
|
||||
val entry = archive.fileHeaders.sortedWith(
|
||||
Comparator<FileHeader> { f1, f2 ->
|
||||
f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString)
|
||||
}
|
||||
).find {
|
||||
!it.isDirectory && ImageUtil.isImage(it.fileNameString) {
|
||||
archive.getInputStream(it)
|
||||
}
|
||||
|
@ -21,13 +21,24 @@ open class SourceManager(private val context: Context) {
|
||||
|
||||
private val delegatedSources = listOf(
|
||||
DelegatedSource(
|
||||
"reader.kireicake.com", 5509224355268673176, KireiCake()
|
||||
), DelegatedSource(
|
||||
"jaiminisbox.com", 9064882169246918586, FoolSlide("jaiminis", "/reader")
|
||||
), DelegatedSource(
|
||||
"mangadex.org", 2499283573021220255, MangaDex()
|
||||
), DelegatedSource(
|
||||
"mangaplus.shueisha.co.jp", 1998944621602463790, MangaPlus()
|
||||
"reader.kireicake.com",
|
||||
5509224355268673176,
|
||||
KireiCake()
|
||||
),
|
||||
DelegatedSource(
|
||||
"jaiminisbox.com",
|
||||
9064882169246918586,
|
||||
FoolSlide("jaiminis", "/reader")
|
||||
),
|
||||
DelegatedSource(
|
||||
"mangadex.org",
|
||||
2499283573021220255,
|
||||
MangaDex()
|
||||
),
|
||||
DelegatedSource(
|
||||
"mangaplus.shueisha.co.jp",
|
||||
1998944621602463790,
|
||||
MangaPlus()
|
||||
)
|
||||
).associateBy { it.sourceId }
|
||||
|
||||
@ -92,8 +103,10 @@ open class SourceManager(private val context: Context) {
|
||||
private fun getSourceNotInstalledException(): Exception {
|
||||
return SourceNotFoundException(
|
||||
context.getString(
|
||||
R.string.source_not_installed_, id.toString()
|
||||
), id
|
||||
R.string.source_not_installed_,
|
||||
id.toString()
|
||||
),
|
||||
id
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -90,10 +90,10 @@ abstract class HttpSource : CatalogueSource {
|
||||
*/
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
return client.newCall(popularMangaRequest(page))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
popularMangaParse(response)
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
popularMangaParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,10 +120,10 @@ abstract class HttpSource : CatalogueSource {
|
||||
*/
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return client.newCall(searchMangaRequest(page, query, filters))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
searchMangaParse(response)
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
searchMangaParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -149,10 +149,10 @@ abstract class HttpSource : CatalogueSource {
|
||||
*/
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
return client.newCall(latestUpdatesRequest(page))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
latestUpdatesParse(response)
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
latestUpdatesParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,10 +177,10 @@ abstract class HttpSource : CatalogueSource {
|
||||
*/
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
mangaDetailsParse(response).apply { initialized = true }
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
mangaDetailsParse(response).apply { initialized = true }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -209,10 +209,10 @@ abstract class HttpSource : CatalogueSource {
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return if (manga.status != SManga.LICENSED) {
|
||||
client.newCall(chapterListRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
chapterListParse(response)
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
chapterListParse(response)
|
||||
}
|
||||
} else {
|
||||
Observable.error(Exception("Licensed - No chapters to show"))
|
||||
}
|
||||
@ -242,10 +242,10 @@ abstract class HttpSource : CatalogueSource {
|
||||
*/
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
return client.newCall(pageListRequest(chapter))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
pageListParse(response)
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
pageListParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -273,8 +273,8 @@ abstract class HttpSource : CatalogueSource {
|
||||
*/
|
||||
open fun fetchImageUrl(page: Page): Observable<String> {
|
||||
return client.newCall(imageUrlRequest(page))
|
||||
.asObservableSuccess()
|
||||
.map { imageUrlParse(it) }
|
||||
.asObservableSuccess()
|
||||
.map { imageUrlParse(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -301,7 +301,7 @@ abstract class HttpSource : CatalogueSource {
|
||||
*/
|
||||
fun fetchImage(page: Page): Observable<Response> {
|
||||
return client.newCallWithProgress(imageRequest(page), page)
|
||||
.asObservableSuccess()
|
||||
.asObservableSuccess()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,12 +14,12 @@ fun HttpSource.getImageUrl(page: Page): Observable<Page> {
|
||||
|
||||
fun HttpSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
|
||||
return Observable.from(pages)
|
||||
.filter { !it.imageUrl.isNullOrEmpty() }
|
||||
.mergeWith(fetchRemainingImageUrlsFromPageList(pages))
|
||||
.filter { !it.imageUrl.isNullOrEmpty() }
|
||||
.mergeWith(fetchRemainingImageUrlsFromPageList(pages))
|
||||
}
|
||||
|
||||
fun HttpSource.fetchRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
|
||||
return Observable.from(pages)
|
||||
.filter { it.imageUrl.isNullOrEmpty() }
|
||||
.concatMap { getImageUrl(it) }
|
||||
.filter { it.imageUrl.isNullOrEmpty() }
|
||||
.concatMap { getImageUrl(it) }
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
open class FoolSlide(override val domainName: String, private val urlModifier: String = "") :
|
||||
DelegatedHttpSource
|
||||
DelegatedHttpSource
|
||||
() {
|
||||
|
||||
override fun canOpenUrl(uri: Uri): Boolean = true
|
||||
@ -34,7 +34,11 @@ DelegatedHttpSource
|
||||
val chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null
|
||||
val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull()?.toString()
|
||||
return "$urlModifier/read/" + listOfNotNull(
|
||||
mangaName, lang, volume, chapterNumber, subChapterNumber
|
||||
mangaName,
|
||||
lang,
|
||||
volume,
|
||||
chapterNumber,
|
||||
subChapterNumber
|
||||
).joinToString("/") + "/"
|
||||
}
|
||||
|
||||
@ -95,8 +99,11 @@ DelegatedHttpSource
|
||||
private fun allowAdult(request: Request) = allowAdult(request.url.toString())
|
||||
|
||||
private fun allowAdult(url: String): Request {
|
||||
return POST(url, body = FormBody.Builder()
|
||||
.add("adult", "true")
|
||||
.build())
|
||||
return POST(
|
||||
url,
|
||||
body = FormBody.Builder()
|
||||
.add("adult", "true")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,8 @@ class KireiCake : FoolSlide("kireicake") {
|
||||
this.url = url
|
||||
source = delegate?.id ?: -1
|
||||
title = document.select("$mangaDetailsInfoSelector li:has(b:contains(title))").first()
|
||||
?.ownText()?.substringAfter(":")?.trim() ?: url.split("/").last().replace(
|
||||
"_", " " + ""
|
||||
).capitalizeWords()
|
||||
?.ownText()?.substringAfter(":")?.trim()
|
||||
?: url.split("/").last().replace("_", " " + "").capitalizeWords()
|
||||
description =
|
||||
document.select("$mangaDetailsInfoSelector li:has(b:contains(description))").first()
|
||||
?.ownText()?.substringAfter(":")
|
||||
|
@ -61,9 +61,13 @@ class MangaPlus : DelegatedHttpSource() {
|
||||
context.getString(R.string.chapter_not_found)
|
||||
)
|
||||
if (manga != null) {
|
||||
Triple(trueChapter, manga.apply {
|
||||
this.title = trimmedTitle
|
||||
}, chapters.orEmpty())
|
||||
Triple(
|
||||
trueChapter,
|
||||
manga.apply {
|
||||
this.title = trimmedTitle
|
||||
},
|
||||
chapters.orEmpty()
|
||||
)
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
@ -33,12 +33,16 @@ class CenteredToolbar@JvmOverloads constructor(context: Context, attrs: Attribut
|
||||
}
|
||||
|
||||
fun showDropdown(down: Boolean = true) {
|
||||
toolbar_title.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_blank_24dp, 0,
|
||||
if (down) {
|
||||
R.drawable.ic_arrow_drop_down_24dp
|
||||
} else {
|
||||
R.drawable.ic_arrow_drop_up_24dp
|
||||
}, 0)
|
||||
toolbar_title.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_blank_24dp,
|
||||
0,
|
||||
if (down) {
|
||||
R.drawable.ic_arrow_drop_down_24dp
|
||||
} else {
|
||||
R.drawable.ic_arrow_drop_up_24dp
|
||||
},
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
fun hideDropdown() {
|
||||
|
@ -21,7 +21,9 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
|
||||
var scrollOffset = 0
|
||||
init {
|
||||
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
|
||||
ignoreTouchesOutsideHandle = false
|
||||
@ -85,7 +87,8 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
|
||||
val targetPos = getTargetPos(y)
|
||||
if (layoutManager is StaggeredGridLayoutManager) {
|
||||
(layoutManager as StaggeredGridLayoutManager).scrollToPositionWithOffset(
|
||||
targetPos, scrollOffset
|
||||
targetPos,
|
||||
scrollOffset
|
||||
)
|
||||
} else {
|
||||
(layoutManager as LinearLayoutManager).scrollToPositionWithOffset(targetPos, scrollOffset)
|
||||
|
@ -119,7 +119,10 @@ class MaterialMenuSheet(
|
||||
isElevated = elevate
|
||||
elevationAnimator?.cancel()
|
||||
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()
|
||||
}
|
||||
|
@ -14,31 +14,34 @@ import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.*
|
||||
import timber.log.Timber
|
||||
|
||||
abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateController(bundle),
|
||||
LayoutContainer {
|
||||
abstract class BaseController(bundle: Bundle? = null) :
|
||||
RestoreViewOnCreateController(bundle),
|
||||
LayoutContainer {
|
||||
|
||||
init {
|
||||
addLifecycleListener(object : LifecycleListener() {
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
onViewCreated(view)
|
||||
}
|
||||
addLifecycleListener(
|
||||
object : LifecycleListener() {
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
onViewCreated(view)
|
||||
}
|
||||
|
||||
override fun preCreateView(controller: Controller) {
|
||||
Timber.d("Create view for ${controller.instance()}")
|
||||
}
|
||||
override fun preCreateView(controller: Controller) {
|
||||
Timber.d("Create view for ${controller.instance()}")
|
||||
}
|
||||
|
||||
override fun preAttach(controller: Controller, view: View) {
|
||||
Timber.d("Attach view for ${controller.instance()}")
|
||||
}
|
||||
override fun preAttach(controller: Controller, view: View) {
|
||||
Timber.d("Attach view for ${controller.instance()}")
|
||||
}
|
||||
|
||||
override fun preDetach(controller: Controller, view: View) {
|
||||
Timber.d("Detach view for ${controller.instance()}")
|
||||
}
|
||||
override fun preDetach(controller: Controller, view: View) {
|
||||
Timber.d("Detach view for ${controller.instance()}")
|
||||
}
|
||||
|
||||
override fun preDestroyView(controller: Controller, view: View) {
|
||||
Timber.d("Destroy view for ${controller.instance()}")
|
||||
override fun preDestroyView(controller: Controller, view: View) {
|
||||
Timber.d("Destroy view for ${controller.instance()}")
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
override val containerView: View?
|
||||
@ -96,17 +99,19 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
|
||||
*/
|
||||
var expandActionViewFromInteraction = false
|
||||
fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
|
||||
setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
return onExpand?.invoke(item) ?: true
|
||||
}
|
||||
setOnActionExpandListener(
|
||||
object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
return onExpand?.invoke(item) ?: true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
activity?.invalidateOptionsMenu()
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
activity?.invalidateOptionsMenu()
|
||||
|
||||
return onCollapse?.invoke(item) ?: true
|
||||
return onCollapse?.invoke(item) ?: true
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (expandActionViewFromInteraction) {
|
||||
expandActionViewFromInteraction = false
|
||||
|
@ -87,10 +87,12 @@ abstract class DialogController : RestoreViewOnCreateController {
|
||||
*/
|
||||
fun showDialog(router: Router, tag: String?) {
|
||||
dismissed = false
|
||||
router.pushController(RouterTransaction.with(this)
|
||||
router.pushController(
|
||||
RouterTransaction.with(this)
|
||||
.pushChangeHandler(SimpleSwapChangeHandler(false))
|
||||
.popChangeHandler(SimpleSwapChangeHandler(false))
|
||||
.tag(tag))
|
||||
.tag(tag)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,8 +7,9 @@ import nucleus.factory.PresenterFactory
|
||||
import nucleus.presenter.Presenter
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
abstract class NucleusController<P : Presenter<*>>(val bundle: Bundle? = null) : RxController(bundle),
|
||||
PresenterFactory<P> {
|
||||
abstract class NucleusController<P : Presenter<*>>(val bundle: Bundle? = null) :
|
||||
RxController(bundle),
|
||||
PresenterFactory<P> {
|
||||
|
||||
private val delegate = NucleusConductorDelegate(this)
|
||||
|
||||
|
@ -10,7 +10,7 @@ abstract class BaseFlexibleViewHolder(
|
||||
adapter: FlexibleAdapter<*>,
|
||||
stickyHeader: Boolean = false
|
||||
) :
|
||||
FlexibleViewHolder(view, adapter, stickyHeader), LayoutContainer {
|
||||
FlexibleViewHolder(view, adapter, stickyHeader), LayoutContainer {
|
||||
|
||||
override val containerView: View?
|
||||
get() = itemView
|
||||
|
@ -24,7 +24,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
|
||||
* @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) =
|
||||
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
|
||||
@ -34,7 +34,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
|
||||
* @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) =
|
||||
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
|
||||
@ -44,7 +44,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
|
||||
* @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) =
|
||||
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
|
||||
@ -54,7 +54,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
|
||||
* @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) =
|
||||
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.
|
||||
@ -63,11 +63,11 @@ open class BasePresenter<V> : RxPresenter<V>() {
|
||||
|
||||
override fun call(observable: Observable<T>): Observable<Delivery<View, T>> {
|
||||
return observable
|
||||
.materialize()
|
||||
.filter { notification -> !notification.isOnCompleted }
|
||||
.flatMap { notification ->
|
||||
view.take(1).filter { it != null }.map { Delivery(it, notification) }
|
||||
}
|
||||
.materialize()
|
||||
.filter { notification -> !notification.isOnCompleted }
|
||||
.flatMap { notification ->
|
||||
view.take(1).filter { it != null }.map { Delivery(it, notification) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
* @param controller The containing controller.
|
||||
*/
|
||||
class CategoryAdapter(controller: CategoryController) :
|
||||
FlexibleAdapter<CategoryItem>(null, controller, true) {
|
||||
FlexibleAdapter<CategoryItem>(null, controller, true) {
|
||||
|
||||
/**
|
||||
* Listener called when an item of the list is released.
|
||||
|
@ -22,10 +22,11 @@ import kotlinx.android.synthetic.main.categories_controller.*
|
||||
/**
|
||||
* Controller to manage the categories for the users' library.
|
||||
*/
|
||||
class CategoryController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemMoveListener,
|
||||
CategoryAdapter.CategoryItemListener {
|
||||
class CategoryController(bundle: Bundle? = null) :
|
||||
BaseController(bundle),
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemMoveListener,
|
||||
CategoryAdapter.CategoryItemListener {
|
||||
|
||||
/**
|
||||
* Adapter containing category items.
|
||||
@ -147,12 +148,14 @@ class CategoryController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
adapter?.restoreDeletedItems()
|
||||
undoing = true
|
||||
}
|
||||
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
||||
super.onDismissed(transientBottomBar, event)
|
||||
if (!undoing) confirmDelete()
|
||||
addCallback(
|
||||
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
||||
super.onDismissed(transientBottomBar, event)
|
||||
if (!undoing) confirmDelete()
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
(activity as? MainActivity)?.setUndoSnackBar(snack)
|
||||
}
|
||||
|
@ -51,16 +51,22 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
|
||||
createCategory = category.order == CREATE_CATEGORY_ORDER
|
||||
if (createCategory) {
|
||||
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.text_color_hint))
|
||||
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
|
||||
.ic_add_24dp)
|
||||
regularDrawable = ContextCompat.getDrawable(
|
||||
itemView.context,
|
||||
R.drawable
|
||||
.ic_add_24dp
|
||||
)
|
||||
image.gone()
|
||||
edit_button.setImageDrawable(null)
|
||||
edit_text.setText("")
|
||||
edit_text.hint = title.text
|
||||
} else {
|
||||
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary))
|
||||
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
|
||||
.ic_drag_handle_24dp)
|
||||
regularDrawable = ContextCompat.getDrawable(
|
||||
itemView.context,
|
||||
R.drawable
|
||||
.ic_drag_handle_24dp
|
||||
)
|
||||
image.visible()
|
||||
edit_text.setText(title.text)
|
||||
}
|
||||
@ -80,7 +86,8 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
|
||||
if (!createCategory) {
|
||||
reorder.setImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
itemView.context, R.drawable.ic_delete_24dp
|
||||
itemView.context,
|
||||
R.drawable.ic_delete_24dp
|
||||
)
|
||||
)
|
||||
reorder.setOnClickListener {
|
||||
@ -96,8 +103,13 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
|
||||
reorder.setOnTouchListener { _, _ -> true }
|
||||
}
|
||||
edit_text.clearFocus()
|
||||
edit_button.drawable?.mutate()?.setTint(ContextCompat.getColor(itemView.context, R
|
||||
.color.gray_button))
|
||||
edit_button.drawable?.mutate()?.setTint(
|
||||
ContextCompat.getColor(
|
||||
itemView.context,
|
||||
R
|
||||
.color.gray_button
|
||||
)
|
||||
)
|
||||
reorder.setImageDrawable(regularDrawable)
|
||||
}
|
||||
}
|
||||
@ -105,7 +117,8 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
|
||||
private fun submitChanges() {
|
||||
if (edit_text.visibility == View.VISIBLE) {
|
||||
if (adapter.categoryItemListener
|
||||
.onCategoryRename(adapterPosition, edit_text.text.toString())) {
|
||||
.onCategoryRename(adapterPosition, edit_text.text.toString())
|
||||
) {
|
||||
isEditing(false)
|
||||
edit_text.inputType = InputType.TYPE_NULL
|
||||
if (!createCategory)
|
||||
@ -119,8 +132,11 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
|
||||
private fun showKeyboard() {
|
||||
val inputMethodManager: InputMethodManager =
|
||||
itemView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.showSoftInput(edit_text, WindowManager.LayoutParams
|
||||
.SOFT_INPUT_ADJUST_PAN)
|
||||
inputMethodManager.showSoftInput(
|
||||
edit_text,
|
||||
WindowManager.LayoutParams
|
||||
.SOFT_INPUT_ADJUST_PAN
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +70,8 @@ class ManageCategoryDialog(bundle: Bundle? = null) :
|
||||
preferences.downloadNew().set(true)
|
||||
}
|
||||
if (preferences.libraryUpdateInterval().getOrDefault() > 0 &&
|
||||
!updatePref(preferences.libraryUpdateCategories(), view.include_global)) {
|
||||
!updatePref(preferences.libraryUpdateCategories(), view.include_global)
|
||||
) {
|
||||
preferences.libraryUpdateInterval().set(0)
|
||||
LibraryUpdateJob.setupTask(0)
|
||||
}
|
||||
@ -99,9 +100,11 @@ class ManageCategoryDialog(bundle: Bundle? = null) :
|
||||
view.title.hint = category.name
|
||||
view.title.append(category.name)
|
||||
val downloadNew = preferences.downloadNew().getOrDefault()
|
||||
setCheckbox(view.download_new,
|
||||
setCheckbox(
|
||||
view.download_new,
|
||||
preferences.downloadNewCategories(),
|
||||
true)
|
||||
true
|
||||
)
|
||||
if (downloadNew && preferences.downloadNewCategories().getOrDefault().isEmpty())
|
||||
view.download_new.gone()
|
||||
else if (!downloadNew)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user