mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 23:01:51 +01:00
Merge pull request #350 from inorichi/dev
Rewrite DB models, tests and add a chapter loader.
This commit is contained in:
commit
5c98e020f4
@ -84,27 +84,22 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
final SUPPORT_LIBRARY_VERSION = '23.4.0'
|
|
||||||
final DAGGER_VERSION = '2.4'
|
|
||||||
final RETROFIT_VERSION = '2.0.2'
|
|
||||||
final NUCLEUS_VERSION = '3.0.0'
|
|
||||||
final STORIO_VERSION = '1.8.0'
|
|
||||||
final MOCKITO_VERSION = '1.10.19'
|
|
||||||
|
|
||||||
// Modified dependencies
|
// Modified dependencies
|
||||||
compile 'com.github.inorichi:subsampling-scale-image-view:421fb81'
|
compile 'com.github.inorichi:subsampling-scale-image-view:421fb81'
|
||||||
compile 'com.github.inorichi:ReactiveNetwork:69092ed'
|
compile 'com.github.inorichi:ReactiveNetwork:69092ed'
|
||||||
|
|
||||||
// Android support library
|
// Android support library
|
||||||
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
|
final support_library_version = '23.4.0'
|
||||||
compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:support-v4:$support_library_version"
|
||||||
compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:appcompat-v7:$support_library_version"
|
||||||
compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:cardview-v7:$support_library_version"
|
||||||
compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:design:$support_library_version"
|
||||||
compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:recyclerview-v7:$support_library_version"
|
||||||
compile "com.android.support:preference-v7:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:support-annotations:$support_library_version"
|
||||||
compile "com.android.support:preference-v14:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:preference-v7:$support_library_version"
|
||||||
compile "com.android.support:customtabs:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:preference-v14:$support_library_version"
|
||||||
|
compile "com.android.support:customtabs:$support_library_version"
|
||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
compile 'io.reactivex:rxandroid:1.2.0'
|
compile 'io.reactivex:rxandroid:1.2.0'
|
||||||
@ -115,15 +110,17 @@ dependencies {
|
|||||||
compile "com.squareup.okhttp3:okhttp:3.3.1"
|
compile "com.squareup.okhttp3:okhttp:3.3.1"
|
||||||
|
|
||||||
// REST
|
// REST
|
||||||
compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"
|
final retrofit_version = '2.0.2'
|
||||||
compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
|
compile "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||||
compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION"
|
compile "com.squareup.retrofit2:converter-gson:$retrofit_version"
|
||||||
|
compile "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
|
||||||
|
|
||||||
// IO
|
// IO
|
||||||
compile 'com.squareup.okio:okio:1.8.0'
|
compile 'com.squareup.okio:okio:1.8.0'
|
||||||
|
|
||||||
// JSON
|
// JSON
|
||||||
compile 'com.google.code.gson:gson:2.6.2'
|
compile 'com.google.code.gson:gson:2.6.2'
|
||||||
|
compile 'com.github.salomonbrys.kotson:kotson:2.2.1'
|
||||||
|
|
||||||
// YAML
|
// YAML
|
||||||
compile 'org.yaml:snakeyaml:1.17'
|
compile 'org.yaml:snakeyaml:1.17'
|
||||||
@ -141,18 +138,18 @@ dependencies {
|
|||||||
compile 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
compile 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION"
|
final storio_version = '1.8.0'
|
||||||
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
|
compile "com.pushtorefresh.storio:sqlite:$storio_version"
|
||||||
|
compile "com.pushtorefresh.storio:sqlite-annotations:$storio_version"
|
||||||
|
|
||||||
// Model View Presenter
|
// Model View Presenter
|
||||||
compile "info.android15.nucleus:nucleus:$NUCLEUS_VERSION"
|
final nucleus_version = '3.0.0'
|
||||||
compile "info.android15.nucleus:nucleus-support-v4:$NUCLEUS_VERSION"
|
compile "info.android15.nucleus:nucleus:$nucleus_version"
|
||||||
compile "info.android15.nucleus:nucleus-support-v7:$NUCLEUS_VERSION"
|
compile "info.android15.nucleus:nucleus-support-v4:$nucleus_version"
|
||||||
|
compile "info.android15.nucleus:nucleus-support-v7:$nucleus_version"
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
compile "uy.kohesive.injekt:injekt-core:1.16.1"
|
||||||
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
|
||||||
provided 'org.glassfish:javax.annotation:10.0-b28'
|
|
||||||
|
|
||||||
// Image library
|
// Image library
|
||||||
compile 'com.github.bumptech.glide:glide:3.7.0'
|
compile 'com.github.bumptech.glide:glide:3.7.0'
|
||||||
@ -174,13 +171,12 @@ dependencies {
|
|||||||
// Tests
|
// Tests
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.assertj:assertj-core:1.7.1'
|
testCompile 'org.assertj:assertj-core:1.7.1'
|
||||||
testCompile "org.mockito:mockito-core:$MOCKITO_VERSION"
|
testCompile "org.mockito:mockito-core:1.10.19"
|
||||||
testCompile('org.robolectric:robolectric:3.0') {
|
testCompile('org.robolectric:robolectric:3.0') {
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
|
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
|
||||||
}
|
}
|
||||||
|
|
||||||
kaptTest "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,12 +3,10 @@ package eu.kanade.tachiyomi
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.injection.AppComponentFactory
|
|
||||||
import eu.kanade.tachiyomi.injection.ComponentReflectionInjector
|
|
||||||
import eu.kanade.tachiyomi.injection.component.AppComponent
|
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
import org.acra.annotation.ReportsCrashes
|
import org.acra.annotation.ReportsCrashes
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
|
||||||
@ReportsCrashes(
|
@ReportsCrashes(
|
||||||
formUri = "http://tachiyomi.kanade.eu/crash_report",
|
formUri = "http://tachiyomi.kanade.eu/crash_report",
|
||||||
@ -19,22 +17,13 @@ import timber.log.Timber
|
|||||||
)
|
)
|
||||||
open class App : Application() {
|
open class App : Application() {
|
||||||
|
|
||||||
lateinit var component: AppComponent
|
|
||||||
private set
|
|
||||||
|
|
||||||
lateinit var componentReflection: ComponentReflectionInjector<AppComponent>
|
|
||||||
private set
|
|
||||||
|
|
||||||
var appTheme = 0
|
var appTheme = 0
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
Injekt.importModule(AppModule(this))
|
||||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||||
|
|
||||||
component = createAppComponent()
|
|
||||||
|
|
||||||
componentReflection = ComponentReflectionInjector(AppComponent::class.java, component)
|
|
||||||
|
|
||||||
setupTheme()
|
setupTheme()
|
||||||
setupAcra()
|
setupAcra()
|
||||||
}
|
}
|
||||||
@ -43,10 +32,6 @@ open class App : Application() {
|
|||||||
appTheme = PreferencesHelper.getTheme(this)
|
appTheme = PreferencesHelper.getTheme(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun createAppComponent(): AppComponent {
|
|
||||||
return AppComponentFactory.create(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun setupAcra() {
|
protected open fun setupAcra() {
|
||||||
ACRA.init(this)
|
ACRA.init(this)
|
||||||
}
|
}
|
||||||
|
41
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
Normal file
41
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
||||||
|
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
|
import uy.kohesive.injekt.api.addSingletonFactory
|
||||||
|
|
||||||
|
class AppModule(val app: Application) : InjektModule {
|
||||||
|
|
||||||
|
override fun InjektRegistrar.registerInjectables() {
|
||||||
|
|
||||||
|
addSingletonFactory { PreferencesHelper(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { DatabaseHelper(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { ChapterCache(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { CoverCache(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { NetworkHelper(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { SourceManager(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { DownloadManager(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { MangaSyncManager(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { Gson() }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,14 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
|
import com.github.salomonbrys.kotson.fromJson
|
||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import eu.kanade.tachiyomi.data.backup.serializer.IdExclusion
|
import eu.kanade.tachiyomi.data.backup.serializer.IdExclusion
|
||||||
import eu.kanade.tachiyomi.data.backup.serializer.IntegerSerializer
|
import eu.kanade.tachiyomi.data.backup.serializer.IntegerSerializer
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.models.*
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.lang.reflect.Type
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -191,8 +190,7 @@ class BackupManager(private val db: DatabaseHelper) {
|
|||||||
private fun restoreCategories(jsonCategories: JsonArray) {
|
private fun restoreCategories(jsonCategories: JsonArray) {
|
||||||
// Get categories from file and from db
|
// Get categories from file and from db
|
||||||
val dbCategories = db.getCategories().executeAsBlocking()
|
val dbCategories = db.getCategories().executeAsBlocking()
|
||||||
val backupCategories = getArrayOrEmpty<Category>(jsonCategories,
|
val backupCategories = gson.fromJson<List<CategoryImpl>>(jsonCategories)
|
||||||
object : TypeToken<List<Category>>() {}.type)
|
|
||||||
|
|
||||||
// Iterate over them
|
// Iterate over them
|
||||||
for (category in backupCategories) {
|
for (category in backupCategories) {
|
||||||
@ -224,17 +222,13 @@ class BackupManager(private val db: DatabaseHelper) {
|
|||||||
* @param jsonMangas the mangas and its related data (chapters, sync, categories) from the json.
|
* @param jsonMangas the mangas and its related data (chapters, sync, categories) from the json.
|
||||||
*/
|
*/
|
||||||
private fun restoreMangas(jsonMangas: JsonArray) {
|
private fun restoreMangas(jsonMangas: JsonArray) {
|
||||||
val chapterToken = object : TypeToken<List<Chapter>>() {}.type
|
|
||||||
val mangaSyncToken = object : TypeToken<List<MangaSync>>() {}.type
|
|
||||||
val categoriesNamesToken = object : TypeToken<List<String>>() {}.type
|
|
||||||
|
|
||||||
for (backupManga in jsonMangas) {
|
for (backupManga in jsonMangas) {
|
||||||
// Map every entry to objects
|
// Map every entry to objects
|
||||||
val element = backupManga.asJsonObject
|
val element = backupManga.asJsonObject
|
||||||
val manga = gson.fromJson(element.get(MANGA), Manga::class.java)
|
val manga = gson.fromJson(element.get(MANGA), MangaImpl::class.java)
|
||||||
val chapters = getArrayOrEmpty<Chapter>(element.get(CHAPTERS), chapterToken)
|
val chapters = gson.fromJson<List<ChapterImpl>>(element.get(CHAPTERS) ?: JsonArray())
|
||||||
val sync = getArrayOrEmpty<MangaSync>(element.get(MANGA_SYNC), mangaSyncToken)
|
val sync = gson.fromJson<List<MangaSyncImpl>>(element.get(MANGA_SYNC) ?: JsonArray())
|
||||||
val categories = getArrayOrEmpty<String>(element.get(CATEGORIES), categoriesNamesToken)
|
val categories = gson.fromJson<List<String>>(element.get(CATEGORIES) ?: JsonArray())
|
||||||
|
|
||||||
// Restore everything related to this manga
|
// Restore everything related to this manga
|
||||||
restoreManga(manga)
|
restoreManga(manga)
|
||||||
@ -340,7 +334,7 @@ class BackupManager(private val db: DatabaseHelper) {
|
|||||||
private fun restoreSyncForManga(manga: Manga, sync: List<MangaSync>) {
|
private fun restoreSyncForManga(manga: Manga, sync: List<MangaSync>) {
|
||||||
// Fix foreign keys with the current manga id
|
// Fix foreign keys with the current manga id
|
||||||
for (mangaSync in sync) {
|
for (mangaSync in sync) {
|
||||||
mangaSync.manga_id = manga.id
|
mangaSync.manga_id = manga.id!!
|
||||||
}
|
}
|
||||||
|
|
||||||
val dbSyncs = db.getMangasSync(manga).executeAsBlocking()
|
val dbSyncs = db.getMangasSync(manga).executeAsBlocking()
|
||||||
@ -367,15 +361,4 @@ class BackupManager(private val db: DatabaseHelper) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of items from a json element, or an empty list if the element is null.
|
|
||||||
*
|
|
||||||
* @param element the json to be mapped to a list of items.
|
|
||||||
* @param type the gson mapping to restore the list.
|
|
||||||
* @return a list of items.
|
|
||||||
*/
|
|
||||||
private fun <T> getArrayOrEmpty(element: JsonElement?, type: Type): List<T> {
|
|
||||||
return gson.fromJson<List<T>>(element, type) ?: ArrayList<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME
|
||||||
@ -44,7 +45,7 @@ class CategoryPutResolver : DefaultPutResolver<Category>() {
|
|||||||
|
|
||||||
class CategoryGetResolver : DefaultGetResolver<Category>() {
|
class CategoryGetResolver : DefaultGetResolver<Category>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor) = Category().apply {
|
override fun mapFromCursor(cursor: Cursor): Category = CategoryImpl().apply {
|
||||||
id = cursor.getInt(cursor.getColumnIndex(COL_ID))
|
id = cursor.getInt(cursor.getColumnIndex(COL_ID))
|
||||||
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
||||||
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
||||||
|
@ -10,6 +10,7 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_CHAPTER_NUMBER
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_CHAPTER_NUMBER
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_FETCH
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_FETCH
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_UPLOAD
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_UPLOAD
|
||||||
@ -56,7 +57,7 @@ class ChapterPutResolver : DefaultPutResolver<Chapter>() {
|
|||||||
|
|
||||||
class ChapterGetResolver : DefaultGetResolver<Chapter>() {
|
class ChapterGetResolver : DefaultGetResolver<Chapter>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor) = Chapter().apply {
|
override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
||||||
url = cursor.getString(cursor.getColumnIndex(COL_URL))
|
url = cursor.getString(cursor.getColumnIndex(COL_URL))
|
||||||
|
@ -44,7 +44,7 @@ class HistoryPutResolver : DefaultPutResolver<History>() {
|
|||||||
|
|
||||||
class HistoryGetResolver : DefaultGetResolver<History>() {
|
class HistoryGetResolver : DefaultGetResolver<History>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor) = History().apply {
|
override fun mapFromCursor(cursor: Cursor): History = History().apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
chapter_id = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_ID))
|
chapter_id = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_ID))
|
||||||
last_read = cursor.getLong(cursor.getColumnIndex(COL_LAST_READ))
|
last_read = cursor.getLong(cursor.getColumnIndex(COL_LAST_READ))
|
||||||
|
@ -42,7 +42,7 @@ class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
|
|||||||
|
|
||||||
class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
|
class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor) = MangaCategory().apply {
|
override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
||||||
category_id = cursor.getInt(cursor.getColumnIndex(COL_CATEGORY_ID))
|
category_id = cursor.getInt(cursor.getColumnIndex(COL_CATEGORY_ID))
|
||||||
|
@ -10,6 +10,7 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaSyncImpl
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable.COL_ID
|
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable.COL_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable.COL_LAST_CHAPTER_READ
|
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable.COL_LAST_CHAPTER_READ
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable.COL_MANGA_ID
|
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable.COL_MANGA_ID
|
||||||
@ -54,7 +55,7 @@ class MangaSyncPutResolver : DefaultPutResolver<MangaSync>() {
|
|||||||
|
|
||||||
class MangaSyncGetResolver : DefaultGetResolver<MangaSync>() {
|
class MangaSyncGetResolver : DefaultGetResolver<MangaSync>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor) = MangaSync().apply {
|
override fun mapFromCursor(cursor: Cursor): MangaSync = MangaSyncImpl().apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
||||||
sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID))
|
sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID))
|
||||||
|
@ -10,6 +10,7 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
|
||||||
@ -66,7 +67,7 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
|||||||
|
|
||||||
open class MangaGetResolver : DefaultGetResolver<Manga>() {
|
open class MangaGetResolver : DefaultGetResolver<Manga>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor) = Manga().apply {
|
override fun mapFromCursor(cursor: Cursor): Manga = MangaImpl().apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
source = cursor.getInt(cursor.getColumnIndex(COL_SOURCE))
|
source = cursor.getInt(cursor.getColumnIndex(COL_SOURCE))
|
||||||
url = cursor.getString(cursor.getColumnIndex(COL_URL))
|
url = cursor.getString(cursor.getColumnIndex(COL_URL))
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models;
|
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable;
|
|
||||||
|
|
||||||
@StorIOSQLiteType(table = CategoryTable.TABLE)
|
|
||||||
public class Category implements Serializable {
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = CategoryTable.COL_ID, key = true)
|
|
||||||
public Integer id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = CategoryTable.COL_NAME)
|
|
||||||
public String name;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = CategoryTable.COL_ORDER)
|
|
||||||
public int order;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = CategoryTable.COL_FLAGS)
|
|
||||||
public int flags;
|
|
||||||
|
|
||||||
public Category() {}
|
|
||||||
|
|
||||||
public static Category create(String name) {
|
|
||||||
Category c = new Category();
|
|
||||||
c.name = name;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Category createDefault() {
|
|
||||||
Category c = create("Default");
|
|
||||||
c.id = 0;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNameLower() {
|
|
||||||
return name.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
Category category = (Category) o;
|
|
||||||
|
|
||||||
return name.equals(category.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return name.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,27 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
interface Category : Serializable {
|
||||||
|
|
||||||
|
var id: Int?
|
||||||
|
|
||||||
|
var name: String
|
||||||
|
|
||||||
|
var order: Int
|
||||||
|
|
||||||
|
var flags: Int
|
||||||
|
|
||||||
|
val nameLower: String
|
||||||
|
get() = name.toLowerCase()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun create(name: String): Category = CategoryImpl().apply {
|
||||||
|
this.name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createDefault(): Category = create("Default").apply { id = 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
class CategoryImpl : Category {
|
||||||
|
|
||||||
|
override var id: Int? = null
|
||||||
|
|
||||||
|
override lateinit var name: String
|
||||||
|
|
||||||
|
override var order: Int = 0
|
||||||
|
|
||||||
|
override var flags: Int = 0
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || javaClass != other.javaClass) return false
|
||||||
|
|
||||||
|
val category = other as Category
|
||||||
|
|
||||||
|
return name == category.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return name.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,94 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models;
|
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download;
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
|
||||||
import eu.kanade.tachiyomi.util.UrlUtil;
|
|
||||||
|
|
||||||
@StorIOSQLiteType(table = ChapterTable.TABLE)
|
|
||||||
public class Chapter implements Serializable {
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_ID, key = true)
|
|
||||||
public Long id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_MANGA_ID)
|
|
||||||
public Long manga_id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_URL)
|
|
||||||
public String url;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_NAME)
|
|
||||||
public String name;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_READ)
|
|
||||||
public boolean read;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_LAST_PAGE_READ)
|
|
||||||
public int last_page_read;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_DATE_FETCH)
|
|
||||||
public long date_fetch;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_DATE_UPLOAD)
|
|
||||||
public long date_upload;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_CHAPTER_NUMBER)
|
|
||||||
public float chapter_number;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = ChapterTable.COL_SOURCE_ORDER)
|
|
||||||
public int source_order;
|
|
||||||
|
|
||||||
public int status;
|
|
||||||
|
|
||||||
private transient List<Page> pages;
|
|
||||||
|
|
||||||
public Chapter() {}
|
|
||||||
|
|
||||||
public void setUrl(String url) {
|
|
||||||
this.url = UrlUtil.getPath(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
Chapter chapter = (Chapter) o;
|
|
||||||
|
|
||||||
return url.equals(chapter.url);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return url.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Chapter create() {
|
|
||||||
Chapter chapter = new Chapter();
|
|
||||||
chapter.chapter_number = -1;
|
|
||||||
return chapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Page> getPages() {
|
|
||||||
return pages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPages(List<Page> pages) {
|
|
||||||
this.pages = pages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDownloaded() {
|
|
||||||
return status == Download.DOWNLOADED;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRecognizedNumber() {
|
|
||||||
return chapter_number >= 0f;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,36 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
interface Chapter : Serializable {
|
||||||
|
|
||||||
|
var id: Long?
|
||||||
|
|
||||||
|
var manga_id: Long?
|
||||||
|
|
||||||
|
var url: String
|
||||||
|
|
||||||
|
var name: String
|
||||||
|
|
||||||
|
var read: Boolean
|
||||||
|
|
||||||
|
var last_page_read: Int
|
||||||
|
|
||||||
|
var date_fetch: Long
|
||||||
|
|
||||||
|
var date_upload: Long
|
||||||
|
|
||||||
|
var chapter_number: Float
|
||||||
|
|
||||||
|
var source_order: Int
|
||||||
|
|
||||||
|
val isRecognizedNumber: Boolean
|
||||||
|
get() = chapter_number >= 0f
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun create(): Chapter = ChapterImpl().apply {
|
||||||
|
chapter_number = -1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
class ChapterImpl : Chapter {
|
||||||
|
|
||||||
|
override var id: Long? = null
|
||||||
|
|
||||||
|
override var manga_id: Long? = null
|
||||||
|
|
||||||
|
override lateinit var url: String
|
||||||
|
|
||||||
|
override lateinit var name: String
|
||||||
|
|
||||||
|
override var read: Boolean = false
|
||||||
|
|
||||||
|
override var last_page_read: Int = 0
|
||||||
|
|
||||||
|
override var date_fetch: Long = 0
|
||||||
|
|
||||||
|
override var date_upload: Long = 0
|
||||||
|
|
||||||
|
override var chapter_number: Float = 0f
|
||||||
|
|
||||||
|
override var source_order: Int = 0
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || javaClass != other.javaClass) return false
|
||||||
|
|
||||||
|
val chapter = other as Chapter
|
||||||
|
|
||||||
|
return url == chapter.url
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return url.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models;
|
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object containing the history statistics of a chapter
|
|
||||||
*/
|
|
||||||
@StorIOSQLiteType(table = HistoryTable.TABLE)
|
|
||||||
public class History implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Id of history object.
|
|
||||||
*/
|
|
||||||
@StorIOSQLiteColumn(name = HistoryTable.COL_ID, key = true)
|
|
||||||
public Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chapter id of history object.
|
|
||||||
*/
|
|
||||||
@StorIOSQLiteColumn(name = HistoryTable.COL_CHAPTER_ID)
|
|
||||||
public long chapter_id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Last time chapter was read in time long format
|
|
||||||
*/
|
|
||||||
@StorIOSQLiteColumn(name = HistoryTable.COL_LAST_READ)
|
|
||||||
public long last_read;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total time chapter was read - todo not yet implemented
|
|
||||||
*/
|
|
||||||
@StorIOSQLiteColumn(name = HistoryTable.COL_TIME_READ)
|
|
||||||
public long time_read;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty history constructor
|
|
||||||
*/
|
|
||||||
public History() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* History constructor
|
|
||||||
*
|
|
||||||
* @param chapter chapter object
|
|
||||||
* @return history object
|
|
||||||
*/
|
|
||||||
public static History create(Chapter chapter) {
|
|
||||||
History history = new History();
|
|
||||||
history.chapter_id = chapter.id;
|
|
||||||
return history;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object containing the history statistics of a chapter
|
||||||
|
*/
|
||||||
|
class History : Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of history object.
|
||||||
|
*/
|
||||||
|
var id: Long? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chapter id of history object.
|
||||||
|
*/
|
||||||
|
var chapter_id: Long = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last time chapter was read in time long format
|
||||||
|
*/
|
||||||
|
var last_read: Long = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total time chapter was read - todo not yet implemented
|
||||||
|
*/
|
||||||
|
var time_read: Long = 0
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* History constructor
|
||||||
|
*
|
||||||
|
* @param chapter chapter object
|
||||||
|
* @return history object
|
||||||
|
*/
|
||||||
|
fun create(chapter: Chapter): History {
|
||||||
|
val history = History()
|
||||||
|
history.chapter_id = chapter.id!!
|
||||||
|
return history
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,213 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable;
|
|
||||||
import eu.kanade.tachiyomi.util.UrlUtil;
|
|
||||||
|
|
||||||
@StorIOSQLiteType(table = MangaTable.TABLE)
|
|
||||||
public class Manga implements Serializable {
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_ID, key = true)
|
|
||||||
public Long id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_SOURCE)
|
|
||||||
public int source;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_URL)
|
|
||||||
public String url;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_ARTIST)
|
|
||||||
public String artist;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_AUTHOR)
|
|
||||||
public String author;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_DESCRIPTION)
|
|
||||||
public String description;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_GENRE)
|
|
||||||
public String genre;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_TITLE)
|
|
||||||
public String title;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_STATUS)
|
|
||||||
public int status;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_THUMBNAIL_URL)
|
|
||||||
public String thumbnail_url;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_FAVORITE)
|
|
||||||
public boolean favorite;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_LAST_UPDATE)
|
|
||||||
public long last_update;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_INITIALIZED)
|
|
||||||
public boolean initialized;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_VIEWER)
|
|
||||||
public int viewer;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaTable.COL_CHAPTER_FLAGS)
|
|
||||||
public int chapter_flags;
|
|
||||||
|
|
||||||
public transient int unread;
|
|
||||||
|
|
||||||
public transient int category;
|
|
||||||
|
|
||||||
public static final int UNKNOWN = 0;
|
|
||||||
public static final int ONGOING = 1;
|
|
||||||
public static final int COMPLETED = 2;
|
|
||||||
public static final int LICENSED = 3;
|
|
||||||
|
|
||||||
public static final int SORT_DESC = 0x00000000;
|
|
||||||
public static final int SORT_ASC = 0x00000001;
|
|
||||||
public static final int SORT_MASK = 0x00000001;
|
|
||||||
|
|
||||||
// Generic filter that does not filter anything
|
|
||||||
public static final int SHOW_ALL = 0x00000000;
|
|
||||||
|
|
||||||
public static final int SHOW_UNREAD = 0x00000002;
|
|
||||||
public static final int SHOW_READ = 0x00000004;
|
|
||||||
public static final int READ_MASK = 0x00000006;
|
|
||||||
|
|
||||||
public static final int SHOW_DOWNLOADED = 0x00000008;
|
|
||||||
public static final int SHOW_NOT_DOWNLOADED = 0x00000010;
|
|
||||||
public static final int DOWNLOADED_MASK = 0x00000018;
|
|
||||||
|
|
||||||
public static final int SORTING_SOURCE = 0x00000000;
|
|
||||||
public static final int SORTING_NUMBER = 0x00000100;
|
|
||||||
public static final int SORTING_MASK = 0x00000100;
|
|
||||||
|
|
||||||
public static final int DISPLAY_NAME = 0x00000000;
|
|
||||||
public static final int DISPLAY_NUMBER = 0x00100000;
|
|
||||||
public static final int DISPLAY_MASK = 0x00100000;
|
|
||||||
|
|
||||||
public Manga() {}
|
|
||||||
|
|
||||||
public static Manga create(String pathUrl) {
|
|
||||||
Manga m = new Manga();
|
|
||||||
m.url = pathUrl;
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Manga create(String pathUrl, int source) {
|
|
||||||
Manga m = new Manga();
|
|
||||||
m.url = pathUrl;
|
|
||||||
m.source = source;
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUrl(String url) {
|
|
||||||
this.url = UrlUtil.getPath(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void copyFrom(Manga other) {
|
|
||||||
if (other.title != null)
|
|
||||||
title = other.title;
|
|
||||||
|
|
||||||
if (other.author != null)
|
|
||||||
author = other.author;
|
|
||||||
|
|
||||||
if (other.artist != null)
|
|
||||||
artist = other.artist;
|
|
||||||
|
|
||||||
if (other.url != null)
|
|
||||||
url = other.url;
|
|
||||||
|
|
||||||
if (other.description != null)
|
|
||||||
description = other.description;
|
|
||||||
|
|
||||||
if (other.genre != null)
|
|
||||||
genre = other.genre;
|
|
||||||
|
|
||||||
if (other.thumbnail_url != null)
|
|
||||||
thumbnail_url = other.thumbnail_url;
|
|
||||||
|
|
||||||
status = other.status;
|
|
||||||
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus(Context context) {
|
|
||||||
switch (status) {
|
|
||||||
case ONGOING:
|
|
||||||
return context.getString(R.string.ongoing);
|
|
||||||
case COMPLETED:
|
|
||||||
return context.getString(R.string.completed);
|
|
||||||
case LICENSED:
|
|
||||||
return context.getString(R.string.licensed);
|
|
||||||
default:
|
|
||||||
return context.getString(R.string.unknown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChapterOrder(int order) {
|
|
||||||
setFlags(order, SORT_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDisplayMode(int mode) {
|
|
||||||
setFlags(mode, DISPLAY_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReadFilter(int filter) {
|
|
||||||
setFlags(filter, READ_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDownloadedFilter(int filter) {
|
|
||||||
setFlags(filter, DOWNLOADED_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSorting(int sort) {
|
|
||||||
setFlags(sort, SORTING_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setFlags(int flag, int mask) {
|
|
||||||
chapter_flags = (chapter_flags & ~mask) | (flag & mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean sortDescending() {
|
|
||||||
return (chapter_flags & SORT_MASK) == SORT_DESC;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to display the chapter's title one way or another
|
|
||||||
public int getDisplayMode() {
|
|
||||||
return chapter_flags & DISPLAY_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReadFilter() {
|
|
||||||
return chapter_flags & READ_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDownloadedFilter() {
|
|
||||||
return chapter_flags & DOWNLOADED_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSorting() {
|
|
||||||
return chapter_flags & SORTING_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
Manga manga = (Manga) o;
|
|
||||||
|
|
||||||
return url.equals(manga.url);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return url.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,131 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
interface Manga : Serializable {
|
||||||
|
|
||||||
|
var id: Long?
|
||||||
|
|
||||||
|
var source: Int
|
||||||
|
|
||||||
|
var url: String
|
||||||
|
|
||||||
|
var title: String
|
||||||
|
|
||||||
|
var artist: String?
|
||||||
|
|
||||||
|
var author: String?
|
||||||
|
|
||||||
|
var description: String?
|
||||||
|
|
||||||
|
var genre: String?
|
||||||
|
|
||||||
|
var status: Int
|
||||||
|
|
||||||
|
var thumbnail_url: String?
|
||||||
|
|
||||||
|
var favorite: Boolean
|
||||||
|
|
||||||
|
var last_update: Long
|
||||||
|
|
||||||
|
var initialized: Boolean
|
||||||
|
|
||||||
|
var viewer: Int
|
||||||
|
|
||||||
|
var chapter_flags: Int
|
||||||
|
|
||||||
|
var unread: Int
|
||||||
|
|
||||||
|
var category: Int
|
||||||
|
|
||||||
|
fun copyFrom(other: Manga) {
|
||||||
|
if (other.author != null)
|
||||||
|
author = other.author
|
||||||
|
|
||||||
|
if (other.artist != null)
|
||||||
|
artist = other.artist
|
||||||
|
|
||||||
|
if (other.description != null)
|
||||||
|
description = other.description
|
||||||
|
|
||||||
|
if (other.genre != null)
|
||||||
|
genre = other.genre
|
||||||
|
|
||||||
|
if (other.thumbnail_url != null)
|
||||||
|
thumbnail_url = other.thumbnail_url
|
||||||
|
|
||||||
|
status = other.status
|
||||||
|
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setChapterOrder(order: Int) {
|
||||||
|
setFlags(order, SORT_MASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setFlags(flag: Int, mask: Int) {
|
||||||
|
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sortDescending(): Boolean {
|
||||||
|
return chapter_flags and SORT_MASK == SORT_DESC
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to display the chapter's title one way or another
|
||||||
|
var displayMode: Int
|
||||||
|
get() = chapter_flags and DISPLAY_MASK
|
||||||
|
set(mode) = setFlags(mode, DISPLAY_MASK)
|
||||||
|
|
||||||
|
var readFilter: Int
|
||||||
|
get() = chapter_flags and READ_MASK
|
||||||
|
set(filter) = setFlags(filter, READ_MASK)
|
||||||
|
|
||||||
|
var downloadedFilter: Int
|
||||||
|
get() = chapter_flags and DOWNLOADED_MASK
|
||||||
|
set(filter) = setFlags(filter, DOWNLOADED_MASK)
|
||||||
|
|
||||||
|
var sorting: Int
|
||||||
|
get() = chapter_flags and SORTING_MASK
|
||||||
|
set(sort) = setFlags(sort, SORTING_MASK)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val UNKNOWN = 0
|
||||||
|
const val ONGOING = 1
|
||||||
|
const val COMPLETED = 2
|
||||||
|
const val LICENSED = 3
|
||||||
|
|
||||||
|
const val SORT_DESC = 0x00000000
|
||||||
|
const val SORT_ASC = 0x00000001
|
||||||
|
const val SORT_MASK = 0x00000001
|
||||||
|
|
||||||
|
// Generic filter that does not filter anything
|
||||||
|
const val SHOW_ALL = 0x00000000
|
||||||
|
|
||||||
|
const val SHOW_UNREAD = 0x00000002
|
||||||
|
const val SHOW_READ = 0x00000004
|
||||||
|
const val READ_MASK = 0x00000006
|
||||||
|
|
||||||
|
const val SHOW_DOWNLOADED = 0x00000008
|
||||||
|
const val SHOW_NOT_DOWNLOADED = 0x00000010
|
||||||
|
const val DOWNLOADED_MASK = 0x00000018
|
||||||
|
|
||||||
|
const val SORTING_SOURCE = 0x00000000
|
||||||
|
const val SORTING_NUMBER = 0x00000100
|
||||||
|
const val SORTING_MASK = 0x00000100
|
||||||
|
|
||||||
|
const val DISPLAY_NAME = 0x00000000
|
||||||
|
const val DISPLAY_NUMBER = 0x00100000
|
||||||
|
const val DISPLAY_MASK = 0x00100000
|
||||||
|
|
||||||
|
fun create(source: Int): Manga = MangaImpl().apply {
|
||||||
|
this.source = source
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(pathUrl: String, source: Int = 0): Manga = MangaImpl().apply {
|
||||||
|
url = pathUrl
|
||||||
|
this.source = source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models;
|
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable;
|
|
||||||
|
|
||||||
@StorIOSQLiteType(table = MangaCategoryTable.TABLE)
|
|
||||||
public class MangaCategory {
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaCategoryTable.COL_ID, key = true)
|
|
||||||
public Long id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaCategoryTable.COL_MANGA_ID)
|
|
||||||
public long manga_id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaCategoryTable.COL_CATEGORY_ID)
|
|
||||||
public int category_id;
|
|
||||||
|
|
||||||
public MangaCategory() {}
|
|
||||||
|
|
||||||
public static MangaCategory create(Manga manga, Category category) {
|
|
||||||
MangaCategory mc = new MangaCategory();
|
|
||||||
mc.manga_id = manga.id;
|
|
||||||
mc.category_id = category.id;
|
|
||||||
return mc;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
class MangaCategory {
|
||||||
|
|
||||||
|
var id: Long? = null
|
||||||
|
|
||||||
|
var manga_id: Long = 0
|
||||||
|
|
||||||
|
var category_id: Int = 0
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun create(manga: Manga, category: Category): MangaCategory {
|
||||||
|
val mc = MangaCategory()
|
||||||
|
mc.manga_id = manga.id!!
|
||||||
|
mc.category_id = category.id!!
|
||||||
|
return mc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models;
|
|
||||||
|
|
||||||
public class MangaChapter {
|
|
||||||
|
|
||||||
public Manga manga;
|
|
||||||
public Chapter chapter;
|
|
||||||
|
|
||||||
public MangaChapter(Manga manga, Chapter chapter) {
|
|
||||||
this.manga = manga;
|
|
||||||
this.chapter = chapter;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
class MangaChapter(val manga: Manga, val chapter: Chapter)
|
@ -1,27 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object containing manga, chapter and history
|
|
||||||
*/
|
|
||||||
public class MangaChapterHistory {
|
|
||||||
/**
|
|
||||||
* Object containing manga and chapter
|
|
||||||
*/
|
|
||||||
public MangaChapter mangaChapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object containing history
|
|
||||||
*/
|
|
||||||
public History history;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MangaChapterHistory constructor
|
|
||||||
*
|
|
||||||
* @param mangaChapter object containing manga and chapter
|
|
||||||
* @param history object containing history
|
|
||||||
*/
|
|
||||||
public MangaChapterHistory(MangaChapter mangaChapter, History history) {
|
|
||||||
this.mangaChapter = mangaChapter;
|
|
||||||
this.history = history;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object containing manga, chapter and history
|
||||||
|
*
|
||||||
|
* @param manga object containing manga
|
||||||
|
* @param chapter object containing chater
|
||||||
|
* @param history object containing history
|
||||||
|
*/
|
||||||
|
class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History)
|
@ -0,0 +1,53 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
class MangaImpl : Manga {
|
||||||
|
|
||||||
|
override var id: Long? = null
|
||||||
|
|
||||||
|
override var source: Int = 0
|
||||||
|
|
||||||
|
override lateinit var url: String
|
||||||
|
|
||||||
|
override lateinit var title: String
|
||||||
|
|
||||||
|
override var artist: String? = null
|
||||||
|
|
||||||
|
override var author: String? = null
|
||||||
|
|
||||||
|
override var description: String? = null
|
||||||
|
|
||||||
|
override var genre: String? = null
|
||||||
|
|
||||||
|
override var status: Int = 0
|
||||||
|
|
||||||
|
override var thumbnail_url: String? = null
|
||||||
|
|
||||||
|
override var favorite: Boolean = false
|
||||||
|
|
||||||
|
override var last_update: Long = 0
|
||||||
|
|
||||||
|
override var initialized: Boolean = false
|
||||||
|
|
||||||
|
override var viewer: Int = 0
|
||||||
|
|
||||||
|
override var chapter_flags: Int = 0
|
||||||
|
|
||||||
|
@Transient override var unread: Int = 0
|
||||||
|
|
||||||
|
@Transient override var category: Int = 0
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || javaClass != other.javaClass) return false
|
||||||
|
|
||||||
|
val manga = other as Manga
|
||||||
|
|
||||||
|
return url == manga.url
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return url.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models;
|
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
|
||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable;
|
|
||||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncService;
|
|
||||||
|
|
||||||
@StorIOSQLiteType(table = MangaSyncTable.TABLE)
|
|
||||||
public class MangaSync implements Serializable {
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaSyncTable.COL_ID, key = true)
|
|
||||||
public Long id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaSyncTable.COL_MANGA_ID)
|
|
||||||
public long manga_id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaSyncTable.COL_SYNC_ID)
|
|
||||||
public int sync_id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaSyncTable.COL_REMOTE_ID)
|
|
||||||
public int remote_id;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaSyncTable.COL_TITLE)
|
|
||||||
public String title;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaSyncTable.COL_LAST_CHAPTER_READ)
|
|
||||||
public int last_chapter_read;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaSyncTable.COL_TOTAL_CHAPTERS)
|
|
||||||
public int total_chapters;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaSyncTable.COL_SCORE)
|
|
||||||
public float score;
|
|
||||||
|
|
||||||
@StorIOSQLiteColumn(name = MangaSyncTable.COL_STATUS)
|
|
||||||
public int status;
|
|
||||||
|
|
||||||
public boolean update;
|
|
||||||
|
|
||||||
public static MangaSync create() {
|
|
||||||
return new MangaSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MangaSync create(MangaSyncService service) {
|
|
||||||
MangaSync mangasync = new MangaSync();
|
|
||||||
mangasync.sync_id = service.getId();
|
|
||||||
return mangasync;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void copyPersonalFrom(MangaSync other) {
|
|
||||||
last_chapter_read = other.last_chapter_read;
|
|
||||||
score = other.score;
|
|
||||||
status = other.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
MangaSync mangaSync = (MangaSync) o;
|
|
||||||
|
|
||||||
if (manga_id != mangaSync.manga_id) return false;
|
|
||||||
if (sync_id != mangaSync.sync_id) return false;
|
|
||||||
return remote_id == mangaSync.remote_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = (int) (manga_id ^ (manga_id >>> 32));
|
|
||||||
result = 31 * result + sync_id;
|
|
||||||
result = 31 * result + remote_id;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,40 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
interface MangaSync : Serializable {
|
||||||
|
|
||||||
|
var id: Long?
|
||||||
|
|
||||||
|
var manga_id: Long
|
||||||
|
|
||||||
|
var sync_id: Int
|
||||||
|
|
||||||
|
var remote_id: Int
|
||||||
|
|
||||||
|
var title: String
|
||||||
|
|
||||||
|
var last_chapter_read: Int
|
||||||
|
|
||||||
|
var total_chapters: Int
|
||||||
|
|
||||||
|
var score: Float
|
||||||
|
|
||||||
|
var status: Int
|
||||||
|
|
||||||
|
var update: Boolean
|
||||||
|
|
||||||
|
fun copyPersonalFrom(other: MangaSync) {
|
||||||
|
last_chapter_read = other.last_chapter_read
|
||||||
|
score = other.score
|
||||||
|
status = other.status
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun create(serviceId: Int): MangaSync = MangaSyncImpl().apply {
|
||||||
|
sync_id = serviceId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
class MangaSyncImpl : MangaSync {
|
||||||
|
|
||||||
|
override var id: Long? = null
|
||||||
|
|
||||||
|
override var manga_id: Long = 0
|
||||||
|
|
||||||
|
override var sync_id: Int = 0
|
||||||
|
|
||||||
|
override var remote_id: Int = 0
|
||||||
|
|
||||||
|
override lateinit var title: String
|
||||||
|
|
||||||
|
override var last_chapter_read: Int = 0
|
||||||
|
|
||||||
|
override var total_chapters: Int = 0
|
||||||
|
|
||||||
|
override var score: Float = 0f
|
||||||
|
|
||||||
|
override var status: Int = 0
|
||||||
|
|
||||||
|
override var update: Boolean = false
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || javaClass != other.javaClass) return false
|
||||||
|
|
||||||
|
val mangaSync = other as MangaSync
|
||||||
|
|
||||||
|
if (manga_id != mangaSync.manga_id) return false
|
||||||
|
if (sync_id != mangaSync.sync_id) return false
|
||||||
|
return remote_id == mangaSync.remote_id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = (manga_id xor manga_id.ushr(32)).toInt()
|
||||||
|
result = 31 * result + sync_id
|
||||||
|
result = 31 * result + remote_id
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.queries
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
@ -34,80 +33,6 @@ interface ChapterQueries : DbProvider {
|
|||||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getNextChapter(chapter: Chapter): PreparedGetObject<Chapter> {
|
|
||||||
// Add a delta to the chapter number, because binary decimal representation
|
|
||||||
// can retrieve the same chapter again
|
|
||||||
val chapterNumber = chapter.chapter_number + 0.00001
|
|
||||||
|
|
||||||
return db.get()
|
|
||||||
.`object`(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(ChapterTable.TABLE)
|
|
||||||
.where("${ChapterTable.COL_MANGA_ID} = ? AND " +
|
|
||||||
"${ChapterTable.COL_CHAPTER_NUMBER} > ? AND " +
|
|
||||||
"${ChapterTable.COL_CHAPTER_NUMBER} <= ?")
|
|
||||||
.whereArgs(chapter.manga_id, chapterNumber, chapterNumber + 1)
|
|
||||||
.orderBy(ChapterTable.COL_CHAPTER_NUMBER)
|
|
||||||
.limit(1)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getNextChapterBySource(chapter: Chapter) = db.get()
|
|
||||||
.`object`(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(ChapterTable.TABLE)
|
|
||||||
.where("""${ChapterTable.COL_MANGA_ID} = ? AND
|
|
||||||
${ChapterTable.COL_SOURCE_ORDER} < ?""")
|
|
||||||
.whereArgs(chapter.manga_id, chapter.source_order)
|
|
||||||
.orderBy("${ChapterTable.COL_SOURCE_ORDER} DESC")
|
|
||||||
.limit(1)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getPreviousChapter(chapter: Chapter): PreparedGetObject<Chapter> {
|
|
||||||
// Add a delta to the chapter number, because binary decimal representation
|
|
||||||
// can retrieve the same chapter again
|
|
||||||
val chapterNumber = chapter.chapter_number - 0.00001
|
|
||||||
|
|
||||||
return db.get()
|
|
||||||
.`object`(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder().table(ChapterTable.TABLE)
|
|
||||||
.where("${ChapterTable.COL_MANGA_ID} = ? AND " +
|
|
||||||
"${ChapterTable.COL_CHAPTER_NUMBER} < ? AND " +
|
|
||||||
"${ChapterTable.COL_CHAPTER_NUMBER} >= ?")
|
|
||||||
.whereArgs(chapter.manga_id, chapterNumber, chapterNumber - 1)
|
|
||||||
.orderBy("${ChapterTable.COL_CHAPTER_NUMBER} DESC")
|
|
||||||
.limit(1)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPreviousChapterBySource(chapter: Chapter) = db.get()
|
|
||||||
.`object`(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(ChapterTable.TABLE)
|
|
||||||
.where("""${ChapterTable.COL_MANGA_ID} = ? AND
|
|
||||||
${ChapterTable.COL_SOURCE_ORDER} > ?""")
|
|
||||||
.whereArgs(chapter.manga_id, chapter.source_order)
|
|
||||||
.orderBy(ChapterTable.COL_SOURCE_ORDER)
|
|
||||||
.limit(1)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getNextUnreadChapter(manga: Manga) = db.get()
|
|
||||||
.`object`(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(ChapterTable.TABLE)
|
|
||||||
.where("${ChapterTable.COL_MANGA_ID} = ? AND " +
|
|
||||||
"${ChapterTable.COL_READ} = ? AND " +
|
|
||||||
"${ChapterTable.COL_CHAPTER_NUMBER} >= ?")
|
|
||||||
.whereArgs(manga.id, 0, 0)
|
|
||||||
.orderBy(ChapterTable.COL_CHAPTER_NUMBER)
|
|
||||||
.limit(1)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
||||||
|
|
||||||
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
|
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
|
||||||
|
@ -5,7 +5,6 @@ import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
|||||||
import eu.kanade.tachiyomi.data.database.mappers.ChapterGetResolver
|
import eu.kanade.tachiyomi.data.database.mappers.ChapterGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.HistoryGetResolver
|
import eu.kanade.tachiyomi.data.database.mappers.HistoryGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver
|
import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
||||||
|
|
||||||
class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>() {
|
class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>() {
|
||||||
@ -46,10 +45,7 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>()
|
|||||||
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"))
|
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"))
|
||||||
chapter.id = history.chapter_id
|
chapter.id = history.chapter_id
|
||||||
|
|
||||||
// Create mangaChapter object
|
|
||||||
val mangaChapter = MangaChapter(manga, chapter)
|
|
||||||
|
|
||||||
// Return result
|
// Return result
|
||||||
return MangaChapterHistory(mangaChapter, history)
|
return MangaChapterHistory(manga, chapter, history)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,7 @@ import eu.kanade.tachiyomi.data.source.Source
|
|||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
import eu.kanade.tachiyomi.util.DiskUtils
|
import eu.kanade.tachiyomi.util.*
|
||||||
import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator
|
|
||||||
import eu.kanade.tachiyomi.util.UrlUtil
|
|
||||||
import eu.kanade.tachiyomi.util.saveImageTo
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
@ -27,12 +24,17 @@ import rx.schedulers.Schedulers
|
|||||||
import rx.subjects.BehaviorSubject
|
import rx.subjects.BehaviorSubject
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class DownloadManager(private val context: Context, private val sourceManager: SourceManager, private val preferences: PreferencesHelper) {
|
class DownloadManager(
|
||||||
|
private val context: Context,
|
||||||
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
|
private val preferences: PreferencesHelper = Injekt.get()
|
||||||
|
) {
|
||||||
|
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
|
|
||||||
@ -270,10 +272,8 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
|||||||
}
|
}
|
||||||
page
|
page
|
||||||
}
|
}
|
||||||
.retryWhen {
|
// Retry 3 times, waiting 2, 4 and 8 seconds between attempts.
|
||||||
it.zipWith(Observable.range(1, 3)) { errors, retries -> retries }
|
.retryWhen(RetryWithDelay(3, { (2 shl it - 1) * 1000 }))
|
||||||
.flatMap { retries -> Observable.timer((retries * 2).toLong(), TimeUnit.SECONDS) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public method to get the image from the filesystem. It does NOT provide any way to download the image
|
// Public method to get the image from the filesystem. It does NOT provide any way to download the image
|
||||||
|
@ -7,14 +7,13 @@ import android.os.IBinder
|
|||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
|
import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
|
||||||
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
|
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class DownloadService : Service() {
|
class DownloadService : Service() {
|
||||||
|
|
||||||
@ -29,8 +28,8 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject lateinit var downloadManager: DownloadManager
|
val downloadManager: DownloadManager by injectLazy()
|
||||||
@Inject lateinit var preferences: PreferencesHelper
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
private var networkChangeSubscription: Subscription? = null
|
private var networkChangeSubscription: Subscription? = null
|
||||||
@ -39,7 +38,6 @@ class DownloadService : Service() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
App.get(this).component.inject(this)
|
|
||||||
|
|
||||||
createWakeLock()
|
createWakeLock()
|
||||||
|
|
||||||
|
@ -7,28 +7,26 @@ import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
|
|||||||
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
|
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.bumptech.glide.module.GlideModule
|
import com.bumptech.glide.module.GlideModule
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to update Glide module settings
|
* Class used to update Glide module settings
|
||||||
*/
|
*/
|
||||||
class AppGlideModule : GlideModule {
|
class AppGlideModule : GlideModule {
|
||||||
|
|
||||||
@Inject lateinit var networkHelper: NetworkHelper
|
|
||||||
|
|
||||||
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
||||||
// Set the cache size of Glide to 15 MiB
|
// Set the cache size of Glide to 15 MiB
|
||||||
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 15 * 1024 * 1024))
|
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 15 * 1024 * 1024))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun registerComponents(context: Context, glide: Glide) {
|
override fun registerComponents(context: Context, glide: Glide) {
|
||||||
App.get(context).component.inject(this)
|
val networkFactory = OkHttpUrlLoader.Factory(Injekt.get<NetworkHelper>().client)
|
||||||
glide.register(GlideUrl::class.java, InputStream::class.java,
|
|
||||||
OkHttpUrlLoader.Factory(networkHelper.client))
|
glide.register(GlideUrl::class.java, InputStream::class.java, networkFactory)
|
||||||
glide.register(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory())
|
glide.register(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,13 @@ import com.bumptech.glide.Glide
|
|||||||
import com.bumptech.glide.load.data.DataFetcher
|
import com.bumptech.glide.load.data.DataFetcher
|
||||||
import com.bumptech.glide.load.model.*
|
import com.bumptech.glide.load.model.*
|
||||||
import com.bumptech.glide.load.model.stream.StreamModelLoader
|
import com.bumptech.glide.load.model.stream.StreamModelLoader
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for loading a cover associated with a [Manga] that can be present in our own cache.
|
* A class for loading a cover associated with a [Manga] that can be present in our own cache.
|
||||||
@ -30,12 +29,12 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
|||||||
/**
|
/**
|
||||||
* Cover cache where persistent covers are stored.
|
* Cover cache where persistent covers are stored.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var coverCache: CoverCache
|
val coverCache: CoverCache by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source manager.
|
* Source manager.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base network loader.
|
* Base network loader.
|
||||||
@ -54,10 +53,6 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
|||||||
*/
|
*/
|
||||||
private val cachedHeaders = hashMapOf<Int, LazyHeaders>()
|
private val cachedHeaders = hashMapOf<Int, LazyHeaders>()
|
||||||
|
|
||||||
init {
|
|
||||||
App.get(context).component.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory class for creating [MangaModelLoader] instances.
|
* Factory class for creating [MangaModelLoader] instances.
|
||||||
*/
|
*/
|
||||||
@ -88,7 +83,7 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
|||||||
// Obtain the request url and the file for this url from the LRU cache, or calculate it
|
// Obtain the request url and the file for this url from the LRU cache, or calculate it
|
||||||
// and add them to the cache.
|
// and add them to the cache.
|
||||||
val (glideUrl, file) = modelCache.get(url, width, height) ?:
|
val (glideUrl, file) = modelCache.get(url, width, height) ?:
|
||||||
Pair(GlideUrl(url, getHeaders(manga)), coverCache.getCoverFile(url)).apply {
|
Pair(GlideUrl(url, getHeaders(manga)), coverCache.getCoverFile(url!!)).apply {
|
||||||
modelCache.put(url, width, height, this)
|
modelCache.put(url, width, height, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,12 +10,12 @@ import android.os.PowerManager
|
|||||||
import android.support.v4.app.NotificationCompat
|
import android.support.v4.app.NotificationCompat
|
||||||
import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
|
import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
|
||||||
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
|
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.Constants
|
import eu.kanade.tachiyomi.Constants
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
@ -24,9 +24,9 @@ import eu.kanade.tachiyomi.util.*
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class will take care of updating the chapters of the manga from the library. It can be
|
* This class will take care of updating the chapters of the manga from the library. It can be
|
||||||
@ -41,17 +41,17 @@ class LibraryUpdateService : Service() {
|
|||||||
/**
|
/**
|
||||||
* Database helper.
|
* Database helper.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source manager.
|
* Source manager.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences.
|
* Preferences.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var preferences: PreferencesHelper
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wake lock that will be held until the service is destroyed.
|
* Wake lock that will be held until the service is destroyed.
|
||||||
@ -126,7 +126,6 @@ class LibraryUpdateService : Service() {
|
|||||||
*/
|
*/
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
App.get(this).component.inject(this)
|
|
||||||
createAndAcquireWakeLock()
|
createAndAcquireWakeLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,23 +2,18 @@ package eu.kanade.tachiyomi.data.mangasync
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.annotation.CallSuper
|
import android.support.annotation.CallSuper
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||||
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import rx.Completable
|
import rx.Completable
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
abstract class MangaSyncService(private val context: Context, val id: Int) {
|
abstract class MangaSyncService(private val context: Context, val id: Int) {
|
||||||
|
|
||||||
@Inject lateinit var preferences: PreferencesHelper
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
@Inject lateinit var networkService: NetworkHelper
|
val networkService: NetworkHelper by injectLazy()
|
||||||
|
|
||||||
init {
|
|
||||||
App.get(context).component.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
open val client: OkHttpClient
|
open val client: OkHttpClient
|
||||||
get() = networkService.client
|
get() = networkService.client
|
||||||
|
@ -4,25 +4,23 @@ import android.app.Service
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class UpdateMangaSyncService : Service() {
|
class UpdateMangaSyncService : Service() {
|
||||||
|
|
||||||
@Inject lateinit var syncManager: MangaSyncManager
|
val syncManager: MangaSyncManager by injectLazy()
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
private lateinit var subscriptions: CompositeSubscription
|
private lateinit var subscriptions: CompositeSubscription
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
App.get(this).component.inject(this)
|
|
||||||
subscriptions = CompositeSubscription()
|
subscriptions = CompositeSubscription()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +97,8 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
|
|||||||
.flatMap { Observable.from(it.select("entry")) }
|
.flatMap { Observable.from(it.select("entry")) }
|
||||||
.filter { it.select("type").text() != "Novel" }
|
.filter { it.select("type").text() != "Novel" }
|
||||||
.map {
|
.map {
|
||||||
MangaSync.create(this).apply {
|
MangaSync.create(id).apply {
|
||||||
title = it.selectText("title")
|
title = it.selectText("title")!!
|
||||||
remote_id = it.selectInt("id")
|
remote_id = it.selectInt("id")
|
||||||
total_chapters = it.selectInt("chapters")
|
total_chapters = it.selectInt("chapters")
|
||||||
}
|
}
|
||||||
@ -114,8 +114,8 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
|
|||||||
.map { Jsoup.parse(it.body().string()) }
|
.map { Jsoup.parse(it.body().string()) }
|
||||||
.flatMap { Observable.from(it.select("manga")) }
|
.flatMap { Observable.from(it.select("manga")) }
|
||||||
.map {
|
.map {
|
||||||
MangaSync.create(this).apply {
|
MangaSync.create(id).apply {
|
||||||
title = it.selectText("series_title")
|
title = it.selectText("series_title")!!
|
||||||
remote_id = it.selectInt("series_mangadb_id")
|
remote_id = it.selectInt("series_mangadb_id")
|
||||||
last_chapter_read = it.selectInt("my_read_chapters")
|
last_chapter_read = it.selectInt("my_read_chapters")
|
||||||
status = it.selectInt("my_status")
|
status = it.selectInt("my_status")
|
||||||
|
@ -54,8 +54,6 @@ class PreferenceKeys(context: Context) {
|
|||||||
|
|
||||||
val lastUsedCategory = context.getString(R.string.pref_last_used_category_key)
|
val lastUsedCategory = context.getString(R.string.pref_last_used_category_key)
|
||||||
|
|
||||||
val seamlessMode = context.getString(R.string.pref_seamless_mode_key)
|
|
||||||
|
|
||||||
val catalogueAsList = context.getString(R.string.pref_display_catalogue_as_list)
|
val catalogueAsList = context.getString(R.string.pref_display_catalogue_as_list)
|
||||||
|
|
||||||
val enabledLanguages = context.getString(R.string.pref_source_languages)
|
val enabledLanguages = context.getString(R.string.pref_source_languages)
|
||||||
|
@ -101,8 +101,6 @@ class PreferencesHelper(private val context: Context) {
|
|||||||
|
|
||||||
fun lastVersionCode() = rxPrefs.getInteger("last_version_code", 0)
|
fun lastVersionCode() = rxPrefs.getInteger("last_version_code", 0)
|
||||||
|
|
||||||
fun seamlessMode() = prefs.getBoolean(keys.seamlessMode, true)
|
|
||||||
|
|
||||||
fun catalogueAsList() = rxPrefs.getBoolean(keys.catalogueAsList, false)
|
fun catalogueAsList() = rxPrefs.getBoolean(keys.catalogueAsList, false)
|
||||||
|
|
||||||
fun enabledLanguages() = rxPrefs.getStringSet(keys.enabledLanguages, setOf("EN"))
|
fun enabledLanguages() = rxPrefs.getStringSet(keys.enabledLanguages, setOf("EN"))
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.data.source.model;
|
package eu.kanade.tachiyomi.data.source.model;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
|
||||||
import eu.kanade.tachiyomi.data.network.ProgressListener;
|
import eu.kanade.tachiyomi.data.network.ProgressListener;
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderChapter;
|
||||||
import rx.subjects.PublishSubject;
|
import rx.subjects.PublishSubject;
|
||||||
|
|
||||||
public class Page implements ProgressListener {
|
public class Page implements ProgressListener {
|
||||||
@ -11,7 +9,7 @@ public class Page implements ProgressListener {
|
|||||||
private int pageNumber;
|
private int pageNumber;
|
||||||
private String url;
|
private String url;
|
||||||
private String imageUrl;
|
private String imageUrl;
|
||||||
private transient Chapter chapter;
|
private transient ReaderChapter chapter;
|
||||||
private transient String imagePath;
|
private transient String imagePath;
|
||||||
private transient volatile int status;
|
private transient volatile int status;
|
||||||
private transient volatile int progress;
|
private transient volatile int progress;
|
||||||
@ -90,16 +88,12 @@ public class Page implements ProgressListener {
|
|||||||
this.statusSubject = subject;
|
this.statusSubject = subject;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Chapter getChapter() {
|
public ReaderChapter getChapter() {
|
||||||
return chapter;
|
return chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChapter(Chapter chapter) {
|
public void setChapter(ReaderChapter chapter) {
|
||||||
this.chapter = chapter;
|
this.chapter = chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLastPage() {
|
|
||||||
List<Page> chapterPages = chapter.getPages();
|
|
||||||
return chapterPages.size() -1 == pageNumber;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.source.online
|
package eu.kanade.tachiyomi.data.source.online
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
@ -14,9 +13,10 @@ import eu.kanade.tachiyomi.data.source.Language
|
|||||||
import eu.kanade.tachiyomi.data.source.Source
|
import eu.kanade.tachiyomi.data.source.Source
|
||||||
import eu.kanade.tachiyomi.data.source.model.MangasPage
|
import eu.kanade.tachiyomi.data.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.util.UrlUtil
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple implementation for sources from a website.
|
* A simple implementation for sources from a website.
|
||||||
@ -28,17 +28,17 @@ abstract class OnlineSource(context: Context) : Source {
|
|||||||
/**
|
/**
|
||||||
* Network service.
|
* Network service.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var network: NetworkHelper
|
val network: NetworkHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chapter cache.
|
* Chapter cache.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var chapterCache: ChapterCache
|
val chapterCache: ChapterCache by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences helper.
|
* Preferences helper.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var preferences: PreferencesHelper
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base url of the website without the trailing slash, like: http://mysite.com
|
* Base url of the website without the trailing slash, like: http://mysite.com
|
||||||
@ -61,11 +61,6 @@ abstract class OnlineSource(context: Context) : Source {
|
|||||||
open val client: OkHttpClient
|
open val client: OkHttpClient
|
||||||
get() = network.client
|
get() = network.client
|
||||||
|
|
||||||
init {
|
|
||||||
// Inject dependencies.
|
|
||||||
App.get(context).component.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Headers builder for requests. Implementations can override this method for custom headers.
|
* Headers builder for requests. Implementations can override this method for custom headers.
|
||||||
*/
|
*/
|
||||||
@ -443,6 +438,15 @@ abstract class OnlineSource(context: Context) : Source {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Chapter.setUrlWithoutDomain(url: String) {
|
||||||
|
this.url = UrlUtil.getPath(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Manga.setUrlWithoutDomain(url: String) {
|
||||||
|
this.url = UrlUtil.getPath(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Overridable method to allow custom parsing.
|
// Overridable method to allow custom parsing.
|
||||||
open fun parseChapterNumber(chapter: Chapter) {
|
open fun parseChapterNumber(chapter: Chapter) {
|
||||||
|
|
||||||
|
@ -26,8 +26,7 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) {
|
|||||||
override fun popularMangaParse(response: Response, page: MangasPage) {
|
override fun popularMangaParse(response: Response, page: MangasPage) {
|
||||||
val document = Jsoup.parse(response.body().string())
|
val document = Jsoup.parse(response.body().string())
|
||||||
for (element in document.select(popularMangaSelector())) {
|
for (element in document.select(popularMangaSelector())) {
|
||||||
Manga().apply {
|
Manga.create(id).apply {
|
||||||
source = this@ParsedOnlineSource.id
|
|
||||||
popularMangaFromElement(element, this)
|
popularMangaFromElement(element, this)
|
||||||
page.mangas.add(this)
|
page.mangas.add(this)
|
||||||
}
|
}
|
||||||
@ -70,8 +69,7 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) {
|
|||||||
override fun searchMangaParse(response: Response, page: MangasPage, query: String) {
|
override fun searchMangaParse(response: Response, page: MangasPage, query: String) {
|
||||||
val document = Jsoup.parse(response.body().string())
|
val document = Jsoup.parse(response.body().string())
|
||||||
for (element in document.select(searchMangaSelector())) {
|
for (element in document.select(searchMangaSelector())) {
|
||||||
Manga().apply {
|
Manga.create(id).apply {
|
||||||
source = this@ParsedOnlineSource.id
|
|
||||||
searchMangaFromElement(element, this)
|
searchMangaFromElement(element, this)
|
||||||
page.mangas.add(this)
|
page.mangas.add(this)
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,9 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
|
|||||||
override fun popularMangaParse(response: Response, page: MangasPage) {
|
override fun popularMangaParse(response: Response, page: MangasPage) {
|
||||||
val document = Jsoup.parse(response.body().string())
|
val document = Jsoup.parse(response.body().string())
|
||||||
for (element in document.select(map.popular.manga_css)) {
|
for (element in document.select(map.popular.manga_css)) {
|
||||||
Manga().apply {
|
Manga.create(id).apply {
|
||||||
source = this@YamlOnlineSource.id
|
|
||||||
title = element.text()
|
title = element.text()
|
||||||
setUrl(element.attr("href"))
|
setUrlWithoutDomain(element.attr("href"))
|
||||||
page.mangas.add(this)
|
page.mangas.add(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,10 +83,9 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
|
|||||||
override fun searchMangaParse(response: Response, page: MangasPage, query: String) {
|
override fun searchMangaParse(response: Response, page: MangasPage, query: String) {
|
||||||
val document = Jsoup.parse(response.body().string())
|
val document = Jsoup.parse(response.body().string())
|
||||||
for (element in document.select(map.search.manga_css)) {
|
for (element in document.select(map.search.manga_css)) {
|
||||||
Manga().apply {
|
Manga.create(id).apply {
|
||||||
source = this@YamlOnlineSource.id
|
|
||||||
title = element.text()
|
title = element.text()
|
||||||
setUrl(element.attr("href"))
|
setUrlWithoutDomain(element.attr("href"))
|
||||||
page.mangas.add(this)
|
page.mangas.add(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +121,7 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
|
|||||||
val chapter = Chapter.create()
|
val chapter = Chapter.create()
|
||||||
element.select(title).first().let {
|
element.select(title).first().let {
|
||||||
chapter.name = it.text()
|
chapter.name = it.text()
|
||||||
chapter.setUrl(it.attr("href"))
|
chapter.setUrlWithoutDomain(it.attr("href"))
|
||||||
}
|
}
|
||||||
val dateElement = element.select(date?.select).first()
|
val dateElement = element.select(date?.select).first()
|
||||||
chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0
|
chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0
|
||||||
|
@ -62,8 +62,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
|
|||||||
override fun popularMangaParse(response: Response, page: MangasPage) {
|
override fun popularMangaParse(response: Response, page: MangasPage) {
|
||||||
val document = Jsoup.parse(response.body().string())
|
val document = Jsoup.parse(response.body().string())
|
||||||
for (element in document.select(popularMangaSelector())) {
|
for (element in document.select(popularMangaSelector())) {
|
||||||
Manga().apply {
|
Manga.create(id).apply {
|
||||||
source = this@Batoto.id
|
|
||||||
popularMangaFromElement(element, this)
|
popularMangaFromElement(element, this)
|
||||||
page.mangas.add(this)
|
page.mangas.add(this)
|
||||||
}
|
}
|
||||||
@ -78,7 +77,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
|
|||||||
|
|
||||||
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("a[href^=http://bato.to]").first().let {
|
element.select("a[href^=http://bato.to]").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.text().trim()
|
manga.title = it.text().trim()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,8 +89,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
|
|||||||
override fun searchMangaParse(response: Response, page: MangasPage, query: String) {
|
override fun searchMangaParse(response: Response, page: MangasPage, query: String) {
|
||||||
val document = Jsoup.parse(response.body().string())
|
val document = Jsoup.parse(response.body().string())
|
||||||
for (element in document.select(searchMangaSelector())) {
|
for (element in document.select(searchMangaSelector())) {
|
||||||
Manga().apply {
|
Manga.create(id).apply {
|
||||||
source = this@Batoto.id
|
|
||||||
searchMangaFromElement(element, this)
|
searchMangaFromElement(element, this)
|
||||||
page.mangas.add(this)
|
page.mangas.add(this)
|
||||||
}
|
}
|
||||||
@ -156,7 +154,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
|
|||||||
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
||||||
val urlElement = element.select("a[href^=http://bato.to/reader").first()
|
val urlElement = element.select("a[href^=http://bato.to/reader").first()
|
||||||
|
|
||||||
chapter.setUrl(urlElement.attr("href"))
|
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||||
chapter.name = urlElement.text()
|
chapter.name = urlElement.text()
|
||||||
chapter.date_upload = element.select("td").getOrNull(4)?.let {
|
chapter.date_upload = element.select("td").getOrNull(4)?.let {
|
||||||
parseDateFromElement(it)
|
parseDateFromElement(it)
|
||||||
|
@ -35,7 +35,7 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
|
|
||||||
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("td a:eq(0)").first().let {
|
element.select("td a:eq(0)").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.text()
|
manga.title = it.text()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
||||||
val urlElement = element.select("a").first()
|
val urlElement = element.select("a").first()
|
||||||
|
|
||||||
chapter.setUrl(urlElement.attr("href"))
|
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||||
chapter.name = urlElement.text()
|
chapter.name = urlElement.text()
|
||||||
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
|
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
|
||||||
SimpleDateFormat("MM/dd/yyyy").parse(it).time
|
SimpleDateFormat("MM/dd/yyyy").parse(it).time
|
||||||
|
@ -29,7 +29,7 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
|
|||||||
|
|
||||||
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("a.title").first().let {
|
element.select("a.title").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.text()
|
manga.title = it.text()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
|
|||||||
|
|
||||||
override fun searchMangaFromElement(element: Element, manga: Manga) {
|
override fun searchMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("a.series_preview").first().let {
|
element.select("a.series_preview").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.text()
|
manga.title = it.text()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,7 +74,7 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
|
|||||||
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
||||||
val urlElement = element.select("a.tips").first()
|
val urlElement = element.select("a.tips").first()
|
||||||
|
|
||||||
chapter.setUrl(urlElement.attr("href"))
|
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||||
chapter.name = urlElement.text()
|
chapter.name = urlElement.text()
|
||||||
chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0
|
chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
|
|
||||||
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("div.title > a").first().let {
|
element.select("div.title > a").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.text()
|
manga.title = it.text()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
|
|
||||||
override fun searchMangaFromElement(element: Element, manga: Manga) {
|
override fun searchMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("a.manga_info").first().let {
|
element.select("a.manga_info").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.text()
|
manga.title = it.text()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
||||||
val urlElement = element.select("a").first()
|
val urlElement = element.select("a").first()
|
||||||
|
|
||||||
chapter.setUrl(urlElement.attr("href"))
|
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||||
chapter.name = urlElement.text()
|
chapter.name = urlElement.text()
|
||||||
chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0
|
chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
|
|||||||
|
|
||||||
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("div.title > h2 > a").first().let {
|
element.select("div.title > h2 > a").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.attr("title")
|
manga.title = it.attr("title")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
|
|||||||
|
|
||||||
override fun searchMangaFromElement(element: Element, manga: Manga) {
|
override fun searchMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("div.title > h2 > a").first().let {
|
element.select("div.title > h2 > a").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.attr("title")
|
manga.title = it.attr("title")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
|
|||||||
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
||||||
val urlElement = element.select("a").first()
|
val urlElement = element.select("a").first()
|
||||||
|
|
||||||
chapter.setUrl(urlElement.attr("href"))
|
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||||
chapter.name = urlElement.select("span.val").text()
|
chapter.name = urlElement.select("span.val").text()
|
||||||
chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0
|
chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
|
|
||||||
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("h2 > a").first().let {
|
element.select("h2 > a").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.text()
|
manga.title = it.text()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
||||||
val urlElement = element.select("a").first()
|
val urlElement = element.select("a").first()
|
||||||
|
|
||||||
chapter.setUrl(urlElement.attr("href"))
|
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||||
chapter.name = urlElement.text()
|
chapter.name = urlElement.text()
|
||||||
chapter.date_upload = element.select("div.date").first()?.text()?.let {
|
chapter.date_upload = element.select("div.date").first()?.text()?.let {
|
||||||
SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time
|
SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time
|
||||||
|
@ -30,7 +30,7 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
|
|
||||||
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("h3 > a").first().let {
|
element.select("h3 > a").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.attr("title")
|
manga.title = it.attr("title")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
||||||
val urlElement = element.select("a").first()
|
val urlElement = element.select("a").first()
|
||||||
|
|
||||||
chapter.setUrl(urlElement.attr("href") + "?mature=1")
|
chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1")
|
||||||
chapter.name = urlElement.text().replace(" новое", "")
|
chapter.name = urlElement.text().replace(" новое", "")
|
||||||
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
|
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
|
||||||
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time
|
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time
|
||||||
|
@ -30,7 +30,7 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
|
|
||||||
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
||||||
element.select("h3 > a").first().let {
|
element.select("h3 > a").first().let {
|
||||||
manga.setUrl(it.attr("href"))
|
manga.setUrlWithoutDomain(it.attr("href"))
|
||||||
manga.title = it.attr("title")
|
manga.title = it.attr("title")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
|
|||||||
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
override fun chapterFromElement(element: Element, chapter: Chapter) {
|
||||||
val urlElement = element.select("a").first()
|
val urlElement = element.select("a").first()
|
||||||
|
|
||||||
chapter.setUrl(urlElement.attr("href") + "?mature=1")
|
chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1")
|
||||||
chapter.name = urlElement.text().replace(" новое", "")
|
chapter.name = urlElement.text().replace(" новое", "")
|
||||||
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
|
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
|
||||||
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time
|
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time
|
||||||
|
@ -8,7 +8,6 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
import android.support.v4.app.NotificationCompat
|
import android.support.v4.app.NotificationCompat
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.Constants
|
import eu.kanade.tachiyomi.Constants
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.network.GET
|
import eu.kanade.tachiyomi.data.network.GET
|
||||||
@ -18,8 +17,8 @@ import eu.kanade.tachiyomi.data.network.newCallWithProgress
|
|||||||
import eu.kanade.tachiyomi.util.notificationManager
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
import eu.kanade.tachiyomi.util.saveTo
|
import eu.kanade.tachiyomi.util.saveTo
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class UpdateDownloader(private val context: Context) :
|
class UpdateDownloader(private val context: Context) :
|
||||||
AsyncTask<String, Int, UpdateDownloader.DownloadResult>() {
|
AsyncTask<String, Int, UpdateDownloader.DownloadResult>() {
|
||||||
@ -40,7 +39,7 @@ class UpdateDownloader(private val context: Context) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject lateinit var network: NetworkHelper
|
val network: NetworkHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default download dir
|
* Default download dir
|
||||||
@ -59,9 +58,6 @@ class UpdateDownloader(private val context: Context) :
|
|||||||
private val notificationId: Int
|
private val notificationId: Int
|
||||||
get() = Constants.NOTIFICATION_UPDATER_ID
|
get() = Constants.NOTIFICATION_UPDATER_ID
|
||||||
|
|
||||||
init {
|
|
||||||
App.get(context).component.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class containing download result
|
* Class containing download result
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.injection;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.App;
|
|
||||||
import eu.kanade.tachiyomi.injection.component.AppComponent;
|
|
||||||
import eu.kanade.tachiyomi.injection.component.DaggerAppComponent;
|
|
||||||
import eu.kanade.tachiyomi.injection.module.AppModule;
|
|
||||||
|
|
||||||
|
|
||||||
public class AppComponentFactory {
|
|
||||||
|
|
||||||
public static AppComponent create(App app) {
|
|
||||||
return DaggerAppComponent.builder().appModule(new AppModule(app)).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.injection;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class allows to inject into objects through a base class,
|
|
||||||
* so we don't have to repeat injection code everywhere.
|
|
||||||
*
|
|
||||||
* The performance drawback is about 0.013 ms per injection on a very slow device,
|
|
||||||
* which is negligible in most cases.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* <pre>{@code
|
|
||||||
* Component {
|
|
||||||
* void inject(B b);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* class A {
|
|
||||||
* void onCreate() {
|
|
||||||
* componentReflectionInjector.inject(this);
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* class B extends A {
|
|
||||||
* @Inject MyDependency dependency;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* new B().onCreate() // dependency will be injected at this point
|
|
||||||
*
|
|
||||||
* class C extends B {
|
|
||||||
*
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* new C().onCreate() // dependency will be injected at this point as well
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* @param <T> a type of dagger 2 component.
|
|
||||||
*/
|
|
||||||
public final class ComponentReflectionInjector<T> {
|
|
||||||
|
|
||||||
private static final ConcurrentHashMap<Class<?>, HashMap<Class<?>, Method>> cache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final Class<T> componentClass;
|
|
||||||
private final T component;
|
|
||||||
private final HashMap<Class<?>, Method> methods;
|
|
||||||
|
|
||||||
public ComponentReflectionInjector(Class<T> componentClass, T component) {
|
|
||||||
this.componentClass = componentClass;
|
|
||||||
this.component = component;
|
|
||||||
this.methods = getMethods(componentClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getComponent() {
|
|
||||||
return component;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void inject(Object target) {
|
|
||||||
|
|
||||||
Class targetClass = target.getClass();
|
|
||||||
Method method = methods.get(targetClass);
|
|
||||||
while (method == null && targetClass != null) {
|
|
||||||
targetClass = targetClass.getSuperclass();
|
|
||||||
method = methods.get(targetClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method == null)
|
|
||||||
throw new RuntimeException(String.format("No %s injecting method exists in %s component", target.getClass(), componentClass));
|
|
||||||
|
|
||||||
try {
|
|
||||||
method.invoke(component, target);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HashMap<Class<?>, Method> getMethods(Class componentClass) {
|
|
||||||
HashMap<Class<?>, Method> methods = cache.get(componentClass);
|
|
||||||
if (methods == null) {
|
|
||||||
synchronized (cache) {
|
|
||||||
methods = cache.get(componentClass);
|
|
||||||
if (methods == null) {
|
|
||||||
methods = new HashMap<>();
|
|
||||||
for (Method method : componentClass.getMethods()) {
|
|
||||||
Class<?>[] params = method.getParameterTypes();
|
|
||||||
if (params.length == 1)
|
|
||||||
methods.put(params[0], method);
|
|
||||||
}
|
|
||||||
cache.put(componentClass, methods);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return methods;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.injection.component
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import dagger.Component
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
|
||||||
import eu.kanade.tachiyomi.data.glide.AppGlideModule
|
|
||||||
import eu.kanade.tachiyomi.data.glide.MangaModelLoader
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
|
||||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncService
|
|
||||||
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
|
|
||||||
import eu.kanade.tachiyomi.data.source.Source
|
|
||||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateDownloader
|
|
||||||
import eu.kanade.tachiyomi.injection.module.AppModule
|
|
||||||
import eu.kanade.tachiyomi.injection.module.DataModule
|
|
||||||
import eu.kanade.tachiyomi.ui.backup.BackupPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadPresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Component(modules = arrayOf(AppModule::class, DataModule::class))
|
|
||||||
interface AppComponent {
|
|
||||||
|
|
||||||
fun inject(libraryPresenter: LibraryPresenter)
|
|
||||||
fun inject(mangaPresenter: MangaPresenter)
|
|
||||||
fun inject(cataloguePresenter: CataloguePresenter)
|
|
||||||
fun inject(mangaInfoPresenter: MangaInfoPresenter)
|
|
||||||
fun inject(chaptersPresenter: ChaptersPresenter)
|
|
||||||
fun inject(readerPresenter: ReaderPresenter)
|
|
||||||
fun inject(downloadPresenter: DownloadPresenter)
|
|
||||||
fun inject(myAnimeListPresenter: MyAnimeListPresenter)
|
|
||||||
fun inject(categoryPresenter: CategoryPresenter)
|
|
||||||
fun inject(recentChaptersPresenter: RecentChaptersPresenter)
|
|
||||||
fun inject(recentlyReadPresenter: RecentlyReadPresenter)
|
|
||||||
fun inject(backupPresenter: BackupPresenter)
|
|
||||||
|
|
||||||
fun inject(mainActivity: MainActivity)
|
|
||||||
fun inject(settingsActivity: SettingsActivity)
|
|
||||||
|
|
||||||
fun inject(source: Source)
|
|
||||||
fun inject(mangaSyncService: MangaSyncService)
|
|
||||||
|
|
||||||
fun inject(onlineSource: OnlineSource)
|
|
||||||
|
|
||||||
fun inject(libraryUpdateService: LibraryUpdateService)
|
|
||||||
fun inject(downloadService: DownloadService)
|
|
||||||
fun inject(updateMangaSyncService: UpdateMangaSyncService)
|
|
||||||
|
|
||||||
fun inject(mangaModelLoader: MangaModelLoader)
|
|
||||||
fun inject(appGlideModule: AppGlideModule)
|
|
||||||
|
|
||||||
fun inject(updateDownloader: UpdateDownloader)
|
|
||||||
fun application(): Application
|
|
||||||
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.injection.module
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide application-level dependencies. Mainly singleton object that can be injected from
|
|
||||||
* anywhere in the app.
|
|
||||||
*/
|
|
||||||
@Module
|
|
||||||
class AppModule(private val application: Application) {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideApplication(): Application {
|
|
||||||
return application
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.injection.module
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
|
||||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
|
||||||
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide dependencies to the DataManager, mainly Helper classes and Retrofit services.
|
|
||||||
*/
|
|
||||||
@Module
|
|
||||||
open class DataModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun providePreferencesHelper(app: Application): PreferencesHelper {
|
|
||||||
return PreferencesHelper(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
open fun provideDatabaseHelper(app: Application): DatabaseHelper {
|
|
||||||
return DatabaseHelper(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideChapterCache(app: Application): ChapterCache {
|
|
||||||
return ChapterCache(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideCoverCache(app: Application): CoverCache {
|
|
||||||
return CoverCache(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
open fun provideNetworkHelper(app: Application): NetworkHelper {
|
|
||||||
return NetworkHelper(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
open fun provideSourceManager(app: Application): SourceManager {
|
|
||||||
return SourceManager(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideDownloadManager(app: Application, sourceManager: SourceManager, preferences: PreferencesHelper): DownloadManager {
|
|
||||||
return DownloadManager(app, sourceManager, preferences)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideMangaSyncManager(app: Application): MangaSyncManager {
|
|
||||||
return MangaSyncManager(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -8,9 +8,9 @@ import rx.Observable
|
|||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [BackupFragment].
|
* Presenter of [BackupFragment].
|
||||||
@ -20,7 +20,7 @@ class BackupPresenter : BasePresenter<BackupFragment>() {
|
|||||||
/**
|
/**
|
||||||
* Database.
|
* Database.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backup manager.
|
* Backup manager.
|
||||||
|
@ -12,7 +12,6 @@ abstract class BaseRxActivity<P : BasePresenter<*>> : NucleusAppCompatActivity<P
|
|||||||
setPresenterFactory {
|
setPresenterFactory {
|
||||||
superFactory.createPresenter().apply {
|
superFactory.createPresenter().apply {
|
||||||
val app = application as App
|
val app = application as App
|
||||||
app.componentReflection.inject(this)
|
|
||||||
context = app.applicationContext
|
context = app.applicationContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ abstract class BaseRxFragment<P : BasePresenter<*>> : NucleusSupportFragment<P>(
|
|||||||
setPresenterFactory {
|
setPresenterFactory {
|
||||||
superFactory.createPresenter().apply {
|
superFactory.createPresenter().apply {
|
||||||
val app = activity.application as App
|
val app = activity.application as App
|
||||||
app.componentReflection.inject(this)
|
|
||||||
context = app.applicationContext
|
context = app.applicationContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ class CatalogueAdapter(val fragment: CatalogueFragment) : FlexibleAdapter<Catalo
|
|||||||
* @return an identifier for the item.
|
* @return an identifier for the item.
|
||||||
*/
|
*/
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
return mItems[position].id
|
return mItems[position].id!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -383,7 +383,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
* @return the holder of the manga or null if it's not bound.
|
* @return the holder of the manga or null if it's not bound.
|
||||||
*/
|
*/
|
||||||
private fun getHolder(manga: Manga): CatalogueGridHolder? {
|
private fun getHolder(manga: Manga): CatalogueGridHolder? {
|
||||||
return catalogue_grid.findViewHolderForItemId(manga.id) as? CatalogueGridHolder
|
return catalogue_grid.findViewHolderForItemId(manga.id!!) as? CatalogueGridHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,7 +19,7 @@ import rx.android.schedulers.AndroidSchedulers
|
|||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [CatalogueFragment].
|
* Presenter of [CatalogueFragment].
|
||||||
@ -29,22 +29,22 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
/**
|
/**
|
||||||
* Source manager.
|
* Source manager.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database.
|
* Database.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences.
|
* Preferences.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var prefs: PreferencesHelper
|
val prefs: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cover cache.
|
* Cover cache.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var coverCache: CoverCache
|
val coverCache: CoverCache by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enabled sources.
|
* Enabled sources.
|
||||||
|
@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of CategoryActivity.
|
* Presenter of CategoryActivity.
|
||||||
@ -17,7 +17,7 @@ class CategoryPresenter : BasePresenter<CategoryActivity>() {
|
|||||||
/**
|
/**
|
||||||
* Used to connect to database
|
* Used to connect to database
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List containing categories
|
* List containing categories
|
||||||
|
@ -35,7 +35,7 @@ class DownloadAdapter(private val context: Context) : FlexibleAdapter<DownloadHo
|
|||||||
* @return an identifier for the item.
|
* @return an identifier for the item.
|
||||||
*/
|
*/
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
return getItem(position).chapter.id
|
return getItem(position).chapter.id!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -262,7 +262,7 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
|
|||||||
* @return the holder of the download or null if it's not bound.
|
* @return the holder of the download or null if it's not bound.
|
||||||
*/
|
*/
|
||||||
private fun getHolder(download: Download): DownloadHolder? {
|
private fun getHolder(download: Download): DownloadHolder? {
|
||||||
return recycler.findViewHolderForItemId(download.chapter.id) as? DownloadHolder
|
return recycler.findViewHolderForItemId(download.chapter.id!!) as? DownloadHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
|||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [DownloadFragment].
|
* Presenter of [DownloadFragment].
|
||||||
@ -24,7 +24,7 @@ class DownloadPresenter : BasePresenter<DownloadFragment>() {
|
|||||||
/**
|
/**
|
||||||
* Download manager.
|
* Download manager.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var downloadManager: DownloadManager
|
val downloadManager: DownloadManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property to get the queue from the download manager.
|
* Property to get the queue from the download manager.
|
||||||
|
@ -49,7 +49,7 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) :
|
|||||||
* @return an identifier for the item.
|
* @return an identifier for the item.
|
||||||
*/
|
*/
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
return mItems[position].id
|
return mItems[position].id!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,8 +72,8 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) :
|
|||||||
* @return true if the manga should be included, false otherwise.
|
* @return true if the manga should be included, false otherwise.
|
||||||
*/
|
*/
|
||||||
override fun filterObject(manga: Manga, query: String): Boolean = with(manga) {
|
override fun filterObject(manga: Manga, query: String): Boolean = with(manga) {
|
||||||
title != null && title.toLowerCase().contains(query) ||
|
title.toLowerCase().contains(query) ||
|
||||||
author != null && author.toLowerCase().contains(query)
|
author != null && author!!.toLowerCase().contains(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,10 +16,10 @@ import rx.Observable
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import rx.subjects.BehaviorSubject
|
import rx.subjects.BehaviorSubject
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [LibraryFragment].
|
* Presenter of [LibraryFragment].
|
||||||
@ -49,27 +49,27 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
/**
|
/**
|
||||||
* Database.
|
* Database.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences.
|
* Preferences.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var preferences: PreferencesHelper
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cover cache.
|
* Cover cache.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var coverCache: CoverCache
|
val coverCache: CoverCache by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source manager.
|
* Source manager.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download manager.
|
* Download manager.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var downloadManager: DownloadManager
|
val downloadManager: DownloadManager by injectLazy()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
@ -279,7 +279,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean {
|
fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean {
|
||||||
if (manga.thumbnail_url != null && manga.favorite) {
|
if (manga.thumbnail_url != null && manga.favorite) {
|
||||||
coverCache.copyToCache(manga.thumbnail_url, inputStream)
|
coverCache.copyToCache(manga.thumbnail_url!!, inputStream)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -5,7 +5,6 @@ import android.os.Bundle
|
|||||||
import android.support.v4.app.Fragment
|
import android.support.v4.app.Fragment
|
||||||
import android.support.v4.view.GravityCompat
|
import android.support.v4.view.GravityCompat
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.ui.backup.BackupFragment
|
import eu.kanade.tachiyomi.ui.backup.BackupFragment
|
||||||
@ -18,11 +17,11 @@ import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadFragment
|
|||||||
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
|
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.toolbar.*
|
import kotlinx.android.synthetic.main.toolbar.*
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class MainActivity : BaseActivity() {
|
class MainActivity : BaseActivity() {
|
||||||
|
|
||||||
@Inject lateinit var preferences: PreferencesHelper
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
setAppTheme()
|
setAppTheme()
|
||||||
@ -34,8 +33,6 @@ class MainActivity : BaseActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
App.get(this).component.inject(this)
|
|
||||||
|
|
||||||
// Inflate activity_main.xml.
|
// Inflate activity_main.xml.
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent
|
|||||||
import eu.kanade.tachiyomi.util.SharedData
|
import eu.kanade.tachiyomi.util.SharedData
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [MangaActivity].
|
* Presenter of [MangaActivity].
|
||||||
@ -19,12 +19,12 @@ class MangaPresenter : BasePresenter<MangaActivity>() {
|
|||||||
/**
|
/**
|
||||||
* Database helper.
|
* Database helper.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manga sync manager.
|
* Manga sync manager.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var syncManager: MangaSyncManager
|
val syncManager: MangaSyncManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manga associated with this instance.
|
* Manga associated with this instance.
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
|
|
||||||
|
class ChapterModel(c: Chapter) : Chapter by c {
|
||||||
|
|
||||||
|
private var _status: Int = 0
|
||||||
|
|
||||||
|
var status: Int
|
||||||
|
get() = download?.status ?: _status
|
||||||
|
set(value) { _status = value }
|
||||||
|
|
||||||
|
var download: Download? = null
|
||||||
|
|
||||||
|
val isDownloaded: Boolean
|
||||||
|
get() = status == Download.DOWNLOADED
|
||||||
|
|
||||||
|
}
|
@ -3,10 +3,9 @@ package eu.kanade.tachiyomi.ui.manga.chapter
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.util.inflate
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
|
|
||||||
class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<ChaptersHolder, Chapter>() {
|
class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<ChaptersHolder, ChapterModel>() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasStableIds(true)
|
setHasStableIds(true)
|
||||||
@ -30,10 +29,10 @@ class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<Chapters
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
return mItems[position].id
|
return mItems[position].id!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setItems(chapters: List<Chapter>) {
|
fun setItems(chapters: List<ChapterModel>) {
|
||||||
mItems = chapters
|
mItems = chapters
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
@ -134,8 +134,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
|||||||
presenter.setDownloadedFilter(item.isChecked)
|
presenter.setDownloadedFilter(item.isChecked)
|
||||||
}
|
}
|
||||||
R.id.action_filter_empty -> {
|
R.id.action_filter_empty -> {
|
||||||
presenter.setReadFilter(false)
|
presenter.removeFilters()
|
||||||
presenter.setDownloadedFilter(false)
|
|
||||||
activity.supportInvalidateOptionsMenu()
|
activity.supportInvalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
R.id.action_sort -> presenter.revertSortOrder()
|
R.id.action_sort -> presenter.revertSortOrder()
|
||||||
@ -150,7 +149,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
|||||||
setDownloadedFilter()
|
setDownloadedFilter()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNextChapters(chapters: List<Chapter>) {
|
fun onNextChapters(chapters: List<ChapterModel>) {
|
||||||
// If the list is empty, fetch chapters from source if the conditions are met
|
// If the list is empty, fetch chapters from source if the conditions are met
|
||||||
// We use presenter chapters instead because they are always unfiltered
|
// We use presenter chapters instead because they are always unfiltered
|
||||||
if (presenter.chapters.isEmpty())
|
if (presenter.chapters.isEmpty())
|
||||||
@ -206,7 +205,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
|||||||
// Save the new display mode
|
// Save the new display mode
|
||||||
presenter.setDisplayMode(itemView.id)
|
presenter.setDisplayMode(itemView.id)
|
||||||
// Refresh ui
|
// Refresh ui
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@ -271,7 +270,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getHolder(chapter: Chapter): ChaptersHolder? {
|
private fun getHolder(chapter: Chapter): ChaptersHolder? {
|
||||||
return recycler.findViewHolderForItemId(chapter.id) as? ChaptersHolder
|
return recycler.findViewHolderForItemId(chapter.id!!) as? ChaptersHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||||
@ -309,7 +308,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
|||||||
actionMode = null
|
actionMode = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelectedChapters(): List<Chapter> {
|
fun getSelectedChapters(): List<ChapterModel> {
|
||||||
return adapter.selectedItems.map { adapter.getItem(it) }
|
return adapter.selectedItems.map { adapter.getItem(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,27 +321,27 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
|||||||
setContextTitle(adapter.selectedItemCount)
|
setContextTitle(adapter.selectedItemCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markAsRead(chapters: List<Chapter>) {
|
fun markAsRead(chapters: List<ChapterModel>) {
|
||||||
presenter.markChaptersRead(chapters, true)
|
presenter.markChaptersRead(chapters, true)
|
||||||
if (presenter.preferences.removeAfterMarkedAsRead()) {
|
if (presenter.preferences.removeAfterMarkedAsRead()) {
|
||||||
deleteChapters(chapters)
|
deleteChapters(chapters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markAsUnread(chapters: List<Chapter>) {
|
fun markAsUnread(chapters: List<ChapterModel>) {
|
||||||
presenter.markChaptersRead(chapters, false)
|
presenter.markChaptersRead(chapters, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markPreviousAsRead(chapter: Chapter) {
|
fun markPreviousAsRead(chapter: ChapterModel) {
|
||||||
presenter.markPreviousChaptersAsRead(chapter)
|
presenter.markPreviousChaptersAsRead(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadChapters(chapters: List<Chapter>) {
|
fun downloadChapters(chapters: List<ChapterModel>) {
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
presenter.downloadChapters(chapters)
|
presenter.downloadChapters(chapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteChapters(chapters: List<Chapter>) {
|
fun deleteChapters(chapters: List<ChapterModel>) {
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
|
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
|
||||||
presenter.deleteChapters(chapters)
|
presenter.deleteChapters(chapters)
|
||||||
@ -350,7 +349,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
|||||||
|
|
||||||
fun onChaptersDeleted() {
|
fun onChaptersDeleted() {
|
||||||
dismissDeletingDialog()
|
dismissDeletingDialog()
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onChaptersDeletedError(error: Throwable) {
|
fun onChaptersDeletedError(error: Throwable) {
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
@ -26,7 +24,7 @@ class ChaptersHolder(
|
|||||||
private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' })
|
private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' })
|
||||||
private val df = DateFormat.getDateInstance(DateFormat.SHORT)
|
private val df = DateFormat.getDateInstance(DateFormat.SHORT)
|
||||||
|
|
||||||
private var item: Chapter? = null
|
private var item: ChapterModel? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
|
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
|
||||||
@ -35,19 +33,16 @@ class ChaptersHolder(
|
|||||||
view.chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } }
|
view.chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSetValues(chapter: Chapter, manga: Manga?) = with(view) {
|
fun onSetValues(chapter: ChapterModel, manga: Manga?) = with(view) {
|
||||||
item = chapter
|
item = chapter
|
||||||
|
|
||||||
val name: String
|
chapter_title.text = when (manga?.displayMode) {
|
||||||
when (manga?.displayMode) {
|
|
||||||
Manga.DISPLAY_NUMBER -> {
|
Manga.DISPLAY_NUMBER -> {
|
||||||
val formattedNumber = decimalFormat.format(chapter.chapter_number.toDouble())
|
val formattedNumber = decimalFormat.format(chapter.chapter_number.toDouble())
|
||||||
name = context.getString(R.string.display_mode_chapter, formattedNumber)
|
context.getString(R.string.display_mode_chapter, formattedNumber)
|
||||||
}
|
}
|
||||||
else -> name = chapter.name
|
else -> chapter.name
|
||||||
}
|
}
|
||||||
|
|
||||||
chapter_title.text = name
|
|
||||||
chapter_title.setTextColor(if (chapter.read) readColor else unreadColor)
|
chapter_title.setTextColor(if (chapter.read) readColor else unreadColor)
|
||||||
|
|
||||||
if (chapter.date_upload > 0) {
|
if (chapter.date_upload > 0) {
|
||||||
@ -57,31 +52,26 @@ class ChaptersHolder(
|
|||||||
chapter_date.text = ""
|
chapter_date.text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chapter.read && chapter.last_page_read > 0) {
|
chapter_pages.text = if (!chapter.read && chapter.last_page_read > 0) {
|
||||||
chapter_pages.text = context.getString(R.string.chapter_progress, chapter.last_page_read + 1)
|
context.getString(R.string.chapter_progress, chapter.last_page_read + 1)
|
||||||
} else {
|
} else {
|
||||||
chapter_pages.text = ""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyStatus(chapter.status)
|
notifyStatus(chapter.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyStatus(status: Int) = with(view) {
|
fun notifyStatus(status: Int) = with(view.download_text) {
|
||||||
when (status) {
|
when (status) {
|
||||||
Download.QUEUE -> download_text.setText(R.string.chapter_queued)
|
Download.QUEUE -> setText(R.string.chapter_queued)
|
||||||
Download.DOWNLOADING -> download_text.setText(R.string.chapter_downloading)
|
Download.DOWNLOADING -> setText(R.string.chapter_downloading)
|
||||||
Download.DOWNLOADED -> download_text.setText(R.string.chapter_downloaded)
|
Download.DOWNLOADED -> setText(R.string.chapter_downloaded)
|
||||||
Download.ERROR -> download_text.setText(R.string.chapter_error)
|
Download.ERROR -> setText(R.string.chapter_error)
|
||||||
else -> download_text.text = ""
|
else -> text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onProgressChange(context: Context, downloaded: Int, total: Int) {
|
private fun showPopupMenu(view: View) = item?.let { chapter ->
|
||||||
view.download_text.text = context.getString(
|
|
||||||
R.string.chapter_downloading_progress, downloaded, total)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showPopupMenu(view: View) = item?.let { item ->
|
|
||||||
// Create a PopupMenu, giving it the clicked view for an anchor
|
// Create a PopupMenu, giving it the clicked view for an anchor
|
||||||
val popup = PopupMenu(view.context, view)
|
val popup = PopupMenu(view.context, view)
|
||||||
|
|
||||||
@ -89,32 +79,35 @@ class ChaptersHolder(
|
|||||||
popup.menuInflater.inflate(R.menu.chapter_single, popup.menu)
|
popup.menuInflater.inflate(R.menu.chapter_single, popup.menu)
|
||||||
|
|
||||||
// Hide download and show delete if the chapter is downloaded
|
// Hide download and show delete if the chapter is downloaded
|
||||||
if (item.isDownloaded) {
|
if (chapter.isDownloaded) {
|
||||||
popup.menu.findItem(R.id.action_download).isVisible = false
|
popup.menu.findItem(R.id.action_download).isVisible = false
|
||||||
popup.menu.findItem(R.id.action_delete).isVisible = true
|
popup.menu.findItem(R.id.action_delete).isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide mark as unread when the chapter is unread
|
// Hide mark as unread when the chapter is unread
|
||||||
if (!item.read && item.last_page_read == 0) {
|
if (!chapter.read && chapter.last_page_read == 0) {
|
||||||
popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
|
popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide mark as read when the chapter is read
|
// Hide mark as read when the chapter is read
|
||||||
if (item.read) {
|
if (chapter.read) {
|
||||||
popup.menu.findItem(R.id.action_mark_as_read).isVisible = false
|
popup.menu.findItem(R.id.action_mark_as_read).isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a listener so we are notified if a menu item is clicked
|
// Set a listener so we are notified if a menu item is clicked
|
||||||
popup.setOnMenuItemClickListener { menuItem ->
|
popup.setOnMenuItemClickListener { menuItem ->
|
||||||
val chapter = listOf(item)
|
val chapterList = listOf(chapter)
|
||||||
|
|
||||||
|
with(adapter.fragment) {
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.action_download -> adapter.fragment.downloadChapters(chapter)
|
R.id.action_download -> downloadChapters(chapterList)
|
||||||
R.id.action_delete -> adapter.fragment.deleteChapters(chapter)
|
R.id.action_delete -> deleteChapters(chapterList)
|
||||||
R.id.action_mark_as_read -> adapter.fragment.markAsRead(chapter)
|
R.id.action_mark_as_read -> markAsRead(chapterList)
|
||||||
R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(chapter)
|
R.id.action_mark_as_unread -> markAsUnread(chapterList)
|
||||||
R.id.action_mark_previous_as_read -> adapter.fragment.markPreviousAsRead(item)
|
R.id.action_mark_previous_as_read -> markPreviousAsRead(chapter)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,108 +20,197 @@ import rx.android.schedulers.AndroidSchedulers
|
|||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of [ChaptersFragment].
|
||||||
|
*/
|
||||||
class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
|
class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
|
||||||
|
|
||||||
@Inject lateinit var db: DatabaseHelper
|
/**
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
* Database helper.
|
||||||
@Inject lateinit var preferences: PreferencesHelper
|
*/
|
||||||
@Inject lateinit var downloadManager: DownloadManager
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source manager.
|
||||||
|
*/
|
||||||
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preferences.
|
||||||
|
*/
|
||||||
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads manager.
|
||||||
|
*/
|
||||||
|
val downloadManager: DownloadManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active manga.
|
||||||
|
*/
|
||||||
lateinit var manga: Manga
|
lateinit var manga: Manga
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of the manga.
|
||||||
|
*/
|
||||||
lateinit var source: Source
|
lateinit var source: Source
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var chapters: List<Chapter>
|
/**
|
||||||
|
* List of chapters of the manga. It's always unfiltered and unsorted.
|
||||||
|
*/
|
||||||
|
lateinit var chapters: List<ChapterModel>
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var chaptersSubject: PublishSubject<List<Chapter>>
|
/**
|
||||||
|
* Subject of list of chapters to allow updating the view without going to DB.
|
||||||
|
*/
|
||||||
|
val chaptersSubject by lazy { PublishSubject.create<List<ChapterModel>>() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the chapter list has been requested to the source.
|
||||||
|
*/
|
||||||
|
var hasRequested = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var hasRequested: Boolean = false
|
companion object {
|
||||||
private set
|
/**
|
||||||
|
* Id of the restartable which sends a filtered and ordered list of chapters to the view.
|
||||||
|
*/
|
||||||
|
private const val GET_CHAPTERS = 1
|
||||||
|
|
||||||
private val DB_CHAPTERS = 1
|
/**
|
||||||
private val FETCH_CHAPTERS = 2
|
* Id of the restartable which requests an updated list of chapters to the source.
|
||||||
private val CHAPTER_STATUS_CHANGES = 3
|
*/
|
||||||
|
private const val FETCH_CHAPTERS = 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of the restartable which listens for download status changes.
|
||||||
|
*/
|
||||||
|
private const val CHAPTER_STATUS_CHANGES = 3
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
chaptersSubject = PublishSubject.create()
|
startableLatestCache(GET_CHAPTERS,
|
||||||
|
// On each subject emission, apply filters and sort then update the view.
|
||||||
startableLatestCache(DB_CHAPTERS,
|
{ chaptersSubject
|
||||||
{ getDbChaptersObs() },
|
.flatMap { applyChapterFilters(it) }
|
||||||
|
.observeOn(AndroidSchedulers.mainThread()) },
|
||||||
{ view, chapters -> view.onNextChapters(chapters) })
|
{ view, chapters -> view.onNextChapters(chapters) })
|
||||||
|
|
||||||
startableFirst(FETCH_CHAPTERS,
|
startableFirst(FETCH_CHAPTERS,
|
||||||
{ getOnlineChaptersObs() },
|
{ getRemoteChaptersObservable() },
|
||||||
{ view, result -> view.onFetchChaptersDone() },
|
{ view, result -> view.onFetchChaptersDone() },
|
||||||
{ view, error -> view.onFetchChaptersError(error) })
|
{ view, error -> view.onFetchChaptersError(error) })
|
||||||
|
|
||||||
startableLatestCache(CHAPTER_STATUS_CHANGES,
|
startableLatestCache(CHAPTER_STATUS_CHANGES,
|
||||||
{ getChapterStatusObs() },
|
{ getChapterStatusObservable() },
|
||||||
{ view, download -> view.onChapterStatusChange(download) },
|
{ view, download -> view.onChapterStatusChange(download) },
|
||||||
{ view, error -> Timber.e(error.cause, error.message) })
|
{ view, error -> Timber.e(error.cause, error.message) })
|
||||||
|
|
||||||
|
// Find the active manga from the shared data or return.
|
||||||
manga = SharedData.get(MangaEvent::class.java)?.manga ?: return
|
manga = SharedData.get(MangaEvent::class.java)?.manga ?: return
|
||||||
Observable.just(manga)
|
Observable.just(manga)
|
||||||
.subscribeLatestCache({ view, manga -> view.onNextManga(manga) })
|
.subscribeLatestCache({ view, manga -> view.onNextManga(manga) })
|
||||||
|
|
||||||
|
// Find the source for this manga.
|
||||||
source = sourceManager.get(manga.source)!!
|
source = sourceManager.get(manga.source)!!
|
||||||
start(DB_CHAPTERS)
|
|
||||||
|
|
||||||
|
// Prepare the publish subject.
|
||||||
|
start(GET_CHAPTERS)
|
||||||
|
|
||||||
|
// Add the subscription that retrieves the chapters from the database, keeps subscribed to
|
||||||
|
// changes, and sends the list of chapters to the publish subject.
|
||||||
add(db.getChapters(manga).asRxObservable()
|
add(db.getChapters(manga).asRxObservable()
|
||||||
.doOnNext { chapters ->
|
.map { chapters ->
|
||||||
this.chapters = chapters
|
// Convert every chapter to a model.
|
||||||
SharedData.get(ChapterCountEvent::class.java)?.emit(chapters.size)
|
chapters.map { it.toModel() }
|
||||||
for (chapter in chapters) {
|
|
||||||
setChapterStatus(chapter)
|
|
||||||
}
|
}
|
||||||
|
.doOnNext { chapters ->
|
||||||
|
// Store the last emission
|
||||||
|
this.chapters = chapters
|
||||||
|
|
||||||
|
// Listen for download status changes
|
||||||
start(CHAPTER_STATUS_CHANGES)
|
start(CHAPTER_STATUS_CHANGES)
|
||||||
|
|
||||||
|
// Emit the number of chapters to the info tab.
|
||||||
|
SharedData.get(ChapterCountEvent::class.java)?.emit(chapters.size)
|
||||||
}
|
}
|
||||||
.subscribe { chaptersSubject.onNext(it) })
|
.subscribe { chaptersSubject.onNext(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a chapter from the database to an extended model, allowing to store new fields.
|
||||||
|
*/
|
||||||
|
private fun Chapter.toModel(): ChapterModel {
|
||||||
|
// Create the model object.
|
||||||
|
val model = ChapterModel(this)
|
||||||
|
|
||||||
|
// Find an active download for this chapter.
|
||||||
|
val download = downloadManager.queue.find { it.chapter.id == id }
|
||||||
|
|
||||||
|
if (download != null) {
|
||||||
|
// If there's an active download, assign it.
|
||||||
|
model.download = download
|
||||||
|
} else {
|
||||||
|
// Otherwise ask the manager if the chapter is downloaded and assign it to the status.
|
||||||
|
model.status = if (downloadManager.isChapterDownloaded(source, manga, this))
|
||||||
|
Download.DOWNLOADED
|
||||||
|
else
|
||||||
|
Download.NOT_DOWNLOADED
|
||||||
|
}
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests an updated list of chapters from the source.
|
||||||
|
*/
|
||||||
fun fetchChaptersFromSource() {
|
fun fetchChaptersFromSource() {
|
||||||
hasRequested = true
|
hasRequested = true
|
||||||
start(FETCH_CHAPTERS)
|
start(FETCH_CHAPTERS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the UI after applying the filters.
|
||||||
|
*/
|
||||||
private fun refreshChapters() {
|
private fun refreshChapters() {
|
||||||
chaptersSubject.onNext(chapters)
|
chaptersSubject.onNext(chapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOnlineChaptersObs(): Observable<Pair<Int, Int>> {
|
/**
|
||||||
return source.fetchChapterList(manga)
|
* Returns an observable that updates the chapter list with the latest from the source.
|
||||||
|
*/
|
||||||
|
fun getRemoteChaptersObservable() = source.fetchChapterList(manga)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.map { syncChaptersWithSource(db, it, manga, source) }
|
.map { syncChaptersWithSource(db, it, manga, source) }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
}
|
|
||||||
|
|
||||||
fun getDbChaptersObs(): Observable<List<Chapter>> {
|
/**
|
||||||
return chaptersSubject
|
* Returns an observable that listens to download queue status changes.
|
||||||
.flatMap { applyChapterFilters(it) }
|
*/
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
fun getChapterStatusObservable() = downloadManager.queue.getStatusObservable()
|
||||||
}
|
|
||||||
|
|
||||||
fun getChapterStatusObs(): Observable<Download> {
|
|
||||||
return downloadManager.queue.getStatusObservable()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.filter { download -> download.manga.id == manga.id }
|
.filter { download -> download.manga.id == manga.id }
|
||||||
.doOnNext { updateChapterStatus(it) }
|
.doOnNext { onDownloadStatusChange(it) }
|
||||||
}
|
|
||||||
|
|
||||||
private fun applyChapterFilters(chapters: List<Chapter>): Observable<List<Chapter>> {
|
/**
|
||||||
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
|
*
|
||||||
|
* @param chapters the list of chapters from the database
|
||||||
|
* @return an observable of the list of chapters filtered and sorted.
|
||||||
|
*/
|
||||||
|
private fun applyChapterFilters(chapters: List<ChapterModel>): Observable<List<ChapterModel>> {
|
||||||
var observable = Observable.from(chapters).subscribeOn(Schedulers.io())
|
var observable = Observable.from(chapters).subscribeOn(Schedulers.io())
|
||||||
if (onlyUnread()) {
|
if (onlyUnread()) {
|
||||||
observable = observable.filter { chapter -> !chapter.read }
|
observable = observable.filter { !it.read }
|
||||||
}
|
}
|
||||||
if (onlyDownloaded()) {
|
if (onlyDownloaded()) {
|
||||||
observable = observable.filter { chapter -> chapter.status == Download.DOWNLOADED }
|
observable = observable.filter { it.isDownloaded }
|
||||||
}
|
}
|
||||||
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
|
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
|
||||||
Manga.SORTING_SOURCE -> when (sortDescending()) {
|
Manga.SORTING_SOURCE -> when (sortDescending()) {
|
||||||
@ -137,37 +226,40 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
|
|||||||
return observable.toSortedList(sortFunction)
|
return observable.toSortedList(sortFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setChapterStatus(chapter: Chapter) {
|
/**
|
||||||
for (download in downloadManager.queue) {
|
* Called when a download for the active manga changes status.
|
||||||
if (chapter.id == download.chapter.id) {
|
*
|
||||||
chapter.status = download.status
|
* @param download the download whose status changed.
|
||||||
return
|
*/
|
||||||
|
fun onDownloadStatusChange(download: Download) {
|
||||||
|
// Assign the download to the model object.
|
||||||
|
if (download.status == Download.QUEUE) {
|
||||||
|
chapters.find { it.id == download.chapter.id }?.let {
|
||||||
|
if (it.download == null) {
|
||||||
|
it.download = download
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadManager.isChapterDownloaded(source, manga, chapter)) {
|
// Force UI update if downloaded filter active and download finished.
|
||||||
chapter.status = Download.DOWNLOADED
|
|
||||||
} else {
|
|
||||||
chapter.status = Download.NOT_DOWNLOADED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateChapterStatus(download: Download) {
|
|
||||||
for (chapter in chapters) {
|
|
||||||
if (download.chapter.id == chapter.id) {
|
|
||||||
chapter.status = download.status
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (onlyDownloaded() && download.status == Download.DOWNLOADED)
|
if (onlyDownloaded() && download.status == Download.DOWNLOADED)
|
||||||
refreshChapters()
|
refreshChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNextUnreadChapter(): Chapter? {
|
/**
|
||||||
|
* Returns the next unread chapter or null if everything is read.
|
||||||
|
*/
|
||||||
|
fun getNextUnreadChapter(): ChapterModel? {
|
||||||
return chapters.sortedByDescending { it.source_order }.find { !it.read }
|
return chapters.sortedByDescending { it.source_order }.find { !it.read }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markChaptersRead(selectedChapters: List<Chapter>, read: Boolean) {
|
/**
|
||||||
|
* Mark the selected chapter list as read/unread.
|
||||||
|
*
|
||||||
|
* @param selectedChapters the list of selected chapters.
|
||||||
|
* @param read whether to mark chapters as read or unread.
|
||||||
|
*/
|
||||||
|
fun markChaptersRead(selectedChapters: List<ChapterModel>, read: Boolean) {
|
||||||
Observable.from(selectedChapters)
|
Observable.from(selectedChapters)
|
||||||
.doOnNext { chapter ->
|
.doOnNext { chapter ->
|
||||||
chapter.read = read
|
chapter.read = read
|
||||||
@ -181,21 +273,36 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
|
|||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markPreviousChaptersAsRead(selected: Chapter) {
|
/**
|
||||||
|
* Mark the previous chapters to the selected one as read.
|
||||||
|
*
|
||||||
|
* @param chapter the selected chapter.
|
||||||
|
*/
|
||||||
|
fun markPreviousChaptersAsRead(chapter: ChapterModel) {
|
||||||
Observable.from(chapters)
|
Observable.from(chapters)
|
||||||
.filter { it.isRecognizedNumber && it.chapter_number < selected.chapter_number }
|
.filter { it.isRecognizedNumber && it.chapter_number < chapter.chapter_number }
|
||||||
.doOnNext { it.read = true }
|
.doOnNext { it.read = true }
|
||||||
.toList()
|
.toList()
|
||||||
.flatMap { db.updateChaptersProgress(it).asRxObservable() }
|
.flatMap { db.updateChaptersProgress(it).asRxObservable() }
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadChapters(chapters: List<Chapter>) {
|
/**
|
||||||
|
* Downloads the given list of chapters with the manager.
|
||||||
|
*
|
||||||
|
* @param chapters the list of chapters to download.
|
||||||
|
*/
|
||||||
|
fun downloadChapters(chapters: List<ChapterModel>) {
|
||||||
DownloadService.start(context)
|
DownloadService.start(context)
|
||||||
downloadManager.downloadChapters(manga, chapters)
|
downloadManager.downloadChapters(manga, chapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteChapters(chapters: List<Chapter>) {
|
/**
|
||||||
|
* Deletes the given list of chapter.
|
||||||
|
*
|
||||||
|
* @param chapters the list of chapters to delete.
|
||||||
|
*/
|
||||||
|
fun deleteChapters(chapters: List<ChapterModel>) {
|
||||||
val wasRunning = downloadManager.isRunning
|
val wasRunning = downloadManager.isRunning
|
||||||
if (wasRunning) {
|
if (wasRunning) {
|
||||||
DownloadService.stop(context)
|
DownloadService.stop(context)
|
||||||
@ -216,49 +323,97 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteChapter(chapter: Chapter) {
|
/**
|
||||||
|
* Deletes a chapter from disk. This method is called in a background thread.
|
||||||
|
*
|
||||||
|
* @param chapter the chapter to delete.
|
||||||
|
*/
|
||||||
|
private fun deleteChapter(chapter: ChapterModel) {
|
||||||
downloadManager.queue.del(chapter)
|
downloadManager.queue.del(chapter)
|
||||||
downloadManager.deleteChapter(source, manga, chapter)
|
downloadManager.deleteChapter(source, manga, chapter)
|
||||||
chapter.status = Download.NOT_DOWNLOADED
|
chapter.status = Download.NOT_DOWNLOADED
|
||||||
|
chapter.download = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverses the sorting and requests an UI update.
|
||||||
|
*/
|
||||||
fun revertSortOrder() {
|
fun revertSortOrder() {
|
||||||
manga.setChapterOrder(if (sortDescending()) Manga.SORT_ASC else Manga.SORT_DESC)
|
manga.setChapterOrder(if (sortDescending()) Manga.SORT_ASC else Manga.SORT_DESC)
|
||||||
db.updateFlags(manga).executeAsBlocking()
|
db.updateFlags(manga).executeAsBlocking()
|
||||||
refreshChapters()
|
refreshChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the read filter and requests an UI update.
|
||||||
|
*
|
||||||
|
* @param onlyUnread whether to display only unread chapters or all chapters.
|
||||||
|
*/
|
||||||
fun setReadFilter(onlyUnread: Boolean) {
|
fun setReadFilter(onlyUnread: Boolean) {
|
||||||
manga.readFilter = if (onlyUnread) Manga.SHOW_UNREAD else Manga.SHOW_ALL
|
manga.readFilter = if (onlyUnread) Manga.SHOW_UNREAD else Manga.SHOW_ALL
|
||||||
db.updateFlags(manga).executeAsBlocking()
|
db.updateFlags(manga).executeAsBlocking()
|
||||||
refreshChapters()
|
refreshChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the download filter and requests an UI update.
|
||||||
|
*
|
||||||
|
* @param onlyDownloaded whether to display only downloaded chapters or all chapters.
|
||||||
|
*/
|
||||||
fun setDownloadedFilter(onlyDownloaded: Boolean) {
|
fun setDownloadedFilter(onlyDownloaded: Boolean) {
|
||||||
manga.downloadedFilter = if (onlyDownloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL
|
manga.downloadedFilter = if (onlyDownloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL
|
||||||
db.updateFlags(manga).executeAsBlocking()
|
db.updateFlags(manga).executeAsBlocking()
|
||||||
refreshChapters()
|
refreshChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all filters and requests an UI update.
|
||||||
|
*/
|
||||||
|
fun removeFilters() {
|
||||||
|
manga.readFilter = Manga.SHOW_ALL
|
||||||
|
manga.downloadedFilter = Manga.SHOW_ALL
|
||||||
|
db.updateFlags(manga).executeAsBlocking()
|
||||||
|
refreshChapters()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the active display mode.
|
||||||
|
*
|
||||||
|
* @param mode the mode to set.
|
||||||
|
*/
|
||||||
fun setDisplayMode(mode: Int) {
|
fun setDisplayMode(mode: Int) {
|
||||||
manga.displayMode = mode
|
manga.displayMode = mode
|
||||||
db.updateFlags(manga).executeAsBlocking()
|
db.updateFlags(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSorting(mode: Int) {
|
/**
|
||||||
manga.sorting = mode
|
* Sets the sorting method and requests an UI update.
|
||||||
|
*
|
||||||
|
* @param sort the sorting mode.
|
||||||
|
*/
|
||||||
|
fun setSorting(sort: Int) {
|
||||||
|
manga.sorting = sort
|
||||||
db.updateFlags(manga).executeAsBlocking()
|
db.updateFlags(manga).executeAsBlocking()
|
||||||
refreshChapters()
|
refreshChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the display only downloaded filter is enabled.
|
||||||
|
*/
|
||||||
fun onlyDownloaded(): Boolean {
|
fun onlyDownloaded(): Boolean {
|
||||||
return manga.downloadedFilter == Manga.SHOW_DOWNLOADED
|
return manga.downloadedFilter == Manga.SHOW_DOWNLOADED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the display only unread filter is enabled.
|
||||||
|
*/
|
||||||
fun onlyUnread(): Boolean {
|
fun onlyUnread(): Boolean {
|
||||||
return manga.readFilter == Manga.SHOW_UNREAD
|
return manga.readFilter == Manga.SHOW_UNREAD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the sorting method is descending or ascending.
|
||||||
|
*/
|
||||||
fun sortDescending(): Boolean {
|
fun sortDescending(): Boolean {
|
||||||
return manga.sortDescending()
|
return manga.sortDescending()
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,12 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
|
|||||||
manga_genres.text = manga.genre
|
manga_genres.text = manga.genre
|
||||||
|
|
||||||
// Update status TextView.
|
// Update status TextView.
|
||||||
manga_status.text = manga.getStatus(activity)
|
manga_status.setText(when (manga.status) {
|
||||||
|
Manga.ONGOING -> R.string.ongoing
|
||||||
|
Manga.COMPLETED -> R.string.completed
|
||||||
|
Manga.LICENSED -> R.string.licensed
|
||||||
|
else -> R.string.unknown
|
||||||
|
})
|
||||||
|
|
||||||
// Update description TextView.
|
// Update description TextView.
|
||||||
manga_summary.text = manga.description
|
manga_summary.text = manga.description
|
||||||
|
@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.util.SharedData
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of MangaInfoFragment.
|
* Presenter of MangaInfoFragment.
|
||||||
@ -36,17 +36,17 @@ class MangaInfoPresenter : BasePresenter<MangaInfoFragment>() {
|
|||||||
/**
|
/**
|
||||||
* Used to connect to database.
|
* Used to connect to database.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to connect to different manga sources.
|
* Used to connect to different manga sources.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to connect to cache.
|
* Used to connect to cache.
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var coverCache: CoverCache
|
val coverCache: CoverCache by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the restartable.
|
* The id of the restartable.
|
||||||
|
@ -15,12 +15,12 @@ import rx.Observable
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
|
class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
|
||||||
|
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
@Inject lateinit var syncManager: MangaSyncManager
|
val syncManager: MangaSyncManager by injectLazy()
|
||||||
|
|
||||||
val myAnimeList by lazy { syncManager.myAnimeList }
|
val myAnimeList by lazy { syncManager.myAnimeList }
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
|
|||||||
|
|
||||||
fun registerManga(sync: MangaSync?) {
|
fun registerManga(sync: MangaSync?) {
|
||||||
if (sync != null) {
|
if (sync != null) {
|
||||||
sync.manga_id = manga.id
|
sync.manga_id = manga.id!!
|
||||||
add(myAnimeList.bind(sync)
|
add(myAnimeList.bind(sync)
|
||||||
.flatMap { db.insertMangaSync(sync).asRxObservable() }
|
.flatMap { db.insertMangaSync(sync).asRxObservable() }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
138
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt
Normal file
138
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.source.Source
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.util.plusAssign
|
||||||
|
import rx.Observable
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import rx.subscriptions.CompositeSubscription
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
class ChapterLoader(
|
||||||
|
private val downloadManager: DownloadManager,
|
||||||
|
private val manga: Manga,
|
||||||
|
private val source: Source
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val queue = PriorityBlockingQueue<PriorityPage>()
|
||||||
|
private val subscriptions = CompositeSubscription()
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
prepareOnlineReading()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restart() {
|
||||||
|
cleanup()
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanup() {
|
||||||
|
subscriptions.clear()
|
||||||
|
queue.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareOnlineReading() {
|
||||||
|
subscriptions += Observable.defer { Observable.just(queue.take().page) }
|
||||||
|
.filter { it.status == Page.QUEUE }
|
||||||
|
.concatMap { source.fetchImage(it) }
|
||||||
|
.repeat()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe({
|
||||||
|
}, {
|
||||||
|
if (it !is InterruptedException) {
|
||||||
|
Timber.e(it, it.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadChapter(chapter: ReaderChapter) = Observable.just(chapter)
|
||||||
|
.flatMap {
|
||||||
|
if (chapter.pages == null)
|
||||||
|
retrievePageList(chapter)
|
||||||
|
else
|
||||||
|
Observable.just(chapter.pages!!)
|
||||||
|
}
|
||||||
|
.doOnNext { pages ->
|
||||||
|
// Now that the number of pages is known, fix the requested page if the last one
|
||||||
|
// was requested.
|
||||||
|
if (chapter.requestedPage == -1) {
|
||||||
|
chapter.requestedPage = pages.lastIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPages(chapter)
|
||||||
|
}
|
||||||
|
.map { chapter }
|
||||||
|
|
||||||
|
private fun retrievePageList(chapter: ReaderChapter) = Observable.just(chapter)
|
||||||
|
.flatMap {
|
||||||
|
// Check if the chapter is downloaded.
|
||||||
|
chapter.isDownloaded = downloadManager.isChapterDownloaded(source, manga, chapter)
|
||||||
|
|
||||||
|
// Fetch the page list from disk.
|
||||||
|
if (chapter.isDownloaded)
|
||||||
|
Observable.just(downloadManager.getSavedPageList(source, manga, chapter)!!)
|
||||||
|
// Fetch the page list from cache or fallback to network
|
||||||
|
else
|
||||||
|
source.fetchPageList(chapter)
|
||||||
|
}
|
||||||
|
.doOnNext { pages ->
|
||||||
|
chapter.pages = pages
|
||||||
|
pages.forEach { it.chapter = chapter }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadPages(chapter: ReaderChapter) {
|
||||||
|
if (chapter.isDownloaded) {
|
||||||
|
loadDownloadedPages(chapter)
|
||||||
|
} else {
|
||||||
|
loadOnlinePages(chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadDownloadedPages(chapter: ReaderChapter) {
|
||||||
|
val chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter)
|
||||||
|
subscriptions += Observable.from(chapter.pages!!)
|
||||||
|
.flatMap { downloadManager.getDownloadedImage(it, chapterDir) }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadOnlinePages(chapter: ReaderChapter) {
|
||||||
|
chapter.pages?.let { pages ->
|
||||||
|
val startPage = chapter.requestedPage
|
||||||
|
val pagesToLoad = if (startPage == 0)
|
||||||
|
pages
|
||||||
|
else
|
||||||
|
pages.drop(startPage)
|
||||||
|
|
||||||
|
pagesToLoad.forEach { queue.offer(PriorityPage(it, 0)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadPriorizedPage(page: Page) {
|
||||||
|
queue.offer(PriorityPage(page, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retryPage(page: Page) {
|
||||||
|
queue.offer(PriorityPage(page, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class PriorityPage(val page: Page, val priority: Int): Comparable<PriorityPage> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val idGenerator = AtomicInteger()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val identifier = idGenerator.incrementAndGet()
|
||||||
|
|
||||||
|
override fun compareTo(other: PriorityPage): Int {
|
||||||
|
val p = other.priority.compareTo(priority)
|
||||||
|
return if (p != 0) p else identifier.compareTo(other.identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -20,7 +20,6 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
||||||
@ -116,16 +115,6 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
setSystemUiVisibility()
|
setSystemUiVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
viewer?.let {
|
|
||||||
val activePage = it.getActivePage()
|
|
||||||
if (activePage != null) {
|
|
||||||
presenter.currentPage = activePage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
subscriptions.unsubscribe()
|
subscriptions.unsubscribe()
|
||||||
popupMenu?.dismiss()
|
popupMenu?.dismiss()
|
||||||
@ -230,6 +219,9 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the presenter at startup, allowing to prepare the selected reader.
|
||||||
|
*/
|
||||||
fun onMangaOpen(manga: Manga) {
|
fun onMangaOpen(manga: Manga) {
|
||||||
if (viewer == null) {
|
if (viewer == null) {
|
||||||
viewer = getOrCreateViewer(manga)
|
viewer = getOrCreateViewer(manga)
|
||||||
@ -243,22 +235,23 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
please_wait.startAnimation(AnimationUtils.loadAnimation(this, R.anim.fade_in_long))
|
please_wait.startAnimation(AnimationUtils.loadAnimation(this, R.anim.fade_in_long))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onChapterReady(manga: Manga, chapter: Chapter, currentPage: Page?) {
|
fun onChapterReady(chapter: ReaderChapter) {
|
||||||
please_wait.visibility = View.GONE
|
please_wait.visibility = View.GONE
|
||||||
val activePage = currentPage ?: chapter.pages.last()
|
val pages = chapter.pages ?: run { onChapterError(Exception("Null pages")); return }
|
||||||
|
val activePage = pages.getOrElse(chapter.requestedPage) { pages.first() }
|
||||||
|
|
||||||
viewer?.onPageListReady(chapter, activePage)
|
viewer?.onPageListReady(chapter, activePage)
|
||||||
setActiveChapter(chapter, activePage.pageNumber)
|
setActiveChapter(chapter, activePage.pageNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEnterChapter(chapter: Chapter, currentPage: Int) {
|
fun onEnterChapter(chapter: ReaderChapter, currentPage: Int) {
|
||||||
val activePage = if (currentPage == -1) chapter.pages.lastIndex else currentPage
|
val activePage = if (currentPage == -1) chapter.pages!!.lastIndex else currentPage
|
||||||
presenter.setActiveChapter(chapter)
|
presenter.setActiveChapter(chapter)
|
||||||
setActiveChapter(chapter, activePage)
|
setActiveChapter(chapter, activePage)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setActiveChapter(chapter: Chapter, currentPage: Int) {
|
fun setActiveChapter(chapter: ReaderChapter, currentPage: Int) {
|
||||||
val numPages = chapter.pages.size
|
val numPages = chapter.pages!!.size
|
||||||
if (page_seekbar.rotation != 180f) {
|
if (page_seekbar.rotation != 180f) {
|
||||||
right_page_text.text = "$numPages"
|
right_page_text.text = "$numPages"
|
||||||
left_page_text.text = "${currentPage + 1}"
|
left_page_text.text = "${currentPage + 1}"
|
||||||
@ -275,7 +268,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
chapter.name)
|
chapter.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAppendChapter(chapter: Chapter) {
|
fun onAppendChapter(chapter: ReaderChapter) {
|
||||||
viewer?.onPageListAppendReady(chapter)
|
viewer?.onPageListAppendReady(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +317,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
viewer?.let {
|
viewer?.let {
|
||||||
val activePage = it.getActivePage()
|
val activePage = it.getActivePage()
|
||||||
if (activePage != null) {
|
if (activePage != null) {
|
||||||
val requestedPage = activePage.chapter.pages[pageIndex]
|
val requestedPage = activePage.chapter.pages!![pageIndex]
|
||||||
it.setActivePage(requestedPage)
|
it.setActivePage(requestedPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
|
||||||
|
class ReaderChapter(c: Chapter) : Chapter by c {
|
||||||
|
|
||||||
|
@Transient var pages: List<Page>? = null
|
||||||
|
|
||||||
|
var isDownloaded: Boolean = false
|
||||||
|
|
||||||
|
var requestedPage: Int = 0
|
||||||
|
}
|
@ -8,66 +8,127 @@ import eu.kanade.tachiyomi.data.database.models.History
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
|
||||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
||||||
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
|
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.source.Source
|
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.util.RetryWithDelay
|
||||||
import eu.kanade.tachiyomi.util.SharedData
|
import eu.kanade.tachiyomi.util.SharedData
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import rx.subjects.PublishSubject
|
import uy.kohesive.injekt.injectLazy
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of [ReaderActivity].
|
||||||
|
*/
|
||||||
class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||||
|
|
||||||
@Inject lateinit var prefs: PreferencesHelper
|
/**
|
||||||
@Inject lateinit var db: DatabaseHelper
|
* Preferences.
|
||||||
@Inject lateinit var downloadManager: DownloadManager
|
*/
|
||||||
@Inject lateinit var syncManager: MangaSyncManager
|
val prefs: PreferencesHelper by injectLazy()
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
|
||||||
@Inject lateinit var chapterCache: ChapterCache
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database.
|
||||||
|
*/
|
||||||
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download manager.
|
||||||
|
*/
|
||||||
|
val downloadManager: DownloadManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync manager.
|
||||||
|
*/
|
||||||
|
val syncManager: MangaSyncManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source manager.
|
||||||
|
*/
|
||||||
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chapter cache.
|
||||||
|
*/
|
||||||
|
val chapterCache: ChapterCache by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manga being read.
|
||||||
|
*/
|
||||||
lateinit var manga: Manga
|
lateinit var manga: Manga
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var chapter: Chapter
|
/**
|
||||||
|
* Active chapter.
|
||||||
|
*/
|
||||||
|
lateinit var chapter: ReaderChapter
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var source: Source
|
/**
|
||||||
private set
|
* Previous chapter of the active.
|
||||||
|
*/
|
||||||
|
private var prevChapter: ReaderChapter? = null
|
||||||
|
|
||||||
var requestedPage: Int = 0
|
/**
|
||||||
var currentPage: Page? = null
|
* Next chapter of the active.
|
||||||
private var nextChapter: Chapter? = null
|
*/
|
||||||
private var previousChapter: Chapter? = null
|
private var nextChapter: ReaderChapter? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of the manga.
|
||||||
|
*/
|
||||||
|
private val source by lazy { sourceManager.get(manga.source)!! }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chapter list for the active manga. It's retrieved lazily and should be accessed for the first
|
||||||
|
* time in a background thread to avoid blocking the UI.
|
||||||
|
*/
|
||||||
|
private val chapterList by lazy {
|
||||||
|
val dbChapters = db.getChapters(manga).executeAsBlocking().map { it.toModel() }
|
||||||
|
|
||||||
|
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
|
||||||
|
Manga.SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
|
||||||
|
Manga.SORTING_NUMBER -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
|
||||||
|
else -> throw NotImplementedError("Unknown sorting method")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbChapters.sortedWith(Comparator<Chapter> { c1, c2 -> sortFunction(c1, c2) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of manga services linked to the active manga, or null if auto syncing is not enabled.
|
||||||
|
*/
|
||||||
private var mangaSyncList: List<MangaSync>? = null
|
private var mangaSyncList: List<MangaSync>? = null
|
||||||
|
|
||||||
private val retryPageSubject by lazy { PublishSubject.create<Page>() }
|
/**
|
||||||
private val pageInitializerSubject by lazy { PublishSubject.create<Chapter>() }
|
* Chapter loader whose job is to obtain the chapter list and initialize every page.
|
||||||
|
*/
|
||||||
val isSeamlessMode by lazy { prefs.seamlessMode() }
|
private val loader by lazy { ChapterLoader(downloadManager, manga, source) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription for appending a chapter to the reader (seamless mode).
|
||||||
|
*/
|
||||||
private var appenderSubscription: Subscription? = null
|
private var appenderSubscription: Subscription? = null
|
||||||
|
|
||||||
private val PREPARE_READER = 1
|
/**
|
||||||
private val GET_PAGE_LIST = 2
|
* Subscription for retrieving the adjacent chapters to the current one.
|
||||||
private val GET_ADJACENT_CHAPTERS = 3
|
*/
|
||||||
private val GET_MANGA_SYNC = 4
|
private var adjacentChaptersSubscription: Subscription? = null
|
||||||
private val PRELOAD_NEXT_CHAPTER = 5
|
|
||||||
|
|
||||||
private val MANGA_KEY = "manga_key"
|
companion object {
|
||||||
private val CHAPTER_KEY = "chapter_key"
|
/**
|
||||||
private val PAGE_KEY = "page_key"
|
* Id of the restartable that loads the active chapter.
|
||||||
|
*/
|
||||||
|
private const val LOAD_ACTIVE_CHAPTER = 1
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
@ -75,306 +136,287 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
if (savedState == null) {
|
if (savedState == null) {
|
||||||
val event = SharedData.get(ReaderEvent::class.java) ?: return
|
val event = SharedData.get(ReaderEvent::class.java) ?: return
|
||||||
manga = event.manga
|
manga = event.manga
|
||||||
chapter = event.chapter
|
chapter = event.chapter.toModel()
|
||||||
} else {
|
} else {
|
||||||
manga = savedState.getSerializable(MANGA_KEY) as Manga
|
manga = savedState.getSerializable(ReaderPresenter::manga.name) as Manga
|
||||||
chapter = savedState.getSerializable(CHAPTER_KEY) as Chapter
|
chapter = savedState.getSerializable(ReaderPresenter::chapter.name) as ReaderChapter
|
||||||
requestedPage = savedState.getInt(PAGE_KEY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
source = sourceManager.get(manga.source)!!
|
// Send the active manga to the view to initialize the reader.
|
||||||
|
Observable.just(manga)
|
||||||
|
.subscribeLatestCache({ view, manga -> view.onMangaOpen(manga) })
|
||||||
|
|
||||||
initializeSubjects()
|
// Retrieve the sync list if auto syncing is enabled.
|
||||||
|
if (prefs.autoUpdateMangaSync()) {
|
||||||
|
add(db.getMangasSync(manga).asRxSingle()
|
||||||
|
.subscribe({ mangaSyncList = it }))
|
||||||
|
}
|
||||||
|
|
||||||
restartableLatestCache(PREPARE_READER,
|
restartableLatestCache(LOAD_ACTIVE_CHAPTER,
|
||||||
{ Observable.just(manga) },
|
{ loadChapterObservable(chapter) },
|
||||||
{ view, manga -> view.onMangaOpen(manga) })
|
{ view, chapter -> view.onChapterReady(this.chapter) },
|
||||||
|
|
||||||
startableLatestCache(GET_ADJACENT_CHAPTERS,
|
|
||||||
{ getAdjacentChaptersObservable() },
|
|
||||||
{ view, pair -> view.onAdjacentChapters(pair.first, pair.second) })
|
|
||||||
|
|
||||||
startable(PRELOAD_NEXT_CHAPTER,
|
|
||||||
{ getPreloadNextChapterObservable() },
|
|
||||||
{ },
|
|
||||||
{ error -> Timber.e("Error preloading chapter") })
|
|
||||||
|
|
||||||
|
|
||||||
restartable(GET_MANGA_SYNC,
|
|
||||||
{ getMangaSyncObservable().subscribe() })
|
|
||||||
|
|
||||||
restartableLatestCache(GET_PAGE_LIST,
|
|
||||||
{ getPageListObservable(chapter) },
|
|
||||||
{ view, chapter -> view.onChapterReady(manga, this.chapter, currentPage) },
|
|
||||||
{ view, error -> view.onChapterError(error) })
|
{ view, error -> view.onChapterError(error) })
|
||||||
|
|
||||||
if (savedState == null) {
|
if (savedState == null) {
|
||||||
start(PREPARE_READER)
|
|
||||||
loadChapter(chapter)
|
loadChapter(chapter)
|
||||||
if (prefs.autoUpdateMangaSync()) {
|
|
||||||
start(GET_MANGA_SYNC)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSave(state: Bundle) {
|
override fun onSave(state: Bundle) {
|
||||||
|
chapter.requestedPage = chapter.last_page_read
|
||||||
onChapterLeft()
|
onChapterLeft()
|
||||||
state.putSerializable(MANGA_KEY, manga)
|
state.putSerializable(ReaderPresenter::manga.name, manga)
|
||||||
state.putSerializable(CHAPTER_KEY, chapter)
|
state.putSerializable(ReaderPresenter::chapter.name, chapter)
|
||||||
state.putSerializable(PAGE_KEY, currentPage?.pageNumber ?: 0)
|
|
||||||
super.onSave(state)
|
super.onSave(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeSubjects() {
|
override fun onDestroy() {
|
||||||
// Listen for pages initialization events
|
loader.cleanup()
|
||||||
add(pageInitializerSubject.observeOn(Schedulers.io())
|
super.onDestroy()
|
||||||
.concatMap { ch ->
|
|
||||||
val observable: Observable<Page>
|
|
||||||
if (ch.isDownloaded) {
|
|
||||||
val chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, ch)
|
|
||||||
observable = Observable.from(ch.pages)
|
|
||||||
.flatMap { downloadManager.getDownloadedImage(it, chapterDir) }
|
|
||||||
} else {
|
|
||||||
observable = source.let { source ->
|
|
||||||
if (source is OnlineSource) {
|
|
||||||
source.fetchAllImageUrlsFromPageList(ch.pages)
|
|
||||||
.flatMap({ source.getCachedImage(it) }, 2)
|
|
||||||
.doOnCompleted { source.savePageList(ch, ch.pages) }
|
|
||||||
} else {
|
|
||||||
Observable.from(ch.pages)
|
|
||||||
.flatMap { source.fetchImage(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
observable.doOnCompleted {
|
|
||||||
if (!isSeamlessMode && chapter === ch) {
|
|
||||||
preloadNextChapter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.subscribe())
|
|
||||||
|
|
||||||
// Listen por retry events
|
|
||||||
add(retryPageSubject.observeOn(Schedulers.io())
|
|
||||||
.flatMap { source.fetchImage(it) }
|
|
||||||
.subscribe())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the page list of a chapter
|
/**
|
||||||
private fun getPageListObservable(chapter: Chapter): Observable<Chapter> {
|
* Converts a chapter to a [ReaderChapter] if needed.
|
||||||
val observable: Observable<List<Page>> = if (chapter.isDownloaded)
|
*/
|
||||||
// Fetch the page list from disk
|
private fun Chapter.toModel(): ReaderChapter {
|
||||||
Observable.just(downloadManager.getSavedPageList(source, manga, chapter)!!)
|
if (this is ReaderChapter) return this
|
||||||
else
|
return ReaderChapter(this)
|
||||||
// Fetch the page list from cache or fallback to network
|
}
|
||||||
source.fetchPageList(chapter)
|
|
||||||
|
/**
|
||||||
|
* Returns an observable that loads the given chapter, discarding any previous work.
|
||||||
|
*
|
||||||
|
* @param chapter the now active chapter.
|
||||||
|
*/
|
||||||
|
private fun loadChapterObservable(chapter: ReaderChapter): Observable<ReaderChapter> {
|
||||||
|
loader.restart()
|
||||||
|
return loader.loadChapter(chapter)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
return observable.map { pages ->
|
|
||||||
for (page in pages) {
|
|
||||||
page.chapter = chapter
|
|
||||||
}
|
|
||||||
chapter.pages = pages
|
|
||||||
if (requestedPage >= -1 || currentPage == null) {
|
|
||||||
if (requestedPage == -1) {
|
|
||||||
currentPage = pages[pages.size - 1]
|
|
||||||
} else {
|
|
||||||
currentPage = pages[requestedPage]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestedPage = -2
|
|
||||||
pageInitializerSubject.onNext(chapter)
|
|
||||||
chapter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAdjacentChaptersObservable(): Observable<Pair<Chapter, Chapter>> {
|
/**
|
||||||
val strategy = getAdjacentChaptersStrategy()
|
* Obtains the adjacent chapters of the given one in a background thread, and notifies the view
|
||||||
return Observable.zip(strategy.first, strategy.second) { prev, next -> Pair(prev, next) }
|
* when they are known.
|
||||||
|
*
|
||||||
|
* @param chapter the current active chapter.
|
||||||
|
*/
|
||||||
|
private fun getAdjacentChapters(chapter: ReaderChapter) {
|
||||||
|
// Keep only one subscription
|
||||||
|
adjacentChaptersSubscription?.let { remove(it) }
|
||||||
|
|
||||||
|
adjacentChaptersSubscription = Observable
|
||||||
|
.fromCallable { getAdjacentChaptersStrategy(chapter) }
|
||||||
.doOnNext { pair ->
|
.doOnNext { pair ->
|
||||||
previousChapter = pair.first
|
prevChapter = pair.first
|
||||||
nextChapter = pair.second
|
nextChapter = pair.second
|
||||||
}
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAdjacentChaptersStrategy() = when (manga.sorting) {
|
|
||||||
Manga.SORTING_NUMBER -> Pair(
|
|
||||||
db.getPreviousChapter(chapter).asRxObservable().take(1),
|
|
||||||
db.getNextChapter(chapter).asRxObservable().take(1))
|
|
||||||
Manga.SORTING_SOURCE -> Pair(
|
|
||||||
db.getPreviousChapterBySource(chapter).asRxObservable().take(1),
|
|
||||||
db.getNextChapterBySource(chapter).asRxObservable().take(1))
|
|
||||||
else -> throw AssertionError("Unknown sorting method")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preload the first pages of the next chapter. Only for non seamless mode
|
|
||||||
private fun getPreloadNextChapterObservable(): Observable<Page> {
|
|
||||||
val nextChapter = nextChapter ?: return Observable.error(Exception("No next chapter"))
|
|
||||||
return source.fetchPageList(nextChapter)
|
|
||||||
.flatMap { pages ->
|
|
||||||
nextChapter.pages = pages
|
|
||||||
val pagesToPreload = Math.min(pages.size, 5)
|
|
||||||
Observable.from(pages).take(pagesToPreload)
|
|
||||||
}
|
|
||||||
// Preload up to 5 images
|
|
||||||
.concatMap { source.fetchImage(it) }
|
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnCompleted { stopPreloadingNextChapter() }
|
.subscribeLatestCache({ view, pair ->
|
||||||
|
view.onAdjacentChapters(pair.first, pair.second)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMangaSyncObservable(): Observable<List<MangaSync>> {
|
/**
|
||||||
return db.getMangasSync(manga).asRxObservable()
|
* Returns the previous and next chapters of the given one in a [Pair] according to the sorting
|
||||||
.take(1)
|
* strategy set for the manga.
|
||||||
.doOnNext { mangaSyncList = it }
|
*
|
||||||
|
* @param chapter the current active chapter.
|
||||||
|
*/
|
||||||
|
private fun getAdjacentChaptersStrategy(chapter: ReaderChapter) = when (manga.sorting) {
|
||||||
|
Manga.SORTING_SOURCE -> {
|
||||||
|
val currChapterIndex = chapterList.indexOfFirst { chapter.id == it.id }
|
||||||
|
val nextChapter = chapterList.getOrNull(currChapterIndex + 1)
|
||||||
|
val prevChapter = chapterList.getOrNull(currChapterIndex - 1)
|
||||||
|
Pair(prevChapter, nextChapter)
|
||||||
|
}
|
||||||
|
Manga.SORTING_NUMBER -> {
|
||||||
|
val currChapterIndex = chapterList.indexOfFirst { chapter.id == it.id }
|
||||||
|
val chapterNumber = chapter.chapter_number
|
||||||
|
|
||||||
|
var prevChapter: ReaderChapter? = null
|
||||||
|
for (i in (currChapterIndex - 1) downTo 0) {
|
||||||
|
val c = chapterList[i]
|
||||||
|
if (c.chapter_number < chapterNumber && c.chapter_number >= chapterNumber - 1) {
|
||||||
|
prevChapter = c
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads the given chapter
|
var nextChapter: ReaderChapter? = null
|
||||||
private fun loadChapter(chapter: Chapter, requestedPage: Int = 0) {
|
for (i in (currChapterIndex + 1) until chapterList.size) {
|
||||||
if (isSeamlessMode) {
|
val c = chapterList[i]
|
||||||
if (appenderSubscription != null)
|
if (c.chapter_number > chapterNumber && c.chapter_number <= chapterNumber + 1) {
|
||||||
remove(appenderSubscription)
|
nextChapter = c
|
||||||
} else {
|
break
|
||||||
stopPreloadingNextChapter()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Pair(prevChapter, nextChapter)
|
||||||
|
}
|
||||||
|
else -> throw NotImplementedError("Unknown sorting method")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the given chapter and sets it as the active one. This method also accepts a requested
|
||||||
|
* page, which will be set as active when it's displayed in the view.
|
||||||
|
*
|
||||||
|
* @param chapter the chapter to load.
|
||||||
|
* @param requestedPage the requested page from the view.
|
||||||
|
*/
|
||||||
|
private fun loadChapter(chapter: ReaderChapter, requestedPage: Int = 0) {
|
||||||
|
// Cleanup any append.
|
||||||
|
appenderSubscription?.let { remove(it) }
|
||||||
|
|
||||||
this.chapter = chapter
|
this.chapter = chapter
|
||||||
chapter.status = if (isChapterDownloaded(chapter)) Download.DOWNLOADED else Download.NOT_DOWNLOADED
|
|
||||||
|
|
||||||
// If the chapter is partially read, set the starting page to the last the user read
|
// If the chapter is partially read, set the starting page to the last the user read
|
||||||
if (!chapter.read && chapter.last_page_read != 0)
|
// otherwise use the requested page.
|
||||||
this.requestedPage = chapter.last_page_read
|
chapter.requestedPage = if (!chapter.read) chapter.last_page_read else requestedPage
|
||||||
else
|
|
||||||
this.requestedPage = requestedPage
|
|
||||||
|
|
||||||
// Reset next and previous chapter. They have to be fetched again
|
// Reset next and previous chapter. They have to be fetched again
|
||||||
nextChapter = null
|
nextChapter = null
|
||||||
previousChapter = null
|
prevChapter = null
|
||||||
|
|
||||||
start(GET_PAGE_LIST)
|
start(LOAD_ACTIVE_CHAPTER)
|
||||||
start(GET_ADJACENT_CHAPTERS)
|
getAdjacentChapters(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setActiveChapter(chapter: Chapter) {
|
/**
|
||||||
|
* Changes the active chapter, but doesn't load anything. Called when changing chapters from
|
||||||
|
* the reader with the seamless mode.
|
||||||
|
*
|
||||||
|
* @param chapter the chapter to set as active.
|
||||||
|
*/
|
||||||
|
fun setActiveChapter(chapter: ReaderChapter) {
|
||||||
onChapterLeft()
|
onChapterLeft()
|
||||||
this.chapter = chapter
|
this.chapter = chapter
|
||||||
nextChapter = null
|
nextChapter = null
|
||||||
previousChapter = null
|
prevChapter = null
|
||||||
start(GET_ADJACENT_CHAPTERS)
|
getAdjacentChapters(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the next chapter to the reader, if possible.
|
||||||
|
*/
|
||||||
fun appendNextChapter() {
|
fun appendNextChapter() {
|
||||||
if (nextChapter == null)
|
appenderSubscription?.let { remove(it) }
|
||||||
return
|
|
||||||
|
|
||||||
if (appenderSubscription != null)
|
val nextChapter = nextChapter ?: return
|
||||||
remove(appenderSubscription)
|
|
||||||
|
|
||||||
nextChapter?.let {
|
appenderSubscription = loader.loadChapter(nextChapter)
|
||||||
if (appenderSubscription != null)
|
.subscribeOn(Schedulers.io())
|
||||||
remove(appenderSubscription)
|
.retryWhen(RetryWithDelay(1, { 3000 }))
|
||||||
|
|
||||||
it.status = if (isChapterDownloaded(it)) Download.DOWNLOADED else Download.NOT_DOWNLOADED
|
|
||||||
|
|
||||||
appenderSubscription = getPageListObservable(it).subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.compose(deliverLatestCache<Chapter>())
|
.subscribeLatestCache({ view, chapter ->
|
||||||
.subscribe(split({ view, chapter ->
|
|
||||||
view.onAppendChapter(chapter)
|
view.onAppendChapter(chapter)
|
||||||
}, { view, error ->
|
}, { view, error ->
|
||||||
view.onChapterAppendError()
|
view.onChapterAppendError()
|
||||||
}))
|
})
|
||||||
|
|
||||||
add(appenderSubscription)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check whether the given chapter is downloaded
|
|
||||||
fun isChapterDownloaded(chapter: Chapter): Boolean {
|
|
||||||
return downloadManager.isChapterDownloaded(source, manga, chapter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries a page that failed to load due to network error or corruption.
|
||||||
|
*
|
||||||
|
* @param page the page that failed.
|
||||||
|
*/
|
||||||
fun retryPage(page: Page?) {
|
fun retryPage(page: Page?) {
|
||||||
if (page != null) {
|
if (page != null && source is OnlineSource) {
|
||||||
page.status = Page.QUEUE
|
page.status = Page.QUEUE
|
||||||
if (page.imagePath != null) {
|
if (page.imagePath != null) {
|
||||||
val file = File(page.imagePath)
|
val file = File(page.imagePath)
|
||||||
chapterCache.removeFileFromCache(file.name)
|
chapterCache.removeFileFromCache(file.name)
|
||||||
}
|
}
|
||||||
retryPageSubject.onNext(page)
|
loader.retryPage(page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called before loading another chapter or leaving the reader. It allows to do operations
|
/**
|
||||||
// over the chapter read like saving progress
|
* Called before loading another chapter or leaving the reader. It allows to do operations
|
||||||
|
* over the chapter read like saving progress
|
||||||
|
*/
|
||||||
fun onChapterLeft() {
|
fun onChapterLeft() {
|
||||||
val pages = chapter.pages ?: return
|
val pages = chapter.pages ?: return
|
||||||
|
|
||||||
// Get the last page read
|
// Reference these locally because they are needed later from another thread.
|
||||||
var activePageNumber = chapter.last_page_read
|
val chapter = chapter
|
||||||
|
val prevChapter = prevChapter
|
||||||
|
|
||||||
// Just in case, avoid out of index exceptions
|
Observable
|
||||||
if (activePageNumber >= pages.size) {
|
.fromCallable {
|
||||||
activePageNumber = pages.size - 1
|
|
||||||
}
|
|
||||||
val activePage = pages[activePageNumber]
|
|
||||||
|
|
||||||
// Cache current page list progress for online chapters to allow a faster reopen
|
|
||||||
if (!chapter.isDownloaded) {
|
if (!chapter.isDownloaded) {
|
||||||
source.let { if (it is OnlineSource) it.savePageList(chapter, pages) }
|
source.let { if (it is OnlineSource) it.savePageList(chapter, pages) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save current progress of the chapter. Mark as read if the chapter is finished
|
// Cache current page list progress for online chapters to allow a faster reopen
|
||||||
if (activePage.isLastPage) {
|
if (chapter.read) {
|
||||||
chapter.read = true
|
|
||||||
|
|
||||||
// Check if remove after read is selected by user
|
// Check if remove after read is selected by user
|
||||||
if (prefs.removeAfterRead()) {
|
if (prefs.removeAfterRead()) {
|
||||||
if (prefs.removeAfterReadPrevious() ) {
|
if (prefs.removeAfterReadPrevious() ) {
|
||||||
if (previousChapter != null) {
|
if (prevChapter != null) {
|
||||||
deleteChapter(previousChapter!!, manga)
|
deleteChapter(prevChapter, manga)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deleteChapter(chapter, manga)
|
deleteChapter(chapter, manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.updateChapterProgress(chapter).asRxObservable().subscribe()
|
|
||||||
// Update last read data
|
db.updateChapterProgress(chapter).executeAsBlocking()
|
||||||
db.updateHistoryLastRead(History.create(chapter)
|
|
||||||
.apply { last_read = Date().time })
|
val history = History.create(chapter).apply { last_read = Date().time }
|
||||||
.asRxObservable()
|
db.updateHistoryLastRead(history).executeAsBlocking()
|
||||||
.doOnError { Timber.e(it.message) }
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the active page changes in the reader.
|
||||||
|
*
|
||||||
|
* @param page the active page
|
||||||
|
*/
|
||||||
|
fun onPageChanged(page: Page) {
|
||||||
|
val chapter = page.chapter
|
||||||
|
chapter.last_page_read = page.pageNumber
|
||||||
|
if (chapter.pages!!.last() === page) {
|
||||||
|
chapter.read = true
|
||||||
|
}
|
||||||
|
if (!chapter.isDownloaded && page.status == Page.QUEUE) {
|
||||||
|
loader.loadPriorizedPage(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete selected chapter
|
* Delete selected chapter
|
||||||
|
*
|
||||||
* @param chapter chapter that is selected
|
* @param chapter chapter that is selected
|
||||||
* *
|
|
||||||
* @param manga manga that belongs to chapter
|
* @param manga manga that belongs to chapter
|
||||||
*/
|
*/
|
||||||
fun deleteChapter(chapter: Chapter, manga: Manga) {
|
fun deleteChapter(chapter: ReaderChapter, manga: Manga) {
|
||||||
val source = sourceManager.get(manga.source)!!
|
chapter.isDownloaded = false
|
||||||
|
chapter.pages?.forEach { it.status == Page.QUEUE }
|
||||||
downloadManager.deleteChapter(source, manga, chapter)
|
downloadManager.deleteChapter(source, manga, chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current chapter has been read, we check with this one
|
/**
|
||||||
// If not, we check if the previous chapter has been read
|
* Returns the chapter to be marked as last read in sync services or 0 if no update required.
|
||||||
// We know the chapter we have to check, but we don't know yet if an update is required.
|
*/
|
||||||
// This boolean is used to return 0 if no update is required
|
|
||||||
fun getMangaSyncChapterToUpdate(): Int {
|
fun getMangaSyncChapterToUpdate(): Int {
|
||||||
if (chapter.pages == null || mangaSyncList == null || mangaSyncList!!.isEmpty())
|
if (chapter.pages == null || mangaSyncList == null || mangaSyncList!!.isEmpty())
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
var lastChapterReadLocal = 0
|
var lastChapterReadLocal = 0
|
||||||
|
|
||||||
|
// If the current chapter has been read, we check with this one
|
||||||
if (chapter.read)
|
if (chapter.read)
|
||||||
lastChapterReadLocal = Math.floor(chapter.chapter_number.toDouble()).toInt()
|
lastChapterReadLocal = Math.floor(chapter.chapter_number.toDouble()).toInt()
|
||||||
else if (previousChapter != null && previousChapter!!.read)
|
// If not, we check if the previous chapter has been read
|
||||||
lastChapterReadLocal = Math.floor(previousChapter!!.chapter_number.toDouble()).toInt()
|
else if (prevChapter != null && prevChapter!!.read)
|
||||||
|
lastChapterReadLocal = Math.floor(prevChapter!!.chapter_number.toDouble()).toInt()
|
||||||
|
|
||||||
|
// We know the chapter we have to check, but we don't know yet if an update is required.
|
||||||
|
// This boolean is used to return 0 if no update is required
|
||||||
var hasToUpdate = false
|
var hasToUpdate = false
|
||||||
|
|
||||||
for (mangaSync in mangaSyncList!!) {
|
for (mangaSync in mangaSyncList!!) {
|
||||||
@ -387,6 +429,9 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
return if (hasToUpdate) lastChapterReadLocal else 0
|
return if (hasToUpdate) lastChapterReadLocal else 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the service that updates the last chapter read in sync services
|
||||||
|
*/
|
||||||
fun updateMangaSyncLastChapterRead() {
|
fun updateMangaSyncLastChapterRead() {
|
||||||
for (mangaSync in mangaSyncList ?: emptyList()) {
|
for (mangaSync in mangaSyncList ?: emptyList()) {
|
||||||
val service = syncManager.getService(mangaSync.sync_id) ?: continue
|
val service = syncManager.getService(mangaSync.sync_id) ?: continue
|
||||||
@ -396,6 +441,11 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the next chapter.
|
||||||
|
*
|
||||||
|
* @return true if the next chapter is being loaded, false if there is no next chapter.
|
||||||
|
*/
|
||||||
fun loadNextChapter(): Boolean {
|
fun loadNextChapter(): Boolean {
|
||||||
nextChapter?.let {
|
nextChapter?.let {
|
||||||
onChapterLeft()
|
onChapterLeft()
|
||||||
@ -405,44 +455,42 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the next chapter.
|
||||||
|
*
|
||||||
|
* @return true if the previous chapter is being loaded, false if there is no previous chapter.
|
||||||
|
*/
|
||||||
fun loadPreviousChapter(): Boolean {
|
fun loadPreviousChapter(): Boolean {
|
||||||
previousChapter?.let {
|
prevChapter?.let {
|
||||||
onChapterLeft()
|
onChapterLeft()
|
||||||
loadChapter(it, 0)
|
loadChapter(it, if (it.read) -1 else 0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there's a next chapter.
|
||||||
|
*/
|
||||||
fun hasNextChapter(): Boolean {
|
fun hasNextChapter(): Boolean {
|
||||||
return nextChapter != null
|
return nextChapter != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there's a previous chapter.
|
||||||
|
*/
|
||||||
fun hasPreviousChapter(): Boolean {
|
fun hasPreviousChapter(): Boolean {
|
||||||
return previousChapter != null
|
return prevChapter != null
|
||||||
}
|
|
||||||
|
|
||||||
private fun preloadNextChapter() {
|
|
||||||
nextChapter?.let {
|
|
||||||
if (!isChapterDownloaded(it)) {
|
|
||||||
start(PRELOAD_NEXT_CHAPTER)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stopPreloadingNextChapter() {
|
|
||||||
if (!isUnsubscribed(PRELOAD_NEXT_CHAPTER)) {
|
|
||||||
stop(PRELOAD_NEXT_CHAPTER)
|
|
||||||
nextChapter?.let { chapter ->
|
|
||||||
if (chapter.pages != null) {
|
|
||||||
source.let { if (it is OnlineSource) it.savePageList(chapter, chapter.pages) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the viewer for this manga.
|
||||||
|
*
|
||||||
|
* @param viewer the id of the viewer to set.
|
||||||
|
*/
|
||||||
fun updateMangaViewer(viewer: Int) {
|
fun updateMangaViewer(viewer: Int) {
|
||||||
manga.viewer = viewer
|
manga.viewer = viewer
|
||||||
db.insertManga(manga).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.base
|
package eu.kanade.tachiyomi.ui.reader.viewer.base
|
||||||
|
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.*
|
import com.davemorrissey.labs.subscaleview.decoder.*
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,7 +29,7 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
/**
|
/**
|
||||||
* List of chapters added in the reader.
|
* List of chapters added in the reader.
|
||||||
*/
|
*/
|
||||||
private var chapters = ArrayList<Chapter>()
|
private val chapters = ArrayList<ReaderChapter>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of pages added in the reader. It can contain pages from more than one chapter.
|
* List of pages added in the reader. It can contain pages from more than one chapter.
|
||||||
@ -72,7 +72,7 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
fun updatePageNumber() {
|
fun updatePageNumber() {
|
||||||
val activePage = getActivePage()
|
val activePage = getActivePage()
|
||||||
if (activePage != null) {
|
if (activePage != null) {
|
||||||
readerActivity.onPageChanged(activePage.pageNumber, activePage.chapter.pages.size)
|
readerActivity.onPageChanged(activePage.pageNumber, activePage.chapter.pages!!.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,9 +91,8 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
fun onPageChanged(position: Int) {
|
fun onPageChanged(position: Int) {
|
||||||
val oldPage = pages[currentPage]
|
val oldPage = pages[currentPage]
|
||||||
val newPage = pages[position]
|
val newPage = pages[position]
|
||||||
newPage.chapter.last_page_read = newPage.pageNumber
|
readerActivity.presenter.onPageChanged(newPage)
|
||||||
|
|
||||||
if (readerActivity.presenter.isSeamlessMode) {
|
|
||||||
val oldChapter = oldPage.chapter
|
val oldChapter = oldPage.chapter
|
||||||
val newChapter = newPage.chapter
|
val newChapter = newPage.chapter
|
||||||
|
|
||||||
@ -107,7 +106,7 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
hasRequestedNextChapter = true
|
hasRequestedNextChapter = true
|
||||||
readerActivity.presenter.appendNextChapter()
|
readerActivity.presenter.appendNextChapter()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
currentPage = position
|
currentPage = position
|
||||||
updatePageNumber()
|
updatePageNumber()
|
||||||
}
|
}
|
||||||
@ -144,10 +143,10 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
* @param chapter the chapter to set.
|
* @param chapter the chapter to set.
|
||||||
* @param currentPage the initial page to display.
|
* @param currentPage the initial page to display.
|
||||||
*/
|
*/
|
||||||
fun onPageListReady(chapter: Chapter, currentPage: Page) {
|
fun onPageListReady(chapter: ReaderChapter, currentPage: Page) {
|
||||||
if (!chapters.contains(chapter)) {
|
if (!chapters.contains(chapter)) {
|
||||||
// if we reset the loaded page we also need to reset the loaded chapters
|
// if we reset the loaded page we also need to reset the loaded chapters
|
||||||
chapters = ArrayList<Chapter>()
|
chapters.clear()
|
||||||
chapters.add(chapter)
|
chapters.add(chapter)
|
||||||
pages = ArrayList(chapter.pages)
|
pages = ArrayList(chapter.pages)
|
||||||
onChapterSet(chapter, currentPage)
|
onChapterSet(chapter, currentPage)
|
||||||
@ -162,11 +161,11 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
*
|
*
|
||||||
* @param chapter the chapter to append.
|
* @param chapter the chapter to append.
|
||||||
*/
|
*/
|
||||||
fun onPageListAppendReady(chapter: Chapter) {
|
fun onPageListAppendReady(chapter: ReaderChapter) {
|
||||||
if (!chapters.contains(chapter)) {
|
if (!chapters.contains(chapter)) {
|
||||||
hasRequestedNextChapter = false
|
hasRequestedNextChapter = false
|
||||||
chapters.add(chapter)
|
chapters.add(chapter)
|
||||||
pages.addAll(chapter.pages)
|
pages.addAll(chapter.pages!!)
|
||||||
onChapterAppended(chapter)
|
onChapterAppended(chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,14 +183,14 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
* @param chapter the chapter set.
|
* @param chapter the chapter set.
|
||||||
* @param currentPage the initial page to display.
|
* @param currentPage the initial page to display.
|
||||||
*/
|
*/
|
||||||
abstract fun onChapterSet(chapter: Chapter, currentPage: Page)
|
abstract fun onChapterSet(chapter: ReaderChapter, currentPage: Page)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a chapter is appended in [BaseReader].
|
* Called when a chapter is appended in [BaseReader].
|
||||||
*
|
*
|
||||||
* @param chapter the chapter appended.
|
* @param chapter the chapter appended.
|
||||||
*/
|
*/
|
||||||
abstract fun onChapterAppended(chapter: Chapter)
|
abstract fun onChapterAppended(chapter: ReaderChapter)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves pages forward. Implementations decide how to move (by a page, by some distance...).
|
* Moves pages forward. Implementations decide how to move (by a page, by some distance...).
|
||||||
|
@ -5,8 +5,8 @@ import android.view.MotionEvent
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
|
||||||
@ -181,7 +181,7 @@ abstract class PagerReader : BaseReader() {
|
|||||||
* @param chapter the chapter set.
|
* @param chapter the chapter set.
|
||||||
* @param currentPage the initial page to display.
|
* @param currentPage the initial page to display.
|
||||||
*/
|
*/
|
||||||
override fun onChapterSet(chapter: Chapter, currentPage: Page) {
|
override fun onChapterSet(chapter: ReaderChapter, currentPage: Page) {
|
||||||
this.currentPage = getPageIndex(currentPage) // we might have a new page object
|
this.currentPage = getPageIndex(currentPage) // we might have a new page object
|
||||||
|
|
||||||
// Make sure the view is already initialized.
|
// Make sure the view is already initialized.
|
||||||
@ -195,7 +195,7 @@ abstract class PagerReader : BaseReader() {
|
|||||||
*
|
*
|
||||||
* @param chapter the chapter appended.
|
* @param chapter the chapter appended.
|
||||||
*/
|
*/
|
||||||
override fun onChapterAppended(chapter: Chapter) {
|
override fun onChapterAppended(chapter: ReaderChapter) {
|
||||||
// Make sure the view is already initialized.
|
// Make sure the view is already initialized.
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
adapter.pages = pages
|
adapter.pages = pages
|
||||||
|
@ -6,8 +6,8 @@ import android.view.*
|
|||||||
import android.view.GestureDetector.SimpleOnGestureListener
|
import android.view.GestureDetector.SimpleOnGestureListener
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
|
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
@ -147,7 +147,7 @@ class WebtoonReader : BaseReader() {
|
|||||||
* @param chapter the chapter set.
|
* @param chapter the chapter set.
|
||||||
* @param currentPage the initial page to display.
|
* @param currentPage the initial page to display.
|
||||||
*/
|
*/
|
||||||
override fun onChapterSet(chapter: Chapter, currentPage: Page) {
|
override fun onChapterSet(chapter: ReaderChapter, currentPage: Page) {
|
||||||
// Restoring current page is not supported. It's getting weird scrolling jumps
|
// Restoring current page is not supported. It's getting weird scrolling jumps
|
||||||
// this.currentPage = currentPage;
|
// this.currentPage = currentPage;
|
||||||
|
|
||||||
@ -162,11 +162,11 @@ class WebtoonReader : BaseReader() {
|
|||||||
*
|
*
|
||||||
* @param chapter the chapter appended.
|
* @param chapter the chapter appended.
|
||||||
*/
|
*/
|
||||||
override fun onChapterAppended(chapter: Chapter) {
|
override fun onChapterAppended(chapter: ReaderChapter) {
|
||||||
// Make sure the view is already initialized.
|
// Make sure the view is already initialized.
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
val insertStart = pages.size - chapter.pages.size
|
val insertStart = pages.size - chapter.pages!!.size
|
||||||
adapter.notifyItemRangeInserted(insertStart, chapter.pages.size)
|
adapter.notifyItemRangeInserted(insertStart, chapter.pages!!.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.recent_updates
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
|
|
||||||
|
class RecentChapter(mc: MangaChapter) : Chapter by mc.chapter {
|
||||||
|
|
||||||
|
val manga = mc.manga
|
||||||
|
|
||||||
|
private var _status: Int = 0
|
||||||
|
|
||||||
|
var status: Int
|
||||||
|
get() = download?.status ?: _status
|
||||||
|
set(value) { _status = value }
|
||||||
|
|
||||||
|
var download: Download? = null
|
||||||
|
|
||||||
|
val isDownloaded: Boolean
|
||||||
|
get() = status == Download.DOWNLOADED
|
||||||
|
|
||||||
|
}
|
@ -5,7 +5,6 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
|
||||||
import eu.kanade.tachiyomi.util.inflate
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -18,7 +17,8 @@ import java.util.*
|
|||||||
* @constructor creates an instance of the adapter.
|
* @constructor creates an instance of the adapter.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdapter<RecyclerView.ViewHolder, Any>() {
|
class RecentChaptersAdapter(val fragment: RecentChaptersFragment)
|
||||||
|
: FlexibleAdapter<RecyclerView.ViewHolder, Any>() {
|
||||||
/**
|
/**
|
||||||
* The id of the view type
|
* The id of the view type
|
||||||
*/
|
*/
|
||||||
@ -45,7 +45,7 @@ class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdap
|
|||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
when (holder.itemViewType) {
|
when (holder.itemViewType) {
|
||||||
VIEW_TYPE_CHAPTER -> {
|
VIEW_TYPE_CHAPTER -> {
|
||||||
if (item is MangaChapter) {
|
if (item is RecentChapter) {
|
||||||
(holder as RecentChaptersHolder).onSetValues(item)
|
(holder as RecentChaptersHolder).onSetValues(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,7 +89,7 @@ class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdap
|
|||||||
* @param position position of item
|
* @param position position of item
|
||||||
*/
|
*/
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return if (getItem(position) is MangaChapter) VIEW_TYPE_CHAPTER else VIEW_TYPE_SECTION
|
return if (getItem(position) is RecentChapter) VIEW_TYPE_CHAPTER else VIEW_TYPE_SECTION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -110,8 +110,8 @@ class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdap
|
|||||||
*/
|
*/
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
if (item is MangaChapter)
|
if (item is RecentChapter)
|
||||||
return item.chapter.id
|
return item.id!!
|
||||||
else
|
else
|
||||||
return item.hashCode().toLong()
|
return item.hashCode().toLong()
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,12 @@ import android.view.*
|
|||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.util.getResourceDrawable
|
import eu.kanade.tachiyomi.util.getResourceDrawable
|
||||||
import eu.kanade.tachiyomi.util.toast
|
|
||||||
import eu.kanade.tachiyomi.widget.DeletingChaptersDialog
|
import eu.kanade.tachiyomi.widget.DeletingChaptersDialog
|
||||||
import eu.kanade.tachiyomi.widget.DividerItemDecoration
|
import eu.kanade.tachiyomi.widget.DividerItemDecoration
|
||||||
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
|
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
|
||||||
@ -28,7 +26,9 @@ import timber.log.Timber
|
|||||||
* UI related actions should be called from here.
|
* UI related actions should be called from here.
|
||||||
*/
|
*/
|
||||||
@RequiresPresenter(RecentChaptersPresenter::class)
|
@RequiresPresenter(RecentChaptersPresenter::class)
|
||||||
class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
|
class RecentChaptersFragment
|
||||||
|
: BaseRxFragment<RecentChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Create new RecentChaptersFragment.
|
* Create new RecentChaptersFragment.
|
||||||
@ -40,6 +40,230 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action mode for multiple selection.
|
||||||
|
*/
|
||||||
|
private var actionMode: ActionMode? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter containing the recent chapters.
|
||||||
|
*/
|
||||||
|
lateinit var adapter: RecentChaptersAdapter
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when view gets created
|
||||||
|
* @param inflater layout inflater
|
||||||
|
* @param container view group
|
||||||
|
* @param savedState status of saved state
|
||||||
|
*/
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View {
|
||||||
|
// Inflate view
|
||||||
|
return inflater.inflate(R.layout.fragment_recent_chapters, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when view is created
|
||||||
|
* @param view created view
|
||||||
|
* @param savedInstanceState status of saved sate
|
||||||
|
*/
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
// Init RecyclerView and adapter
|
||||||
|
recycler.layoutManager = NpaLinearLayoutManager(activity)
|
||||||
|
recycler.addItemDecoration(DividerItemDecoration(context.theme.getResourceDrawable(R.attr.divider_drawable)))
|
||||||
|
recycler.setHasFixedSize(true)
|
||||||
|
adapter = RecentChaptersAdapter(this)
|
||||||
|
recycler.adapter = adapter
|
||||||
|
|
||||||
|
// Update toolbar text
|
||||||
|
setToolbarTitle(R.string.label_recent_updates)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns selected chapters
|
||||||
|
* @return list of selected chapters
|
||||||
|
*/
|
||||||
|
fun getSelectedChapters(): List<RecentChapter> {
|
||||||
|
return adapter.selectedItems.map { adapter.getItem(it) as? RecentChapter }.filterNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when item in list is clicked
|
||||||
|
* @param position position of clicked item
|
||||||
|
*/
|
||||||
|
override fun onListItemClick(position: Int): Boolean {
|
||||||
|
// Get item from position
|
||||||
|
val item = adapter.getItem(position)
|
||||||
|
if (item is RecentChapter) {
|
||||||
|
if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) {
|
||||||
|
toggleSelection(position)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
openChapter(item)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when item in list is long clicked
|
||||||
|
* @param position position of clicked item
|
||||||
|
*/
|
||||||
|
override fun onListItemLongClick(position: Int) {
|
||||||
|
if (actionMode == null)
|
||||||
|
actionMode = activity.startSupportActionMode(this)
|
||||||
|
|
||||||
|
toggleSelection(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to toggle selection
|
||||||
|
* @param position position of selected item
|
||||||
|
*/
|
||||||
|
private fun toggleSelection(position: Int) {
|
||||||
|
adapter.toggleSelection(position, false)
|
||||||
|
|
||||||
|
val count = adapter.selectedItemCount
|
||||||
|
if (count == 0) {
|
||||||
|
actionMode?.finish()
|
||||||
|
} else {
|
||||||
|
setContextTitle(count)
|
||||||
|
actionMode?.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the context title
|
||||||
|
* @param count count of selected items
|
||||||
|
*/
|
||||||
|
private fun setContextTitle(count: Int) {
|
||||||
|
actionMode?.title = getString(R.string.label_selected, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open chapter in reader
|
||||||
|
* @param chapter selected chapter
|
||||||
|
*/
|
||||||
|
private fun openChapter(chapter: RecentChapter) {
|
||||||
|
val intent = ReaderActivity.newIntent(activity, chapter.manga, chapter)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download selected items
|
||||||
|
* @param chapters list of selected [RecentChapter]s
|
||||||
|
*/
|
||||||
|
fun downloadChapters(chapters: List<RecentChapter>) {
|
||||||
|
destroyActionModeIfNeeded()
|
||||||
|
presenter.downloadChapters(chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate adapter with chapters
|
||||||
|
* @param chapters list of [Any]
|
||||||
|
*/
|
||||||
|
fun onNextRecentChapters(chapters: List<Any>) {
|
||||||
|
(activity as MainActivity).updateEmptyView(chapters.isEmpty(),
|
||||||
|
R.string.information_no_recent, R.drawable.ic_update_black_128dp)
|
||||||
|
|
||||||
|
destroyActionModeIfNeeded()
|
||||||
|
adapter.setItems(chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update download status of chapter
|
||||||
|
* @param download [Download] object containing download progress.
|
||||||
|
*/
|
||||||
|
fun onChapterStatusChange(download: Download) {
|
||||||
|
getHolder(download)?.notifyStatus(download.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns holder belonging to chapter
|
||||||
|
* @param download [Download] object containing download progress.
|
||||||
|
*/
|
||||||
|
private fun getHolder(download: Download): RecentChaptersHolder? {
|
||||||
|
return recycler.findViewHolderForItemId(download.chapter.id!!) as? RecentChaptersHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark chapter as read
|
||||||
|
* @param chapters list of chapters
|
||||||
|
*/
|
||||||
|
fun markAsRead(chapters: List<RecentChapter>) {
|
||||||
|
presenter.markChapterRead(chapters, true)
|
||||||
|
if (presenter.preferences.removeAfterMarkedAsRead()) {
|
||||||
|
deleteChapters(chapters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete selected chapters
|
||||||
|
* @param chapters list of [RecentChapter] objects
|
||||||
|
*/
|
||||||
|
fun deleteChapters(chapters: List<RecentChapter>) {
|
||||||
|
destroyActionModeIfNeeded()
|
||||||
|
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
|
||||||
|
presenter.deleteChapters(chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destory [ActionMode] if it's shown
|
||||||
|
*/
|
||||||
|
fun destroyActionModeIfNeeded() {
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark chapter as unread
|
||||||
|
* @param chapters list of selected [RecentChapter]
|
||||||
|
*/
|
||||||
|
fun markAsUnread(chapters: List<RecentChapter>) {
|
||||||
|
presenter.markChapterRead(chapters, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start downloading chapter
|
||||||
|
* @param chapter selected chapter with manga
|
||||||
|
*/
|
||||||
|
fun downloadChapter(chapter: RecentChapter) {
|
||||||
|
presenter.downloadChapter(chapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start deleting chapter
|
||||||
|
* @param chapter selected chapter with manga
|
||||||
|
*/
|
||||||
|
fun deleteChapter(chapter: RecentChapter) {
|
||||||
|
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
|
||||||
|
presenter.deleteChapters(listOf(chapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when chapters are deleted
|
||||||
|
*/
|
||||||
|
fun onChaptersDeleted() {
|
||||||
|
dismissDeletingDialog()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when error while deleting
|
||||||
|
* @param error error message
|
||||||
|
*/
|
||||||
|
fun onChaptersDeletedError(error: Throwable) {
|
||||||
|
dismissDeletingDialog()
|
||||||
|
Timber.e(error, error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to dismiss deleting dialog
|
||||||
|
*/
|
||||||
|
fun dismissDeletingDialog() {
|
||||||
|
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -88,229 +312,4 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
|
|||||||
actionMode = null
|
actionMode = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Action mode for multiple selection.
|
|
||||||
*/
|
|
||||||
private var actionMode: ActionMode? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter containing the recent chapters.
|
|
||||||
*/
|
|
||||||
lateinit var adapter: RecentChaptersAdapter
|
|
||||||
private set
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when view gets created
|
|
||||||
* @param inflater layout inflater
|
|
||||||
* @param container view group
|
|
||||||
* @param savedState status of saved state
|
|
||||||
*/
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
|
||||||
// Inflate view
|
|
||||||
return inflater.inflate(R.layout.fragment_recent_chapters, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when view is created
|
|
||||||
* @param view created view
|
|
||||||
* @param savedInstanceState status of saved sate
|
|
||||||
*/
|
|
||||||
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
|
|
||||||
// Init RecyclerView and adapter
|
|
||||||
recycler.layoutManager = NpaLinearLayoutManager(activity)
|
|
||||||
recycler.addItemDecoration(DividerItemDecoration(context.theme.getResourceDrawable(R.attr.divider_drawable)))
|
|
||||||
recycler.setHasFixedSize(true)
|
|
||||||
adapter = RecentChaptersAdapter(this)
|
|
||||||
recycler.adapter = adapter
|
|
||||||
|
|
||||||
// Update toolbar text
|
|
||||||
setToolbarTitle(R.string.label_recent_updates)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns selected chapters
|
|
||||||
* @return list of [MangaChapter]s
|
|
||||||
*/
|
|
||||||
fun getSelectedChapters(): List<MangaChapter> {
|
|
||||||
return adapter.selectedItems.map { adapter.getItem(it) as? MangaChapter }.filterNotNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when item in list is clicked
|
|
||||||
* @param position position of clicked item
|
|
||||||
*/
|
|
||||||
override fun onListItemClick(position: Int): Boolean {
|
|
||||||
// Get item from position
|
|
||||||
val item = adapter.getItem(position)
|
|
||||||
if (item is MangaChapter) {
|
|
||||||
if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) {
|
|
||||||
toggleSelection(position)
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
openChapter(item)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when item in list is long clicked
|
|
||||||
* @param position position of clicked item
|
|
||||||
*/
|
|
||||||
override fun onListItemLongClick(position: Int) {
|
|
||||||
if (actionMode == null)
|
|
||||||
actionMode = activity.startSupportActionMode(this)
|
|
||||||
|
|
||||||
toggleSelection(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to toggle selection
|
|
||||||
* @param position position of selected item
|
|
||||||
*/
|
|
||||||
private fun toggleSelection(position: Int) {
|
|
||||||
adapter.toggleSelection(position, false)
|
|
||||||
|
|
||||||
val count = adapter.selectedItemCount
|
|
||||||
if (count == 0) {
|
|
||||||
actionMode?.finish()
|
|
||||||
} else {
|
|
||||||
setContextTitle(count)
|
|
||||||
actionMode?.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the context title
|
|
||||||
* @param count count of selected items
|
|
||||||
*/
|
|
||||||
private fun setContextTitle(count: Int) {
|
|
||||||
actionMode?.title = getString(R.string.label_selected, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open chapter in reader
|
|
||||||
* @param mangaChapter selected [MangaChapter]
|
|
||||||
*/
|
|
||||||
private fun openChapter(mangaChapter: MangaChapter) {
|
|
||||||
val intent = ReaderActivity.newIntent(activity, mangaChapter.manga, mangaChapter.chapter)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download selected items
|
|
||||||
* @param mangaChapters list of selected [MangaChapter]s
|
|
||||||
*/
|
|
||||||
fun downloadChapters(mangaChapters: List<MangaChapter>) {
|
|
||||||
destroyActionModeIfNeeded()
|
|
||||||
presenter.downloadChapters(mangaChapters)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate adapter with chapters
|
|
||||||
* @param chapters list of [Any]
|
|
||||||
*/
|
|
||||||
fun onNextMangaChapters(chapters: List<Any>) {
|
|
||||||
(activity as MainActivity).updateEmptyView(chapters.isEmpty(),
|
|
||||||
R.string.information_no_recent, R.drawable.ic_update_black_128dp)
|
|
||||||
|
|
||||||
destroyActionModeIfNeeded()
|
|
||||||
adapter.setItems(chapters)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update download status of chapter
|
|
||||||
* @param download [Download] object containing download progress.
|
|
||||||
*/
|
|
||||||
fun onChapterStatusChange(download: Download) {
|
|
||||||
getHolder(download)?.onStatusChange(download.status)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns holder belonging to chapter
|
|
||||||
* @param download [Download] object containing download progress.
|
|
||||||
*/
|
|
||||||
private fun getHolder(download: Download): RecentChaptersHolder? {
|
|
||||||
return recycler.findViewHolderForItemId(download.chapter.id) as? RecentChaptersHolder
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark chapter as read
|
|
||||||
* @param mangaChapters list of [MangaChapter] objects
|
|
||||||
*/
|
|
||||||
fun markAsRead(mangaChapters: List<MangaChapter>) {
|
|
||||||
presenter.markChapterRead(mangaChapters, true)
|
|
||||||
if (presenter.preferences.removeAfterMarkedAsRead()) {
|
|
||||||
deleteChapters(mangaChapters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete selected chapters
|
|
||||||
* @param mangaChapters list of [MangaChapter] objects
|
|
||||||
*/
|
|
||||||
fun deleteChapters(mangaChapters: List<MangaChapter>) {
|
|
||||||
destroyActionModeIfNeeded()
|
|
||||||
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
|
|
||||||
presenter.deleteChapters(mangaChapters)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destory [ActionMode] if it's shown
|
|
||||||
*/
|
|
||||||
fun destroyActionModeIfNeeded() {
|
|
||||||
actionMode?.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark chapter as unread
|
|
||||||
* @param mangaChapters list of selected [MangaChapter]
|
|
||||||
*/
|
|
||||||
fun markAsUnread(mangaChapters: List<MangaChapter>) {
|
|
||||||
presenter.markChapterRead(mangaChapters, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start downloading chapter
|
|
||||||
* @param item selected chapter with manga
|
|
||||||
*/
|
|
||||||
fun downloadChapter(item: MangaChapter) {
|
|
||||||
presenter.downloadChapter(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start deleting chapter
|
|
||||||
* @param item selected chapter with manga
|
|
||||||
*/
|
|
||||||
fun deleteChapter(item: MangaChapter) {
|
|
||||||
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
|
|
||||||
presenter.deleteChapter(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when chapters are deleted
|
|
||||||
*/
|
|
||||||
fun onChaptersDeleted() {
|
|
||||||
dismissDeletingDialog()
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when error while deleting
|
|
||||||
* @param error error message
|
|
||||||
*/
|
|
||||||
fun onChaptersDeletedError(error: Throwable) {
|
|
||||||
dismissDeletingDialog()
|
|
||||||
Timber.e(error, error.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to dismiss deleting dialog
|
|
||||||
*/
|
|
||||||
fun dismissDeletingDialog() {
|
|
||||||
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.recent_updates
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
@ -19,8 +18,11 @@ import kotlinx.android.synthetic.main.item_recent_chapters.view.*
|
|||||||
* @param listener a listener to react to single tap and long tap events.
|
* @param listener a listener to react to single tap and long tap events.
|
||||||
* @constructor creates a new recent chapter holder.
|
* @constructor creates a new recent chapter holder.
|
||||||
*/
|
*/
|
||||||
class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapter, listener: OnListItemClickListener) :
|
class RecentChaptersHolder(
|
||||||
FlexibleViewHolder(view, adapter, listener) {
|
private val view: View,
|
||||||
|
private val adapter: RecentChaptersAdapter,
|
||||||
|
listener: OnListItemClickListener)
|
||||||
|
: FlexibleViewHolder(view, adapter, listener) {
|
||||||
/**
|
/**
|
||||||
* Color of read chapter
|
* Color of read chapter
|
||||||
*/
|
*/
|
||||||
@ -34,100 +36,97 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte
|
|||||||
/**
|
/**
|
||||||
* Object containing chapter information
|
* Object containing chapter information
|
||||||
*/
|
*/
|
||||||
private var mangaChapter: MangaChapter? = null
|
private var chapter: RecentChapter? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
|
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
|
||||||
// correctly positioned. The reason being that the view may change position before the
|
// correctly positioned. The reason being that the view may change position before the
|
||||||
// PopupMenu is shown.
|
// PopupMenu is shown.
|
||||||
itemView.chapter_menu.setOnClickListener { it.post({ showPopupMenu(it) }) }
|
view.chapter_menu.setOnClickListener { it.post({ showPopupMenu(it) }) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set values of view
|
* Set values of view
|
||||||
*
|
*
|
||||||
* @param item item containing chapter information
|
* @param chapter item containing chapter information
|
||||||
*/
|
*/
|
||||||
fun onSetValues(item: MangaChapter) {
|
fun onSetValues(chapter: RecentChapter) {
|
||||||
this.mangaChapter = item
|
this.chapter = chapter
|
||||||
|
|
||||||
// Set chapter title
|
// Set chapter title
|
||||||
itemView.chapter_title.text = item.chapter.name
|
view.chapter_title.text = chapter.name
|
||||||
|
|
||||||
// Set manga title
|
// Set manga title
|
||||||
itemView.manga_title.text = item.manga.title
|
view.manga_title.text = chapter.manga.title
|
||||||
|
|
||||||
// Check if chapter is read and set correct color
|
// Check if chapter is read and set correct color
|
||||||
if (item.chapter.read) {
|
if (chapter.read) {
|
||||||
itemView.chapter_title.setTextColor(readColor)
|
view.chapter_title.setTextColor(readColor)
|
||||||
itemView.manga_title.setTextColor(readColor)
|
view.manga_title.setTextColor(readColor)
|
||||||
} else {
|
} else {
|
||||||
itemView.chapter_title.setTextColor(unreadColor)
|
view.chapter_title.setTextColor(unreadColor)
|
||||||
itemView.manga_title.setTextColor(unreadColor)
|
view.manga_title.setTextColor(unreadColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set chapter status
|
// Set chapter status
|
||||||
onStatusChange(item.chapter.status)
|
notifyStatus(chapter.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates chapter status in view.
|
* Updates chapter status in view.
|
||||||
|
*
|
||||||
* @param status download status
|
* @param status download status
|
||||||
*/
|
*/
|
||||||
fun onStatusChange(status: Int) {
|
fun notifyStatus(status: Int) = with(view.download_text) {
|
||||||
when (status) {
|
when (status) {
|
||||||
Download.QUEUE -> itemView.download_text.setText(R.string.chapter_queued)
|
Download.QUEUE -> setText(R.string.chapter_queued)
|
||||||
Download.DOWNLOADING -> itemView.download_text.setText(R.string.chapter_downloading)
|
Download.DOWNLOADING -> setText(R.string.chapter_downloading)
|
||||||
Download.DOWNLOADED -> itemView.download_text.setText(R.string.chapter_downloaded)
|
Download.DOWNLOADED -> setText(R.string.chapter_downloaded)
|
||||||
Download.ERROR -> itemView.download_text.setText(R.string.chapter_error)
|
Download.ERROR -> setText(R.string.chapter_error)
|
||||||
else -> itemView.download_text.text = ""
|
else -> text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show pop up menu
|
* Show pop up menu
|
||||||
|
*
|
||||||
* @param view view containing popup menu.
|
* @param view view containing popup menu.
|
||||||
*/
|
*/
|
||||||
private fun showPopupMenu(view: View) {
|
private fun showPopupMenu(view: View) = chapter?.let { chapter ->
|
||||||
// Create a PopupMenu, giving it the clicked view for an anchor
|
// Create a PopupMenu, giving it the clicked view for an anchor
|
||||||
val popup = PopupMenu(adapter.fragment.activity, view)
|
val popup = PopupMenu(view.context, view)
|
||||||
|
|
||||||
// Inflate our menu resource into the PopupMenu's Menu
|
// Inflate our menu resource into the PopupMenu's Menu
|
||||||
popup.menuInflater.inflate(R.menu.chapter_recent, popup.menu)
|
popup.menuInflater.inflate(R.menu.chapter_recent, popup.menu)
|
||||||
|
|
||||||
mangaChapter?.let {
|
|
||||||
|
|
||||||
// Hide download and show delete if the chapter is downloaded and
|
// Hide download and show delete if the chapter is downloaded and
|
||||||
if (it.chapter.isDownloaded) {
|
if (chapter.isDownloaded) {
|
||||||
val menu = popup.menu
|
popup.menu.findItem(R.id.action_download).isVisible = false
|
||||||
menu.findItem(R.id.action_download).isVisible = false
|
popup.menu.findItem(R.id.action_delete).isVisible = true
|
||||||
menu.findItem(R.id.action_delete).isVisible = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide mark as unread when the chapter is unread
|
// Hide mark as unread when the chapter is unread
|
||||||
if (!it.chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) {
|
if (!chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) {
|
||||||
popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
|
popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide mark as read when the chapter is read
|
// Hide mark as read when the chapter is read
|
||||||
if (it.chapter.read) {
|
if (chapter.read) {
|
||||||
popup.menu.findItem(R.id.action_mark_as_read).isVisible = false
|
popup.menu.findItem(R.id.action_mark_as_read).isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Set a listener so we are notified if a menu item is clicked
|
// Set a listener so we are notified if a menu item is clicked
|
||||||
popup.setOnMenuItemClickListener { menuItem ->
|
popup.setOnMenuItemClickListener { menuItem ->
|
||||||
|
with(adapter.fragment) {
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.action_download -> adapter.fragment.downloadChapter(it)
|
R.id.action_download -> downloadChapter(chapter)
|
||||||
R.id.action_delete -> adapter.fragment.deleteChapter(it)
|
R.id.action_delete -> deleteChapter(chapter)
|
||||||
R.id.action_mark_as_read -> adapter.fragment.markAsRead(listOf(it))
|
R.id.action_mark_as_read -> markAsRead(listOf(chapter))
|
||||||
R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(listOf(it))
|
R.id.action_mark_as_unread -> markAsUnread(listOf(chapter))
|
||||||
}
|
}
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally show the PopupMenu
|
// Finally show the PopupMenu
|
||||||
|
@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.ui.recent_updates
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
@ -15,34 +13,34 @@ import rx.Observable
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
||||||
/**
|
/**
|
||||||
* Used to connect to database
|
* Used to connect to database
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to get settings
|
* Used to get settings
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var preferences: PreferencesHelper
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to get information from download manager
|
* Used to get information from download manager
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var downloadManager: DownloadManager
|
val downloadManager: DownloadManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to get source from source id
|
* Used to get source from source id
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List containing chapter and manga information
|
* List containing chapter and manga information
|
||||||
*/
|
*/
|
||||||
private var mangaChapters: List<MangaChapter>? = null
|
private var chapters: List<RecentChapter>? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the restartable.
|
* The id of the restartable.
|
||||||
@ -60,173 +58,116 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
|||||||
// Used to get recent chapters
|
// Used to get recent chapters
|
||||||
restartableLatestCache(GET_RECENT_CHAPTERS,
|
restartableLatestCache(GET_RECENT_CHAPTERS,
|
||||||
{ getRecentChaptersObservable() },
|
{ getRecentChaptersObservable() },
|
||||||
{ fragment, chapters ->
|
{ view, chapters ->
|
||||||
// Update adapter to show recent manga's
|
// Update adapter to show recent manga's
|
||||||
fragment.onNextMangaChapters(chapters)
|
view.onNextRecentChapters(chapters)
|
||||||
// Update download status
|
|
||||||
updateChapterStatus(convertToMangaChaptersList(chapters))
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Used to update download status
|
// Used to update download status
|
||||||
startableLatestCache(CHAPTER_STATUS_CHANGES,
|
restartableLatestCache(CHAPTER_STATUS_CHANGES,
|
||||||
{ getChapterStatusObs() },
|
{ getChapterStatusObservable() },
|
||||||
{ recentChaptersFragment, download ->
|
{ view, download ->
|
||||||
// Set chapter status
|
// Set chapter status
|
||||||
recentChaptersFragment.onChapterStatusChange(download)
|
view.onChapterStatusChange(download)
|
||||||
},
|
},
|
||||||
{ view, error -> Timber.e(error.cause, error.message) }
|
{ view, error -> Timber.e(error.cause, error.message) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if (savedState == null) {
|
if (savedState == null) {
|
||||||
// Start fetching recent chapters
|
// Start fetching recent chapters
|
||||||
start(GET_RECENT_CHAPTERS)
|
start(GET_RECENT_CHAPTERS)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns observable containing chapter status.
|
|
||||||
* @return download object containing download progress.
|
|
||||||
*/
|
|
||||||
private fun getChapterStatusObs(): Observable<Download> {
|
|
||||||
return downloadManager.queue.getStatusObservable()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.filter { download: Download ->
|
|
||||||
if (chapterIdEquals(download.chapter.id))
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
}
|
|
||||||
.doOnNext { download1: Download -> updateChapterStatus(download1) }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to check if chapter is in recent list
|
|
||||||
* @param chaptersId id of chapter
|
|
||||||
* @return exist in recent list
|
|
||||||
*/
|
|
||||||
private fun chapterIdEquals(chaptersId: Long): Boolean {
|
|
||||||
mangaChapters!!.forEach { mangaChapter ->
|
|
||||||
if (chaptersId == mangaChapter.chapter.id) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list only containing MangaChapter objects.
|
|
||||||
* @param input the list that will be converted.
|
|
||||||
* @return list containing MangaChapters objects.
|
|
||||||
*/
|
|
||||||
private fun convertToMangaChaptersList(input: List<Any>): List<MangaChapter> {
|
|
||||||
// Create temp list
|
|
||||||
val tempMangaChapterList = ArrayList<MangaChapter>()
|
|
||||||
|
|
||||||
// Only add MangaChapter objects
|
|
||||||
//noinspection Convert2streamapi
|
|
||||||
input.forEach { `object` ->
|
|
||||||
if (`object` is MangaChapter) {
|
|
||||||
tempMangaChapterList.add(`object`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return temp list
|
|
||||||
return tempMangaChapterList
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update status of chapters.
|
|
||||||
* @param download download object containing progress.
|
|
||||||
*/
|
|
||||||
private fun updateChapterStatus(download: Download) {
|
|
||||||
// Loop through list
|
|
||||||
mangaChapters?.let {
|
|
||||||
for (item in it) {
|
|
||||||
if (download.chapter.id == item.chapter.id) {
|
|
||||||
// Update status.
|
|
||||||
item.chapter.status = download.status
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update status of chapters
|
|
||||||
* @param mangaChapters list containing recent chapters
|
|
||||||
*/
|
|
||||||
private fun updateChapterStatus(mangaChapters: List<MangaChapter>) {
|
|
||||||
// Set global list of chapters.
|
|
||||||
this.mangaChapters = mangaChapters
|
|
||||||
|
|
||||||
// Update status.
|
|
||||||
//noinspection Convert2streamapi
|
|
||||||
for (mangaChapter in mangaChapters)
|
|
||||||
setChapterStatus(mangaChapter)
|
|
||||||
|
|
||||||
// Start onChapterStatusChange restartable.
|
|
||||||
start(CHAPTER_STATUS_CHANGES)
|
start(CHAPTER_STATUS_CHANGES)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the chapter status
|
|
||||||
* @param mangaChapter MangaChapter which status gets updated
|
|
||||||
*/
|
|
||||||
private fun setChapterStatus(mangaChapter: MangaChapter) {
|
|
||||||
// Check if chapter in queue
|
|
||||||
for (download in downloadManager.queue) {
|
|
||||||
if (mangaChapter.chapter.id == download.chapter.id) {
|
|
||||||
mangaChapter.chapter.status = download.status
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get source of chapter
|
|
||||||
val source = sourceManager.get(mangaChapter.manga.source)!!
|
|
||||||
|
|
||||||
// Check if chapter is downloaded
|
|
||||||
if (downloadManager.isChapterDownloaded(source, mangaChapter.manga, mangaChapter.chapter)) {
|
|
||||||
mangaChapter.chapter.status = Download.DOWNLOADED
|
|
||||||
} else {
|
|
||||||
mangaChapter.chapter.status = Download.NOT_DOWNLOADED
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get observable containing recent chapters and date
|
* Get observable containing recent chapters and date
|
||||||
* @return observable containing recent chapters and date
|
* @return observable containing recent chapters and date
|
||||||
*/
|
*/
|
||||||
fun getRecentChaptersObservable(): Observable<ArrayList<Any>>? {
|
fun getRecentChaptersObservable(): Observable<ArrayList<Any>> {
|
||||||
// Set date for recent chapters
|
// Set date for recent chapters
|
||||||
val cal = Calendar.getInstance()
|
val cal = Calendar.getInstance().apply {
|
||||||
cal.time = Date()
|
time = Date()
|
||||||
cal.add(Calendar.MONTH, -1)
|
add(Calendar.MONTH, -1)
|
||||||
|
}
|
||||||
|
|
||||||
return db.getRecentChapters(cal.time).asRxObservable()
|
return db.getRecentChapters(cal.time).asRxObservable()
|
||||||
|
// Convert to a list of recent chapters.
|
||||||
|
.map { mangaChapters ->
|
||||||
|
mangaChapters.map { it.toModel() }
|
||||||
|
}
|
||||||
|
.doOnNext { chapters = it }
|
||||||
// Group chapters by the date they were fetched on a ordered map.
|
// Group chapters by the date they were fetched on a ordered map.
|
||||||
.flatMap { recentItems ->
|
.flatMap { recentItems ->
|
||||||
Observable.from(recentItems)
|
Observable.from(recentItems)
|
||||||
.toMultimap(
|
.toMultimap(
|
||||||
{ getMapKey(it.chapter.date_fetch) },
|
{ getMapKey(it.date_fetch) },
|
||||||
{ it },
|
{ it },
|
||||||
{ TreeMap { d1, d2 -> d2.compareTo(d1) } })
|
{ TreeMap { d1, d2 -> d2.compareTo(d1) } })
|
||||||
}
|
}
|
||||||
// Add every day and all its chapters to a single list.
|
// Add every day and all its chapters to a single list.
|
||||||
.map { recentItems ->
|
.map { recentItems ->
|
||||||
val items = ArrayList<Any>()
|
ArrayList<Any>().apply {
|
||||||
recentItems.entries.forEach { recent ->
|
for ((key, value) in recentItems) {
|
||||||
items.add(recent.key)
|
add(key)
|
||||||
items.addAll(recent.value)
|
addAll(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
items
|
|
||||||
}
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns observable containing chapter status.
|
||||||
|
* @return download object containing download progress.
|
||||||
|
*/
|
||||||
|
private fun getChapterStatusObservable(): Observable<Download> {
|
||||||
|
return downloadManager.queue.getStatusObservable()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnNext { download -> onDownloadStatusChange(download) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a chapter from the database to an extended model, allowing to store new fields.
|
||||||
|
*/
|
||||||
|
private fun MangaChapter.toModel(): RecentChapter {
|
||||||
|
// Create the model object.
|
||||||
|
val model = RecentChapter(this)
|
||||||
|
|
||||||
|
// Find an active download for this chapter.
|
||||||
|
val download = downloadManager.queue.find { it.chapter.id == chapter.id }
|
||||||
|
|
||||||
|
// If there's an active download, assign it, otherwise ask the manager if the chapter is
|
||||||
|
// downloaded and assign it to the status.
|
||||||
|
if (download != null) {
|
||||||
|
model.download = download
|
||||||
|
} else {
|
||||||
|
// Get source of chapter.
|
||||||
|
val source = sourceManager.get(manga.source)!!
|
||||||
|
|
||||||
|
model.status = if (downloadManager.isChapterDownloaded(source, manga, chapter))
|
||||||
|
Download.DOWNLOADED
|
||||||
|
else
|
||||||
|
Download.NOT_DOWNLOADED
|
||||||
|
}
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update status of chapters.
|
||||||
|
* @param download download object containing progress.
|
||||||
|
*/
|
||||||
|
private fun onDownloadStatusChange(download: Download) {
|
||||||
|
// Assign the download to the model object.
|
||||||
|
if (download.status == Download.QUEUE) {
|
||||||
|
val chapter = chapters?.find { it.id == download.chapter.id }
|
||||||
|
if (chapter != null && chapter.download == null) {
|
||||||
|
chapter.download = download
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get date as time key
|
* Get date as time key
|
||||||
* @param date desired date
|
* @param date desired date
|
||||||
@ -244,18 +185,17 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark selected chapter as read
|
* Mark selected chapter as read
|
||||||
* @param mangaChapters list of selected MangaChapters
|
* @param chapters list of selected chapters
|
||||||
* @param read read status
|
* @param read read status
|
||||||
*/
|
*/
|
||||||
fun markChapterRead(mangaChapters: List<MangaChapter>, read: Boolean) {
|
fun markChapterRead(chapters: List<RecentChapter>, read: Boolean) {
|
||||||
Observable.from(mangaChapters)
|
Observable.from(chapters)
|
||||||
.doOnNext { mangaChapter ->
|
.doOnNext { chapter ->
|
||||||
mangaChapter.chapter.read = read
|
chapter.read = read
|
||||||
if (!read) {
|
if (!read) {
|
||||||
mangaChapter.chapter.last_page_read = 0
|
chapter.last_page_read = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { mangaChapter -> mangaChapter.chapter }
|
|
||||||
.toList()
|
.toList()
|
||||||
.flatMap { db.updateChaptersProgress(it).asRxObservable() }
|
.flatMap { db.updateChaptersProgress(it).asRxObservable() }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
@ -264,9 +204,9 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete selected chapters
|
* Delete selected chapters
|
||||||
* @param chapters list of MangaChapters
|
* @param chapters list of chapters
|
||||||
*/
|
*/
|
||||||
fun deleteChapters(chapters: List<MangaChapter>) {
|
fun deleteChapters(chapters: List<RecentChapter>) {
|
||||||
val wasRunning = downloadManager.isRunning
|
val wasRunning = downloadManager.isRunning
|
||||||
if (wasRunning) {
|
if (wasRunning) {
|
||||||
DownloadService.stop(context)
|
DownloadService.stop(context)
|
||||||
@ -288,11 +228,11 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Download selected chapters
|
* Download selected chapters
|
||||||
* @param mangaChapters [MangaChapter] that is selected
|
* @param chapters list of recent chapters seleted.
|
||||||
*/
|
*/
|
||||||
fun downloadChapters(mangaChapters: List<MangaChapter>) {
|
fun downloadChapters(chapters: List<RecentChapter>) {
|
||||||
DownloadService.start(context)
|
DownloadService.start(context)
|
||||||
Observable.from(mangaChapters)
|
Observable.from(chapters)
|
||||||
.doOnNext { downloadChapter(it) }
|
.doOnNext { downloadChapter(it) }
|
||||||
.subscribeOn(AndroidSchedulers.mainThread())
|
.subscribeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
@ -300,47 +240,23 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Download selected chapter
|
* Download selected chapter
|
||||||
* @param item chapter that is selected
|
* @param chapter chapter that is selected
|
||||||
*/
|
*/
|
||||||
fun downloadChapter(item: MangaChapter) {
|
fun downloadChapter(chapter: RecentChapter) {
|
||||||
DownloadService.start(context)
|
DownloadService.start(context)
|
||||||
downloadManager.downloadChapters(item.manga, listOf(item.chapter))
|
downloadManager.downloadChapters(chapter.manga, listOf(chapter))
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete selected chapter
|
|
||||||
* @param item chapter that are selected
|
|
||||||
*/
|
|
||||||
fun deleteChapter(item: MangaChapter) {
|
|
||||||
val wasRunning = downloadManager.isRunning
|
|
||||||
if (wasRunning) {
|
|
||||||
DownloadService.stop(context)
|
|
||||||
}
|
|
||||||
Observable.just(item)
|
|
||||||
.doOnNext { deleteChapter(it.chapter, it.manga) }
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribeFirst({ view, result ->
|
|
||||||
view.onChaptersDeleted()
|
|
||||||
if (wasRunning) {
|
|
||||||
DownloadService.start(context)
|
|
||||||
}
|
|
||||||
}, { view, error ->
|
|
||||||
view.onChaptersDeletedError(error)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete selected chapter
|
* Delete selected chapter
|
||||||
* @param chapter chapter that is selected
|
* @param chapter chapter that is selected
|
||||||
* @param manga manga that belongs to chapter
|
|
||||||
*/
|
*/
|
||||||
private fun deleteChapter(chapter: Chapter, manga: Manga) {
|
private fun deleteChapter(chapter: RecentChapter) {
|
||||||
val source = sourceManager.get(manga.source) ?: return
|
val source = sourceManager.get(chapter.manga.source) ?: return
|
||||||
downloadManager.queue.del(chapter)
|
downloadManager.queue.del(chapter)
|
||||||
downloadManager.deleteChapter(source, manga, chapter)
|
downloadManager.deleteChapter(source, chapter.manga, chapter)
|
||||||
chapter.status = Download.NOT_DOWNLOADED
|
chapter.status = Download.NOT_DOWNLOADED
|
||||||
|
chapter.download = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -2,11 +2,12 @@ package eu.kanade.tachiyomi.ui.recent_updates
|
|||||||
|
|
||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
|
import android.text.format.DateUtils.DAY_IN_MILLIS
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import kotlinx.android.synthetic.main.item_recent_chapter_section.view.*
|
import kotlinx.android.synthetic.main.item_recent_chapter_section.view.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class SectionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
class SectionViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current date
|
* Current date
|
||||||
@ -19,8 +20,6 @@ class SectionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|||||||
* @param date of section header
|
* @param date of section header
|
||||||
*/
|
*/
|
||||||
fun onSetValues(date: Date) {
|
fun onSetValues(date: Date) {
|
||||||
val s = DateUtils.getRelativeTimeSpanString(
|
view.section_text.text = DateUtils.getRelativeTimeSpanString(date.time, now, DAY_IN_MILLIS)
|
||||||
date.time, now, DateUtils.DAY_IN_MILLIS)
|
|
||||||
itemView.section_text.text = s
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -37,8 +37,8 @@ class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter)
|
|||||||
*/
|
*/
|
||||||
fun onSetValues(item: MangaChapterHistory) {
|
fun onSetValues(item: MangaChapterHistory) {
|
||||||
// Retrieve objects
|
// Retrieve objects
|
||||||
val manga = item.mangaChapter.manga
|
val manga = item.manga
|
||||||
val chapter = item.mangaChapter.chapter
|
val chapter = item.chapter
|
||||||
val history = item.history
|
val history = item.history
|
||||||
|
|
||||||
// Set manga title
|
// Set manga title
|
||||||
@ -71,7 +71,7 @@ class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter)
|
|||||||
.onPositive { materialDialog, dialogAction ->
|
.onPositive { materialDialog, dialogAction ->
|
||||||
// Check if user wants all chapters reset
|
// Check if user wants all chapters reset
|
||||||
if (materialDialog.customView?.removeAll?.isChecked as Boolean) {
|
if (materialDialog.customView?.removeAll?.isChecked as Boolean) {
|
||||||
adapter.fragment.removeAllFromHistory(manga.id)
|
adapter.fragment.removeAllFromHistory(manga.id!!)
|
||||||
} else {
|
} else {
|
||||||
adapter.fragment.removeFromHistory(history)
|
adapter.fragment.removeFromHistory(history)
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,9 @@ import rx.Observable
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of RecentlyReadFragment.
|
* Presenter of RecentlyReadFragment.
|
||||||
@ -30,7 +30,7 @@ class RecentlyReadPresenter : BasePresenter<RecentlyReadFragment>() {
|
|||||||
/**
|
/**
|
||||||
* Used to connect to database
|
* Used to connect to database
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.setting
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v14.preference.PreferenceFragment
|
import android.support.v14.preference.PreferenceFragment
|
||||||
import eu.kanade.tachiyomi.App
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
@ -12,22 +11,21 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||||
import kotlinx.android.synthetic.main.toolbar.*
|
import kotlinx.android.synthetic.main.toolbar.*
|
||||||
import javax.inject.Inject
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class SettingsActivity : BaseActivity() {
|
class SettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
@Inject lateinit var preferences: PreferencesHelper
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
@Inject lateinit var chapterCache: ChapterCache
|
val chapterCache: ChapterCache by injectLazy()
|
||||||
@Inject lateinit var db: DatabaseHelper
|
val db: DatabaseHelper by injectLazy()
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
val sourceManager: SourceManager by injectLazy()
|
||||||
@Inject lateinit var syncManager: MangaSyncManager
|
val syncManager: MangaSyncManager by injectLazy()
|
||||||
@Inject lateinit var networkHelper: NetworkHelper
|
val networkHelper: NetworkHelper by injectLazy()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
setAppTheme()
|
setAppTheme()
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
setContentView(R.layout.activity_preferences)
|
setContentView(R.layout.activity_preferences)
|
||||||
App.get(this).component.inject(this)
|
|
||||||
|
|
||||||
setupToolbar(toolbar)
|
setupToolbar(toolbar)
|
||||||
|
|
||||||
|
22
app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt
Normal file
22
app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
import rx.Observable
|
||||||
|
import rx.functions.Func1
|
||||||
|
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||||
|
|
||||||
|
class RetryWithDelay(
|
||||||
|
private val maxRetries: Int = 1,
|
||||||
|
private val retryStrategy: (Int) -> Int = { 1000 }
|
||||||
|
) : Func1<Observable<out Throwable>, Observable<*>> {
|
||||||
|
|
||||||
|
private var retryCount = 0
|
||||||
|
|
||||||
|
override fun call(attempts: Observable<out Throwable>) = attempts.flatMap { error ->
|
||||||
|
val count = ++retryCount
|
||||||
|
if (count <= maxRetries) {
|
||||||
|
Observable.timer(retryStrategy(count).toLong(), MILLISECONDS)
|
||||||
|
} else {
|
||||||
|
Observable.error(error as Throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,6 @@
|
|||||||
<string name="pref_custom_brightness_value_key">pref_custom_brightness_value_key</string>
|
<string name="pref_custom_brightness_value_key">pref_custom_brightness_value_key</string>
|
||||||
<string name="pref_reader_theme_key">pref_reader_theme_key</string>
|
<string name="pref_reader_theme_key">pref_reader_theme_key</string>
|
||||||
<string name="pref_image_decoder_key">pref_image_decoder_key</string>
|
<string name="pref_image_decoder_key">pref_image_decoder_key</string>
|
||||||
<string name="pref_seamless_mode_key">pref_seamless_mode_key</string>
|
|
||||||
<string name="pref_read_with_volume_keys_key">reader_volume_keys</string>
|
<string name="pref_read_with_volume_keys_key">reader_volume_keys</string>
|
||||||
<string name="pref_read_with_tapping_key">reader_tap</string>
|
<string name="pref_read_with_tapping_key">reader_tap</string>
|
||||||
<string name="pref_reencode_key">reencode_image</string>
|
<string name="pref_reencode_key">reencode_image</string>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user