diff --git a/app/build.gradle b/app/build.gradle index 7ab0eaf577..7cebfa087d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,27 +84,22 @@ android { } 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 compile 'com.github.inorichi:subsampling-scale-image-view:421fb81' compile 'com.github.inorichi:ReactiveNetwork:69092ed' // Android support library - compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION" - compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION" - compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION" - compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION" - compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION" - compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION" - compile "com.android.support:preference-v7:$SUPPORT_LIBRARY_VERSION" - compile "com.android.support:preference-v14:$SUPPORT_LIBRARY_VERSION" - compile "com.android.support:customtabs:$SUPPORT_LIBRARY_VERSION" + final support_library_version = '23.4.0' + compile "com.android.support:support-v4:$support_library_version" + compile "com.android.support:appcompat-v7:$support_library_version" + compile "com.android.support:cardview-v7:$support_library_version" + compile "com.android.support:design:$support_library_version" + compile "com.android.support:recyclerview-v7:$support_library_version" + compile "com.android.support:support-annotations:$support_library_version" + compile "com.android.support:preference-v7:$support_library_version" + compile "com.android.support:preference-v14:$support_library_version" + compile "com.android.support:customtabs:$support_library_version" // ReactiveX compile 'io.reactivex:rxandroid:1.2.0' @@ -115,15 +110,17 @@ dependencies { compile "com.squareup.okhttp3:okhttp:3.3.1" // REST - compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION" - compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION" - compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION" + final retrofit_version = '2.0.2' + compile "com.squareup.retrofit2:retrofit:$retrofit_version" + compile "com.squareup.retrofit2:converter-gson:$retrofit_version" + compile "com.squareup.retrofit2:adapter-rxjava:$retrofit_version" // IO compile 'com.squareup.okio:okio:1.8.0' // JSON compile 'com.google.code.gson:gson:2.6.2' + compile 'com.github.salomonbrys.kotson:kotson:2.2.1' // YAML compile 'org.yaml:snakeyaml:1.17' @@ -141,18 +138,18 @@ dependencies { compile 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0' // Database - compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION" - compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION" + final storio_version = '1.8.0' + compile "com.pushtorefresh.storio:sqlite:$storio_version" + compile "com.pushtorefresh.storio:sqlite-annotations:$storio_version" // Model View Presenter - compile "info.android15.nucleus:nucleus:$NUCLEUS_VERSION" - compile "info.android15.nucleus:nucleus-support-v4:$NUCLEUS_VERSION" - compile "info.android15.nucleus:nucleus-support-v7:$NUCLEUS_VERSION" + final nucleus_version = '3.0.0' + compile "info.android15.nucleus:nucleus:$nucleus_version" + compile "info.android15.nucleus:nucleus-support-v4:$nucleus_version" + compile "info.android15.nucleus:nucleus-support-v7:$nucleus_version" // Dependency injection - compile "com.google.dagger:dagger:$DAGGER_VERSION" - kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION" - provided 'org.glassfish:javax.annotation:10.0-b28' + compile "uy.kohesive.injekt:injekt-core:1.16.1" // Image library compile 'com.github.bumptech.glide:glide:3.7.0' @@ -174,13 +171,12 @@ dependencies { // Tests testCompile 'junit:junit:4.12' 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') { exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'org.apache.httpcomponents', module: 'httpclient' } - kaptTest "com.google.dagger:dagger-compiler:$DAGGER_VERSION" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 8cff1ff345..d703958aa7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -3,12 +3,10 @@ package eu.kanade.tachiyomi import android.app.Application import android.content.Context 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.annotation.ReportsCrashes import timber.log.Timber +import uy.kohesive.injekt.Injekt @ReportsCrashes( formUri = "http://tachiyomi.kanade.eu/crash_report", @@ -19,22 +17,13 @@ import timber.log.Timber ) open class App : Application() { - lateinit var component: AppComponent - private set - - lateinit var componentReflection: ComponentReflectionInjector - private set - var appTheme = 0 override fun onCreate() { super.onCreate() + Injekt.importModule(AppModule(this)) if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) - component = createAppComponent() - - componentReflection = ComponentReflectionInjector(AppComponent::class.java, component) - setupTheme() setupAcra() } @@ -43,10 +32,6 @@ open class App : Application() { appTheme = PreferencesHelper.getTheme(this) } - protected open fun createAppComponent(): AppComponent { - return AppComponentFactory.create(this) - } - protected open fun setupAcra() { ACRA.init(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt new file mode 100644 index 0000000000..6bc151803b --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt @@ -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() } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt index 160f4603ad..b775f595b4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt @@ -1,14 +1,13 @@ package eu.kanade.tachiyomi.data.backup +import com.github.salomonbrys.kotson.fromJson import com.google.gson.* -import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import eu.kanade.tachiyomi.data.backup.serializer.IdExclusion import eu.kanade.tachiyomi.data.backup.serializer.IntegerSerializer import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.* import java.io.* -import java.lang.reflect.Type import java.util.* /** @@ -191,8 +190,7 @@ class BackupManager(private val db: DatabaseHelper) { private fun restoreCategories(jsonCategories: JsonArray) { // Get categories from file and from db val dbCategories = db.getCategories().executeAsBlocking() - val backupCategories = getArrayOrEmpty(jsonCategories, - object : TypeToken>() {}.type) + val backupCategories = gson.fromJson>(jsonCategories) // Iterate over them 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. */ private fun restoreMangas(jsonMangas: JsonArray) { - val chapterToken = object : TypeToken>() {}.type - val mangaSyncToken = object : TypeToken>() {}.type - val categoriesNamesToken = object : TypeToken>() {}.type - for (backupManga in jsonMangas) { // Map every entry to objects val element = backupManga.asJsonObject - val manga = gson.fromJson(element.get(MANGA), Manga::class.java) - val chapters = getArrayOrEmpty(element.get(CHAPTERS), chapterToken) - val sync = getArrayOrEmpty(element.get(MANGA_SYNC), mangaSyncToken) - val categories = getArrayOrEmpty(element.get(CATEGORIES), categoriesNamesToken) + val manga = gson.fromJson(element.get(MANGA), MangaImpl::class.java) + val chapters = gson.fromJson>(element.get(CHAPTERS) ?: JsonArray()) + val sync = gson.fromJson>(element.get(MANGA_SYNC) ?: JsonArray()) + val categories = gson.fromJson>(element.get(CATEGORIES) ?: JsonArray()) // Restore everything related to this manga restoreManga(manga) @@ -340,7 +334,7 @@ class BackupManager(private val db: DatabaseHelper) { private fun restoreSyncForManga(manga: Manga, sync: List) { // Fix foreign keys with the current manga id for (mangaSync in sync) { - mangaSync.manga_id = manga.id + mangaSync.manga_id = manga.id!! } 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 getArrayOrEmpty(element: JsonElement?, type: Type): List { - return gson.fromJson>(element, type) ?: ArrayList() - } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt index b1bd29fc46..0069934be1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt @@ -10,6 +10,7 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery import com.pushtorefresh.storio.sqlite.queries.InsertQuery import com.pushtorefresh.storio.sqlite.queries.UpdateQuery 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_ID import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME @@ -44,7 +45,7 @@ class CategoryPutResolver : DefaultPutResolver() { class CategoryGetResolver : DefaultGetResolver() { - override fun mapFromCursor(cursor: Cursor) = Category().apply { + override fun mapFromCursor(cursor: Cursor): Category = CategoryImpl().apply { id = cursor.getInt(cursor.getColumnIndex(COL_ID)) name = cursor.getString(cursor.getColumnIndex(COL_NAME)) order = cursor.getInt(cursor.getColumnIndex(COL_ORDER)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt index bdc2e51d45..82914045f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt @@ -10,6 +10,7 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery import com.pushtorefresh.storio.sqlite.queries.InsertQuery import com.pushtorefresh.storio.sqlite.queries.UpdateQuery 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_DATE_FETCH import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_UPLOAD @@ -56,7 +57,7 @@ class ChapterPutResolver : DefaultPutResolver() { class ChapterGetResolver : DefaultGetResolver() { - override fun mapFromCursor(cursor: Cursor) = Chapter().apply { + override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply { id = cursor.getLong(cursor.getColumnIndex(COL_ID)) manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)) url = cursor.getString(cursor.getColumnIndex(COL_URL)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt index 2997f8db1e..ef2378b3bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt @@ -44,7 +44,7 @@ class HistoryPutResolver : DefaultPutResolver() { class HistoryGetResolver : DefaultGetResolver() { - override fun mapFromCursor(cursor: Cursor) = History().apply { + override fun mapFromCursor(cursor: Cursor): History = History().apply { id = cursor.getLong(cursor.getColumnIndex(COL_ID)) chapter_id = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_ID)) last_read = cursor.getLong(cursor.getColumnIndex(COL_LAST_READ)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt index b0b11e2fb5..99cfdd47cc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt @@ -42,7 +42,7 @@ class MangaCategoryPutResolver : DefaultPutResolver() { class MangaCategoryGetResolver : DefaultGetResolver() { - override fun mapFromCursor(cursor: Cursor) = MangaCategory().apply { + override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply { id = cursor.getLong(cursor.getColumnIndex(COL_ID)) manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)) category_id = cursor.getInt(cursor.getColumnIndex(COL_CATEGORY_ID)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaSyncTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaSyncTypeMapping.kt index b3c7cc92bd..b02ae16a57 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaSyncTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaSyncTypeMapping.kt @@ -10,6 +10,7 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery import com.pushtorefresh.storio.sqlite.queries.InsertQuery import com.pushtorefresh.storio.sqlite.queries.UpdateQuery 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_LAST_CHAPTER_READ import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable.COL_MANGA_ID @@ -54,7 +55,7 @@ class MangaSyncPutResolver : DefaultPutResolver() { class MangaSyncGetResolver : DefaultGetResolver() { - override fun mapFromCursor(cursor: Cursor) = MangaSync().apply { + override fun mapFromCursor(cursor: Cursor): MangaSync = MangaSyncImpl().apply { id = cursor.getLong(cursor.getColumnIndex(COL_ID)) manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)) sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt index e98fa6da8a..0d410a2b12 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt @@ -10,6 +10,7 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery import com.pushtorefresh.storio.sqlite.queries.InsertQuery import com.pushtorefresh.storio.sqlite.queries.UpdateQuery 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_AUTHOR import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS @@ -66,7 +67,7 @@ class MangaPutResolver : DefaultPutResolver() { open class MangaGetResolver : DefaultGetResolver() { - override fun mapFromCursor(cursor: Cursor) = Manga().apply { + override fun mapFromCursor(cursor: Cursor): Manga = MangaImpl().apply { id = cursor.getLong(cursor.getColumnIndex(COL_ID)) source = cursor.getInt(cursor.getColumnIndex(COL_SOURCE)) url = cursor.getString(cursor.getColumnIndex(COL_URL)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.java deleted file mode 100644 index e6de5e1ca3..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.java +++ /dev/null @@ -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(); - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt new file mode 100644 index 0000000000..c88d932610 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt @@ -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 } + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt new file mode 100644 index 0000000000..65a004477b --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt @@ -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() + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.java deleted file mode 100644 index abe111f25b..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.java +++ /dev/null @@ -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 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 getPages() { - return pages; - } - - public void setPages(List pages) { - this.pages = pages; - } - - public boolean isDownloaded() { - return status == Download.DOWNLOADED; - } - - public boolean isRecognizedNumber() { - return chapter_number >= 0f; - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt new file mode 100644 index 0000000000..f0294c2758 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt @@ -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 + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt new file mode 100644 index 0000000000..7120e583b0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt @@ -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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.java deleted file mode 100644 index 78d769c088..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.java +++ /dev/null @@ -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; - } -} - diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt new file mode 100644 index 0000000000..5d5c89c353 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt @@ -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 + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.java deleted file mode 100644 index 7068a42259..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.java +++ /dev/null @@ -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(); - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt new file mode 100644 index 0000000000..45709b2564 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -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 + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.java deleted file mode 100644 index 96230b54a7..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.java +++ /dev/null @@ -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; - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt new file mode 100644 index 0000000000..305d5ef9cf --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt @@ -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 + } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.java deleted file mode 100644 index 609a466e44..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt new file mode 100644 index 0000000000..bf584ba9c8 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt @@ -0,0 +1,3 @@ +package eu.kanade.tachiyomi.data.database.models + +class MangaChapter(val manga: Manga, val chapter: Chapter) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.java deleted file mode 100644 index 67a59a404a..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt new file mode 100644 index 0000000000..7fed020fc4 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt new file mode 100644 index 0000000000..000618a3af --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt @@ -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() + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.java deleted file mode 100644 index 0e3beb98e8..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.kt new file mode 100644 index 0000000000..c1e4664fd3 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.kt @@ -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 + } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSyncImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSyncImpl.kt new file mode 100644 index 0000000000..95d3bc4a77 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSyncImpl.kt @@ -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 + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt index 8251c04bb2..f2aa6d2d56 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt @@ -1,6 +1,5 @@ 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.RawQuery import eu.kanade.tachiyomi.data.database.DbProvider @@ -34,80 +33,6 @@ interface ChapterQueries : DbProvider { .withGetResolver(MangaChapterGetResolver.INSTANCE) .prepare() - fun getNextChapter(chapter: Chapter): PreparedGetObject { - // 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 { - // 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 insertChapters(chapters: List) = db.put().objects(chapters).prepare() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt index 6eecaf273d..a87afe78c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt @@ -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.HistoryGetResolver import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver -import eu.kanade.tachiyomi.data.database.models.MangaChapter import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory class MangaChapterHistoryGetResolver : DefaultGetResolver() { @@ -46,10 +45,7 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver() manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl")) chapter.id = history.chapter_id - // Create mangaChapter object - val mangaChapter = MangaChapter(manga, chapter) - // Return result - return MangaChapterHistory(mangaChapter, history) + return MangaChapterHistory(manga, chapter, history) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index f4360cd95d..9bff956837 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -16,10 +16,7 @@ import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.online.OnlineSource -import eu.kanade.tachiyomi.util.DiskUtils -import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator -import eu.kanade.tachiyomi.util.UrlUtil -import eu.kanade.tachiyomi.util.saveImageTo +import eu.kanade.tachiyomi.util.* import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -27,12 +24,17 @@ import rx.schedulers.Schedulers import rx.subjects.BehaviorSubject import rx.subjects.PublishSubject import timber.log.Timber +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.io.File import java.io.FileReader 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() @@ -270,10 +272,8 @@ class DownloadManager(private val context: Context, private val sourceManager: S } page } - .retryWhen { - it.zipWith(Observable.range(1, 3)) { errors, retries -> retries } - .flatMap { retries -> Observable.timer((retries * 2).toLong(), TimeUnit.SECONDS) } - } + // Retry 3 times, waiting 2, 4 and 8 seconds between attempts. + .retryWhen(RetryWithDelay(3, { (2 shl it - 1) * 1000 })) } // Public method to get the image from the filesystem. It does NOT provide any way to download the image diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt index aa85b6c6a3..98c64113a6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt @@ -7,14 +7,13 @@ import android.os.IBinder import android.os.PowerManager import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork -import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.toast import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy class DownloadService : Service() { @@ -29,8 +28,8 @@ class DownloadService : Service() { } } - @Inject lateinit var downloadManager: DownloadManager - @Inject lateinit var preferences: PreferencesHelper + val downloadManager: DownloadManager by injectLazy() + val preferences: PreferencesHelper by injectLazy() private var wakeLock: PowerManager.WakeLock? = null private var networkChangeSubscription: Subscription? = null @@ -39,7 +38,6 @@ class DownloadService : Service() { override fun onCreate() { super.onCreate() - App.get(this).component.inject(this) createWakeLock() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt index aadc83f5e0..6e1862373c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt @@ -7,28 +7,26 @@ import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.module.GlideModule -import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.network.NetworkHelper +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.io.InputStream -import javax.inject.Inject /** * Class used to update Glide module settings */ class AppGlideModule : GlideModule { - @Inject lateinit var networkHelper: NetworkHelper - override fun applyOptions(context: Context, builder: GlideBuilder) { // Set the cache size of Glide to 15 MiB builder.setDiskCache(InternalCacheDiskCacheFactory(context, 15 * 1024 * 1024)) } override fun registerComponents(context: Context, glide: Glide) { - App.get(context).component.inject(this) - glide.register(GlideUrl::class.java, InputStream::class.java, - OkHttpUrlLoader.Factory(networkHelper.client)) + val networkFactory = OkHttpUrlLoader.Factory(Injekt.get().client) + + glide.register(GlideUrl::class.java, InputStream::class.java, networkFactory) glide.register(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt index d5865180ff..dbfa768f87 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt @@ -5,14 +5,13 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.data.DataFetcher import com.bumptech.glide.load.model.* 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.database.models.Manga import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.online.OnlineSource +import uy.kohesive.injekt.injectLazy import java.io.File 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. @@ -30,12 +29,12 @@ class MangaModelLoader(context: Context) : StreamModelLoader { /** * Cover cache where persistent covers are stored. */ - @Inject lateinit var coverCache: CoverCache + val coverCache: CoverCache by injectLazy() /** * Source manager. */ - @Inject lateinit var sourceManager: SourceManager + val sourceManager: SourceManager by injectLazy() /** * Base network loader. @@ -54,10 +53,6 @@ class MangaModelLoader(context: Context) : StreamModelLoader { */ private val cachedHeaders = hashMapOf() - init { - App.get(context).component.inject(this) - } - /** * Factory class for creating [MangaModelLoader] instances. */ @@ -88,7 +83,7 @@ class MangaModelLoader(context: Context) : StreamModelLoader { // Obtain the request url and the file for this url from the LRU cache, or calculate it // and add them to the cache. 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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index a56af3a532..b36acef0ed 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -10,12 +10,12 @@ import android.os.PowerManager import android.support.v4.app.NotificationCompat import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork -import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.Constants import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category 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.source.SourceManager import eu.kanade.tachiyomi.data.source.online.OnlineSource @@ -24,9 +24,9 @@ import eu.kanade.tachiyomi.util.* import rx.Observable import rx.Subscription import rx.schedulers.Schedulers +import uy.kohesive.injekt.injectLazy import java.util.* 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 @@ -41,17 +41,17 @@ class LibraryUpdateService : Service() { /** * Database helper. */ - @Inject lateinit var db: DatabaseHelper + val db: DatabaseHelper by injectLazy() /** * Source manager. */ - @Inject lateinit var sourceManager: SourceManager + val sourceManager: SourceManager by injectLazy() /** * Preferences. */ - @Inject lateinit var preferences: PreferencesHelper + val preferences: PreferencesHelper by injectLazy() /** * Wake lock that will be held until the service is destroyed. @@ -126,7 +126,6 @@ class LibraryUpdateService : Service() { */ override fun onCreate() { super.onCreate() - App.get(this).component.inject(this) createAndAcquireWakeLock() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/MangaSyncService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/MangaSyncService.kt index c5d8dbd266..ae653b0f0f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/MangaSyncService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/MangaSyncService.kt @@ -2,23 +2,18 @@ package eu.kanade.tachiyomi.data.mangasync import android.content.Context import android.support.annotation.CallSuper -import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.data.database.models.MangaSync import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper import okhttp3.OkHttpClient import rx.Completable import rx.Observable -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy abstract class MangaSyncService(private val context: Context, val id: Int) { - @Inject lateinit var preferences: PreferencesHelper - @Inject lateinit var networkService: NetworkHelper - - init { - App.get(context).component.inject(this) - } + val preferences: PreferencesHelper by injectLazy() + val networkService: NetworkHelper by injectLazy() open val client: OkHttpClient get() = networkService.client diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/UpdateMangaSyncService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/UpdateMangaSyncService.kt index b11ba75464..84181b555a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/UpdateMangaSyncService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/UpdateMangaSyncService.kt @@ -4,25 +4,23 @@ import android.app.Service import android.content.Context import android.content.Intent import android.os.IBinder -import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.MangaSync import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import rx.subscriptions.CompositeSubscription -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy class UpdateMangaSyncService : Service() { - @Inject lateinit var syncManager: MangaSyncManager - @Inject lateinit var db: DatabaseHelper + val syncManager: MangaSyncManager by injectLazy() + val db: DatabaseHelper by injectLazy() private lateinit var subscriptions: CompositeSubscription override fun onCreate() { super.onCreate() - App.get(this).component.inject(this) subscriptions = CompositeSubscription() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt index 29e08da1da..3b589972ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt @@ -97,8 +97,8 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont .flatMap { Observable.from(it.select("entry")) } .filter { it.select("type").text() != "Novel" } .map { - MangaSync.create(this).apply { - title = it.selectText("title") + MangaSync.create(id).apply { + title = it.selectText("title")!! remote_id = it.selectInt("id") 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()) } .flatMap { Observable.from(it.select("manga")) } .map { - MangaSync.create(this).apply { - title = it.selectText("series_title") + MangaSync.create(id).apply { + title = it.selectText("series_title")!! remote_id = it.selectInt("series_mangadb_id") last_chapter_read = it.selectInt("my_read_chapters") status = it.selectInt("my_status") diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 3d09756fff..c0eaffc84f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -54,8 +54,6 @@ class PreferenceKeys(context: Context) { 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 enabledLanguages = context.getString(R.string.pref_source_languages) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 4e31a5c047..ef2e2cce43 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -101,8 +101,6 @@ class PreferencesHelper(private val context: Context) { fun lastVersionCode() = rxPrefs.getInteger("last_version_code", 0) - fun seamlessMode() = prefs.getBoolean(keys.seamlessMode, true) - fun catalogueAsList() = rxPrefs.getBoolean(keys.catalogueAsList, false) fun enabledLanguages() = rxPrefs.getStringSet(keys.enabledLanguages, setOf("EN")) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.java index ecd1bbe448..898a7e7a8b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.java @@ -1,9 +1,7 @@ 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.ui.reader.ReaderChapter; import rx.subjects.PublishSubject; public class Page implements ProgressListener { @@ -11,7 +9,7 @@ public class Page implements ProgressListener { private int pageNumber; private String url; private String imageUrl; - private transient Chapter chapter; + private transient ReaderChapter chapter; private transient String imagePath; private transient volatile int status; private transient volatile int progress; @@ -90,16 +88,12 @@ public class Page implements ProgressListener { this.statusSubject = subject; } - public Chapter getChapter() { + public ReaderChapter getChapter() { return chapter; } - public void setChapter(Chapter chapter) { + public void setChapter(ReaderChapter chapter) { this.chapter = chapter; } - public boolean isLastPage() { - List chapterPages = chapter.getPages(); - return chapterPages.size() -1 == pageNumber; - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt index f482b1121d..6d286e5263 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.source.online import android.content.Context -import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.database.models.Chapter 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.model.MangasPage import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.util.UrlUtil import okhttp3.* import rx.Observable -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy /** * A simple implementation for sources from a website. @@ -28,17 +28,17 @@ abstract class OnlineSource(context: Context) : Source { /** * Network service. */ - @Inject lateinit var network: NetworkHelper + val network: NetworkHelper by injectLazy() /** * Chapter cache. */ - @Inject lateinit var chapterCache: ChapterCache + val chapterCache: ChapterCache by injectLazy() /** * 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 @@ -61,11 +61,6 @@ abstract class OnlineSource(context: Context) : Source { open val client: OkHttpClient get() = network.client - init { - // Inject dependencies. - App.get(context).component.inject(this) - } - /** * 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. open fun parseChapterNumber(chapter: Chapter) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt index 78a7af5816..80e11fce8c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt @@ -26,8 +26,7 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) { override fun popularMangaParse(response: Response, page: MangasPage) { val document = Jsoup.parse(response.body().string()) for (element in document.select(popularMangaSelector())) { - Manga().apply { - source = this@ParsedOnlineSource.id + Manga.create(id).apply { popularMangaFromElement(element, 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) { val document = Jsoup.parse(response.body().string()) for (element in document.select(searchMangaSelector())) { - Manga().apply { - source = this@ParsedOnlineSource.id + Manga.create(id).apply { searchMangaFromElement(element, this) page.mangas.add(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt index 00fe76bb6b..22b145e503 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt @@ -54,10 +54,9 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con override fun popularMangaParse(response: Response, page: MangasPage) { val document = Jsoup.parse(response.body().string()) for (element in document.select(map.popular.manga_css)) { - Manga().apply { - source = this@YamlOnlineSource.id + Manga.create(id).apply { title = element.text() - setUrl(element.attr("href")) + setUrlWithoutDomain(element.attr("href")) 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) { val document = Jsoup.parse(response.body().string()) for (element in document.select(map.search.manga_css)) { - Manga().apply { - source = this@YamlOnlineSource.id + Manga.create(id).apply { title = element.text() - setUrl(element.attr("href")) + setUrlWithoutDomain(element.attr("href")) page.mangas.add(this) } } @@ -123,7 +121,7 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con val chapter = Chapter.create() element.select(title).first().let { chapter.name = it.text() - chapter.setUrl(it.attr("href")) + chapter.setUrlWithoutDomain(it.attr("href")) } val dateElement = element.select(date?.select).first() chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt index 845fa17a9c..ac04052da5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt @@ -62,8 +62,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex override fun popularMangaParse(response: Response, page: MangasPage) { val document = Jsoup.parse(response.body().string()) for (element in document.select(popularMangaSelector())) { - Manga().apply { - source = this@Batoto.id + Manga.create(id).apply { popularMangaFromElement(element, 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) { element.select("a[href^=http://bato.to]").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { val document = Jsoup.parse(response.body().string()) for (element in document.select(searchMangaSelector())) { - Manga().apply { - source = this@Batoto.id + Manga.create(id).apply { searchMangaFromElement(element, 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) { 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.date_upload = element.select("td").getOrNull(4)?.let { parseDateFromElement(it) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt index 5a45feec86..1637124259 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt @@ -35,7 +35,7 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaFromElement(element: Element, manga: Manga) { element.select("td a:eq(0)").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { val urlElement = element.select("a").first() - chapter.setUrl(urlElement.attr("href")) + chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.name = urlElement.text() chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { SimpleDateFormat("MM/dd/yyyy").parse(it).time diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt index 3884d77b1c..def557ecdc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt @@ -29,7 +29,7 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont override fun popularMangaFromElement(element: Element, manga: Manga) { element.select("a.title").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { element.select("a.series_preview").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { val urlElement = element.select("a.tips").first() - chapter.setUrl(urlElement.attr("href")) + chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.name = urlElement.text() chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt index 1abd0498ee..fb87e9dc5f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt @@ -27,7 +27,7 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaFromElement(element: Element, manga: Manga) { element.select("div.title > a").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { element.select("a.manga_info").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { val urlElement = element.select("a").first() - chapter.setUrl(urlElement.attr("href")) + chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.name = urlElement.text() chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt index beabf40383..be6d759371 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt @@ -28,7 +28,7 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc override fun popularMangaFromElement(element: Element, manga: Manga) { element.select("div.title > h2 > a").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { element.select("div.title > h2 > a").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { val urlElement = element.select("a").first() - chapter.setUrl(urlElement.attr("href")) + chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.name = urlElement.select("span.val").text() chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt index 20a83ebe8a..b93cc08670 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt @@ -29,7 +29,7 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaFromElement(element: Element, manga: Manga) { element.select("h2 > a").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { val urlElement = element.select("a").first() - chapter.setUrl(urlElement.attr("href")) + chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.name = urlElement.text() chapter.date_upload = element.select("div.date").first()?.text()?.let { SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt index 59703d485e..25abf00828 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt @@ -30,7 +30,7 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaFromElement(element: Element, manga: Manga) { element.select("h3 > a").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { 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.date_upload = element.select("td:eq(1)").first()?.text()?.let { SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt index 42bcd180fb..7bb6cc50fb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt @@ -30,7 +30,7 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaFromElement(element: Element, manga: Manga) { element.select("h3 > a").first().let { - manga.setUrl(it.attr("href")) + manga.setUrlWithoutDomain(it.attr("href")) 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) { 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.date_upload = element.select("td:eq(1)").first()?.text()?.let { SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt index 1084260114..3dad020a7e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt @@ -8,7 +8,6 @@ import android.content.Intent import android.net.Uri import android.os.AsyncTask import android.support.v4.app.NotificationCompat -import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.Constants import eu.kanade.tachiyomi.R 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.saveTo import timber.log.Timber +import uy.kohesive.injekt.injectLazy import java.io.File -import javax.inject.Inject class UpdateDownloader(private val context: Context) : AsyncTask() { @@ -40,7 +39,7 @@ class UpdateDownloader(private val context: Context) : } } - @Inject lateinit var network: NetworkHelper + val network: NetworkHelper by injectLazy() /** * Default download dir @@ -59,9 +58,6 @@ class UpdateDownloader(private val context: Context) : private val notificationId: Int get() = Constants.NOTIFICATION_UPDATER_ID - init { - App.get(context).component.inject(this) - } /** * Class containing download result diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/AppComponentFactory.java b/app/src/main/java/eu/kanade/tachiyomi/injection/AppComponentFactory.java deleted file mode 100644 index 6bcb6a3de3..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/AppComponentFactory.java +++ /dev/null @@ -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(); - } -} - - diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/ComponentReflectionInjector.java b/app/src/main/java/eu/kanade/tachiyomi/injection/ComponentReflectionInjector.java deleted file mode 100644 index e5934b78e2..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/ComponentReflectionInjector.java +++ /dev/null @@ -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: - *
{@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
- * }
- * - * @param a type of dagger 2 component. - */ -public final class ComponentReflectionInjector { - - private static final ConcurrentHashMap, HashMap, Method>> cache = new ConcurrentHashMap<>(); - - private final Class componentClass; - private final T component; - private final HashMap, Method> methods; - - public ComponentReflectionInjector(Class 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, Method> getMethods(Class componentClass) { - HashMap, 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; - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt deleted file mode 100644 index 6084c44ed2..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt +++ /dev/null @@ -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 - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.kt deleted file mode 100644 index ae9e82f425..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.kt +++ /dev/null @@ -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 - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.kt b/app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.kt deleted file mode 100644 index 08512e612c..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.kt +++ /dev/null @@ -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) - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupPresenter.kt index f943f997ca..7ae7c7a58d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupPresenter.kt @@ -8,9 +8,9 @@ import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers +import uy.kohesive.injekt.injectLazy import java.io.File import java.io.InputStream -import javax.inject.Inject /** * Presenter of [BackupFragment]. @@ -20,7 +20,7 @@ class BackupPresenter : BasePresenter() { /** * Database. */ - @Inject lateinit var db: DatabaseHelper + val db: DatabaseHelper by injectLazy() /** * Backup manager. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt index 6f83dfdec2..90dc661713 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt @@ -12,7 +12,6 @@ abstract class BaseRxActivity

> : NucleusAppCompatActivity

> : NucleusSupportFragment

( setPresenterFactory { superFactory.createPresenter().apply { val app = activity.application as App - app.componentReflection.inject(this) context = app.applicationContext } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueAdapter.kt index 3cb42d4340..a71f2a4df9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueAdapter.kt @@ -56,7 +56,7 @@ class CatalogueAdapter(val fragment: CatalogueFragment) : FlexibleAdapter(), FlexibleViewHold * @return the holder of the manga or null if it's not bound. */ private fun getHolder(manga: Manga): CatalogueGridHolder? { - return catalogue_grid.findViewHolderForItemId(manga.id) as? CatalogueGridHolder + return catalogue_grid.findViewHolderForItemId(manga.id!!) as? CatalogueGridHolder } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index 7178bae36b..a22c95edfb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -19,7 +19,7 @@ import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import rx.subjects.PublishSubject import timber.log.Timber -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy /** * Presenter of [CatalogueFragment]. @@ -29,22 +29,22 @@ class CataloguePresenter : BasePresenter() { /** * Source manager. */ - @Inject lateinit var sourceManager: SourceManager + val sourceManager: SourceManager by injectLazy() /** * Database. */ - @Inject lateinit var db: DatabaseHelper + val db: DatabaseHelper by injectLazy() /** * Preferences. */ - @Inject lateinit var prefs: PreferencesHelper + val prefs: PreferencesHelper by injectLazy() /** * Cover cache. */ - @Inject lateinit var coverCache: CoverCache + val coverCache: CoverCache by injectLazy() /** * Enabled sources. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt index 599621f813..d2dbd05b9f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt @@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import rx.android.schedulers.AndroidSchedulers -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy /** * Presenter of CategoryActivity. @@ -17,7 +17,7 @@ class CategoryPresenter : BasePresenter() { /** * Used to connect to database */ - @Inject lateinit var db: DatabaseHelper + val db: DatabaseHelper by injectLazy() /** * List containing categories diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt index 0919e7ccf2..6b2412b51b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt @@ -35,7 +35,7 @@ class DownloadAdapter(private val context: Context) : FlexibleAdapter() { * @return the holder of the download or null if it's not bound. */ private fun getHolder(download: Download): DownloadHolder? { - return recycler.findViewHolderForItemId(download.chapter.id) as? DownloadHolder + return recycler.findViewHolderForItemId(download.chapter.id!!) as? DownloadHolder } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt index a575a89245..bef58dbf60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt @@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import rx.Observable import timber.log.Timber -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy /** * Presenter of [DownloadFragment]. @@ -24,7 +24,7 @@ class DownloadPresenter : BasePresenter() { /** * Download manager. */ - @Inject lateinit var downloadManager: DownloadManager + val downloadManager: DownloadManager by injectLazy() /** * Property to get the queue from the download manager. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index 58124602d3..c16e86af31 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -49,7 +49,7 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : * @return an identifier for the item. */ 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. */ override fun filterObject(manga: Manga, query: String): Boolean = with(manga) { - title != null && title.toLowerCase().contains(query) || - author != null && author.toLowerCase().contains(query) + title.toLowerCase().contains(query) || + author != null && author!!.toLowerCase().contains(query) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 7413b446fb..83c8682c81 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -16,10 +16,10 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import rx.subjects.BehaviorSubject +import uy.kohesive.injekt.injectLazy import java.io.IOException import java.io.InputStream import java.util.* -import javax.inject.Inject /** * Presenter of [LibraryFragment]. @@ -49,27 +49,27 @@ class LibraryPresenter : BasePresenter() { /** * Database. */ - @Inject lateinit var db: DatabaseHelper + val db: DatabaseHelper by injectLazy() /** * Preferences. */ - @Inject lateinit var preferences: PreferencesHelper + val preferences: PreferencesHelper by injectLazy() /** * Cover cache. */ - @Inject lateinit var coverCache: CoverCache + val coverCache: CoverCache by injectLazy() /** * Source manager. */ - @Inject lateinit var sourceManager: SourceManager + val sourceManager: SourceManager by injectLazy() /** * Download manager. */ - @Inject lateinit var downloadManager: DownloadManager + val downloadManager: DownloadManager by injectLazy() companion object { /** @@ -279,7 +279,7 @@ class LibraryPresenter : BasePresenter() { @Throws(IOException::class) fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean { if (manga.thumbnail_url != null && manga.favorite) { - coverCache.copyToCache(manga.thumbnail_url, inputStream) + coverCache.copyToCache(manga.thumbnail_url!!, inputStream) return true } return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 14bd33423f..2fd7e6d554 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -5,7 +5,6 @@ import android.os.Bundle import android.support.v4.app.Fragment import android.support.v4.view.GravityCompat import android.view.MenuItem -import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper 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 kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.toolbar.* -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy class MainActivity : BaseActivity() { - @Inject lateinit var preferences: PreferencesHelper + val preferences: PreferencesHelper by injectLazy() override fun onCreate(savedState: Bundle?) { setAppTheme() @@ -34,8 +33,6 @@ class MainActivity : BaseActivity() { return } - App.get(this).component.inject(this) - // Inflate activity_main.xml. setContentView(R.layout.activity_main) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 1b1ceff5f5..b322360e1a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent import eu.kanade.tachiyomi.util.SharedData import rx.Observable import rx.Subscription -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy /** * Presenter of [MangaActivity]. @@ -19,12 +19,12 @@ class MangaPresenter : BasePresenter() { /** * Database helper. */ - @Inject lateinit var db: DatabaseHelper + val db: DatabaseHelper by injectLazy() /** * Manga sync manager. */ - @Inject lateinit var syncManager: MangaSyncManager + val syncManager: MangaSyncManager by injectLazy() /** * Manga associated with this instance. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterModel.kt new file mode 100644 index 0000000000..a3eed5ed6f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterModel.kt @@ -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 + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt index 9282d425b1..dd2ffc7c06 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt @@ -3,10 +3,9 @@ package eu.kanade.tachiyomi.ui.manga.chapter import android.view.ViewGroup import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.util.inflate -class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter() { +class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter() { init { setHasStableIds(true) @@ -30,10 +29,10 @@ class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter) { + fun setItems(chapters: List) { mItems = chapters notifyDataSetChanged() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt index 2a053034d0..c7bd132e13 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt @@ -134,8 +134,7 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac presenter.setDownloadedFilter(item.isChecked) } R.id.action_filter_empty -> { - presenter.setReadFilter(false) - presenter.setDownloadedFilter(false) + presenter.removeFilters() activity.supportInvalidateOptionsMenu() } R.id.action_sort -> presenter.revertSortOrder() @@ -150,7 +149,7 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac setDownloadedFilter() } - fun onNextChapters(chapters: List) { + fun onNextChapters(chapters: List) { // If the list is empty, fetch chapters from source if the conditions are met // We use presenter chapters instead because they are always unfiltered if (presenter.chapters.isEmpty()) @@ -206,7 +205,7 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac // Save the new display mode presenter.setDisplayMode(itemView.id) // Refresh ui - adapter.notifyDataSetChanged() + adapter.notifyItemRangeChanged(0, adapter.itemCount) true } .show() @@ -271,7 +270,7 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac } 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 { @@ -309,7 +308,7 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac actionMode = null } - fun getSelectedChapters(): List { + fun getSelectedChapters(): List { return adapter.selectedItems.map { adapter.getItem(it) } } @@ -322,27 +321,27 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac setContextTitle(adapter.selectedItemCount) } - fun markAsRead(chapters: List) { + fun markAsRead(chapters: List) { presenter.markChaptersRead(chapters, true) if (presenter.preferences.removeAfterMarkedAsRead()) { deleteChapters(chapters) } } - fun markAsUnread(chapters: List) { + fun markAsUnread(chapters: List) { presenter.markChaptersRead(chapters, false) } - fun markPreviousAsRead(chapter: Chapter) { + fun markPreviousAsRead(chapter: ChapterModel) { presenter.markPreviousChaptersAsRead(chapter) } - fun downloadChapters(chapters: List) { + fun downloadChapters(chapters: List) { destroyActionModeIfNeeded() presenter.downloadChapters(chapters) } - fun deleteChapters(chapters: List) { + fun deleteChapters(chapters: List) { destroyActionModeIfNeeded() DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG) presenter.deleteChapters(chapters) @@ -350,7 +349,7 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac fun onChaptersDeleted() { dismissDeletingDialog() - adapter.notifyDataSetChanged() + adapter.notifyItemRangeChanged(0, adapter.itemCount) } fun onChaptersDeletedError(error: Throwable) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt index 389ce33e26..e2cbe0a1ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt @@ -1,10 +1,8 @@ package eu.kanade.tachiyomi.ui.manga.chapter -import android.content.Context import android.view.View import android.widget.PopupMenu 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.download.model.Download import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder @@ -26,7 +24,7 @@ class ChaptersHolder( private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' }) private val df = DateFormat.getDateInstance(DateFormat.SHORT) - private var item: Chapter? = null + private var item: ChapterModel? = null init { // 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) } } } - fun onSetValues(chapter: Chapter, manga: Manga?) = with(view) { + fun onSetValues(chapter: ChapterModel, manga: Manga?) = with(view) { item = chapter - val name: String - when (manga?.displayMode) { + chapter_title.text = when (manga?.displayMode) { Manga.DISPLAY_NUMBER -> { 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) if (chapter.date_upload > 0) { @@ -57,31 +52,26 @@ class ChaptersHolder( chapter_date.text = "" } - if (!chapter.read && chapter.last_page_read > 0) { - chapter_pages.text = context.getString(R.string.chapter_progress, chapter.last_page_read + 1) + chapter_pages.text = if (!chapter.read && chapter.last_page_read > 0) { + context.getString(R.string.chapter_progress, chapter.last_page_read + 1) } else { - chapter_pages.text = "" + "" } notifyStatus(chapter.status) } - fun notifyStatus(status: Int) = with(view) { + fun notifyStatus(status: Int) = with(view.download_text) { when (status) { - Download.QUEUE -> download_text.setText(R.string.chapter_queued) - Download.DOWNLOADING -> download_text.setText(R.string.chapter_downloading) - Download.DOWNLOADED -> download_text.setText(R.string.chapter_downloaded) - Download.ERROR -> download_text.setText(R.string.chapter_error) - else -> download_text.text = "" + Download.QUEUE -> setText(R.string.chapter_queued) + Download.DOWNLOADING -> setText(R.string.chapter_downloading) + Download.DOWNLOADED -> setText(R.string.chapter_downloaded) + Download.ERROR -> setText(R.string.chapter_error) + else -> text = "" } } - fun onProgressChange(context: Context, downloaded: Int, total: Int) { - view.download_text.text = context.getString( - R.string.chapter_downloading_progress, downloaded, total) - } - - private fun showPopupMenu(view: View) = item?.let { item -> + private fun showPopupMenu(view: View) = item?.let { chapter -> // Create a PopupMenu, giving it the clicked view for an anchor val popup = PopupMenu(view.context, view) @@ -89,32 +79,35 @@ class ChaptersHolder( popup.menuInflater.inflate(R.menu.chapter_single, popup.menu) // 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_delete).isVisible = true } // 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 } // 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 } // Set a listener so we are notified if a menu item is clicked popup.setOnMenuItemClickListener { menuItem -> - val chapter = listOf(item) + val chapterList = listOf(chapter) - when (menuItem.itemId) { - R.id.action_download -> adapter.fragment.downloadChapters(chapter) - R.id.action_delete -> adapter.fragment.deleteChapters(chapter) - R.id.action_mark_as_read -> adapter.fragment.markAsRead(chapter) - R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(chapter) - R.id.action_mark_previous_as_read -> adapter.fragment.markPreviousAsRead(item) + with(adapter.fragment) { + when (menuItem.itemId) { + R.id.action_download -> downloadChapters(chapterList) + R.id.action_delete -> deleteChapters(chapterList) + R.id.action_mark_as_read -> markAsRead(chapterList) + R.id.action_mark_as_unread -> markAsUnread(chapterList) + R.id.action_mark_previous_as_read -> markPreviousAsRead(chapter) + } } + true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index dce4621452..e8a50d08e8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -20,108 +20,197 @@ import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import rx.subjects.PublishSubject import timber.log.Timber -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy +/** + * Presenter of [ChaptersFragment]. + */ class ChaptersPresenter : BasePresenter() { - @Inject lateinit var db: DatabaseHelper - @Inject lateinit var sourceManager: SourceManager - @Inject lateinit var preferences: PreferencesHelper - @Inject lateinit var downloadManager: DownloadManager + /** + * Database helper. + */ + 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 private set + /** + * Source of the manga. + */ lateinit var source: Source private set - lateinit var chapters: List + /** + * List of chapters of the manga. It's always unfiltered and unsorted. + */ + lateinit var chapters: List private set - lateinit var chaptersSubject: PublishSubject> + /** + * Subject of list of chapters to allow updating the view without going to DB. + */ + val chaptersSubject by lazy { PublishSubject.create>() } + + /** + * Whether the chapter list has been requested to the source. + */ + var hasRequested = false private set - var hasRequested: Boolean = false - private set + companion object { + /** + * 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 - private val CHAPTER_STATUS_CHANGES = 3 + /** + * Id of the restartable which requests an updated list of chapters to the source. + */ + 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?) { super.onCreate(savedState) - chaptersSubject = PublishSubject.create() - - startableLatestCache(DB_CHAPTERS, - { getDbChaptersObs() }, + startableLatestCache(GET_CHAPTERS, + // On each subject emission, apply filters and sort then update the view. + { chaptersSubject + .flatMap { applyChapterFilters(it) } + .observeOn(AndroidSchedulers.mainThread()) }, { view, chapters -> view.onNextChapters(chapters) }) startableFirst(FETCH_CHAPTERS, - { getOnlineChaptersObs() }, + { getRemoteChaptersObservable() }, { view, result -> view.onFetchChaptersDone() }, { view, error -> view.onFetchChaptersError(error) }) startableLatestCache(CHAPTER_STATUS_CHANGES, - { getChapterStatusObs() }, + { getChapterStatusObservable() }, { view, download -> view.onChapterStatusChange(download) }, { 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 Observable.just(manga) .subscribeLatestCache({ view, manga -> view.onNextManga(manga) }) + // Find the source for this manga. 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() + .map { chapters -> + // Convert every chapter to a model. + chapters.map { it.toModel() } + } .doOnNext { chapters -> + // Store the last emission this.chapters = chapters - SharedData.get(ChapterCountEvent::class.java)?.emit(chapters.size) - for (chapter in chapters) { - setChapterStatus(chapter) - } + + // Listen for download 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) }) } + /** + * 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() { hasRequested = true start(FETCH_CHAPTERS) } + /** + * Updates the UI after applying the filters. + */ private fun refreshChapters() { chaptersSubject.onNext(chapters) } - fun getOnlineChaptersObs(): Observable> { - return source.fetchChapterList(manga) - .subscribeOn(Schedulers.io()) - .map { syncChaptersWithSource(db, it, manga, source) } - .observeOn(AndroidSchedulers.mainThread()) - } + /** + * Returns an observable that updates the chapter list with the latest from the source. + */ + fun getRemoteChaptersObservable() = source.fetchChapterList(manga) + .subscribeOn(Schedulers.io()) + .map { syncChaptersWithSource(db, it, manga, source) } + .observeOn(AndroidSchedulers.mainThread()) - fun getDbChaptersObs(): Observable> { - return chaptersSubject - .flatMap { applyChapterFilters(it) } - .observeOn(AndroidSchedulers.mainThread()) - } + /** + * Returns an observable that listens to download queue status changes. + */ + fun getChapterStatusObservable() = downloadManager.queue.getStatusObservable() + .observeOn(AndroidSchedulers.mainThread()) + .filter { download -> download.manga.id == manga.id } + .doOnNext { onDownloadStatusChange(it) } - fun getChapterStatusObs(): Observable { - return downloadManager.queue.getStatusObservable() - .observeOn(AndroidSchedulers.mainThread()) - .filter { download -> download.manga.id == manga.id } - .doOnNext { updateChapterStatus(it) } - } - - private fun applyChapterFilters(chapters: List): Observable> { + /** + * 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): Observable> { var observable = Observable.from(chapters).subscribeOn(Schedulers.io()) if (onlyUnread()) { - observable = observable.filter { chapter -> !chapter.read } + observable = observable.filter { !it.read } } if (onlyDownloaded()) { - observable = observable.filter { chapter -> chapter.status == Download.DOWNLOADED } + observable = observable.filter { it.isDownloaded } } val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) { Manga.SORTING_SOURCE -> when (sortDescending()) { @@ -137,37 +226,40 @@ class ChaptersPresenter : BasePresenter() { return observable.toSortedList(sortFunction) } - private fun setChapterStatus(chapter: Chapter) { - for (download in downloadManager.queue) { - if (chapter.id == download.chapter.id) { - chapter.status = download.status - return + /** + * Called when a download for the active manga changes status. + * + * @param download the download whose status changed. + */ + 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)) { - 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 - } - } + // Force UI update if downloaded filter active and download finished. if (onlyDownloaded() && download.status == Download.DOWNLOADED) 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 } } - fun markChaptersRead(selectedChapters: List, 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, read: Boolean) { Observable.from(selectedChapters) .doOnNext { chapter -> chapter.read = read @@ -181,21 +273,36 @@ class ChaptersPresenter : BasePresenter() { .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) - .filter { it.isRecognizedNumber && it.chapter_number < selected.chapter_number } + .filter { it.isRecognizedNumber && it.chapter_number < chapter.chapter_number } .doOnNext { it.read = true } .toList() .flatMap { db.updateChaptersProgress(it).asRxObservable() } .subscribe() } - fun downloadChapters(chapters: List) { + /** + * Downloads the given list of chapters with the manager. + * + * @param chapters the list of chapters to download. + */ + fun downloadChapters(chapters: List) { DownloadService.start(context) downloadManager.downloadChapters(manga, chapters) } - fun deleteChapters(chapters: List) { + /** + * Deletes the given list of chapter. + * + * @param chapters the list of chapters to delete. + */ + fun deleteChapters(chapters: List) { val wasRunning = downloadManager.isRunning if (wasRunning) { DownloadService.stop(context) @@ -216,49 +323,97 @@ class ChaptersPresenter : BasePresenter() { }) } - 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.deleteChapter(source, manga, chapter) chapter.status = Download.NOT_DOWNLOADED + chapter.download = null } + /** + * Reverses the sorting and requests an UI update. + */ fun revertSortOrder() { manga.setChapterOrder(if (sortDescending()) Manga.SORT_ASC else Manga.SORT_DESC) db.updateFlags(manga).executeAsBlocking() 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) { manga.readFilter = if (onlyUnread) Manga.SHOW_UNREAD else Manga.SHOW_ALL db.updateFlags(manga).executeAsBlocking() 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) { manga.downloadedFilter = if (onlyDownloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL db.updateFlags(manga).executeAsBlocking() 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) { manga.displayMode = mode 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() refreshChapters() } + /** + * Whether the display only downloaded filter is enabled. + */ fun onlyDownloaded(): Boolean { return manga.downloadedFilter == Manga.SHOW_DOWNLOADED } + /** + * Whether the display only unread filter is enabled. + */ fun onlyUnread(): Boolean { return manga.readFilter == Manga.SHOW_UNREAD } + /** + * Whether the sorting method is descending or ascending. + */ fun sortDescending(): Boolean { return manga.sortDescending() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt index 86c8005dbd..9bc40b823b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt @@ -104,7 +104,12 @@ class MangaInfoFragment : BaseRxFragment() { manga_genres.text = manga.genre // 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. manga_summary.text = manga.description diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt index d917fc99d5..3c54c6ff8b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt @@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.util.SharedData import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy /** * Presenter of MangaInfoFragment. @@ -36,17 +36,17 @@ class MangaInfoPresenter : BasePresenter() { /** * Used to connect to database. */ - @Inject lateinit var db: DatabaseHelper + val db: DatabaseHelper by injectLazy() /** * Used to connect to different manga sources. */ - @Inject lateinit var sourceManager: SourceManager + val sourceManager: SourceManager by injectLazy() /** * Used to connect to cache. */ - @Inject lateinit var coverCache: CoverCache + val coverCache: CoverCache by injectLazy() /** * The id of the restartable. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt index af5b380d6e..f9d26b33a8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt @@ -15,12 +15,12 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import timber.log.Timber -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy class MyAnimeListPresenter : BasePresenter() { - @Inject lateinit var db: DatabaseHelper - @Inject lateinit var syncManager: MangaSyncManager + val db: DatabaseHelper by injectLazy() + val syncManager: MangaSyncManager by injectLazy() val myAnimeList by lazy { syncManager.myAnimeList } @@ -124,7 +124,7 @@ class MyAnimeListPresenter : BasePresenter() { fun registerManga(sync: MangaSync?) { if (sync != null) { - sync.manga_id = manga.id + sync.manga_id = manga.id!! add(myAnimeList.bind(sync) .flatMap { db.insertMangaSync(sync).asRxObservable() } .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt new file mode 100644 index 0000000000..225a48aa5f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt @@ -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() + 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 { + + 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) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index c59bc313b4..7f6b27d0a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -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.preference.PreferencesHelper 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.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader @@ -116,16 +115,6 @@ class ReaderActivity : BaseRxActivity() { setSystemUiVisibility() } - override fun onPause() { - viewer?.let { - val activePage = it.getActivePage() - if (activePage != null) { - presenter.currentPage = activePage - } - } - super.onPause() - } - override fun onDestroy() { subscriptions.unsubscribe() popupMenu?.dismiss() @@ -230,6 +219,9 @@ class ReaderActivity : BaseRxActivity() { // Ignore } + /** + * Called from the presenter at startup, allowing to prepare the selected reader. + */ fun onMangaOpen(manga: Manga) { if (viewer == null) { viewer = getOrCreateViewer(manga) @@ -243,22 +235,23 @@ class ReaderActivity : BaseRxActivity() { 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 - 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) setActiveChapter(chapter, activePage.pageNumber) } - fun onEnterChapter(chapter: Chapter, currentPage: Int) { - val activePage = if (currentPage == -1) chapter.pages.lastIndex else currentPage + fun onEnterChapter(chapter: ReaderChapter, currentPage: Int) { + val activePage = if (currentPage == -1) chapter.pages!!.lastIndex else currentPage presenter.setActiveChapter(chapter) setActiveChapter(chapter, activePage) } - fun setActiveChapter(chapter: Chapter, currentPage: Int) { - val numPages = chapter.pages.size + fun setActiveChapter(chapter: ReaderChapter, currentPage: Int) { + val numPages = chapter.pages!!.size if (page_seekbar.rotation != 180f) { right_page_text.text = "$numPages" left_page_text.text = "${currentPage + 1}" @@ -275,7 +268,7 @@ class ReaderActivity : BaseRxActivity() { chapter.name) } - fun onAppendChapter(chapter: Chapter) { + fun onAppendChapter(chapter: ReaderChapter) { viewer?.onPageListAppendReady(chapter) } @@ -324,7 +317,7 @@ class ReaderActivity : BaseRxActivity() { viewer?.let { val activePage = it.getActivePage() if (activePage != null) { - val requestedPage = activePage.chapter.pages[pageIndex] + val requestedPage = activePage.chapter.pages!![pageIndex] it.setActivePage(requestedPage) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt new file mode 100644 index 0000000000..56de9c9721 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt @@ -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? = null + + var isDownloaded: Boolean = false + + var requestedPage: Int = 0 +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index a37593a6bc..58c202c8f0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -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.MangaSync 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.UpdateMangaSyncService 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.model.Page import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.util.RetryWithDelay import eu.kanade.tachiyomi.util.SharedData import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers -import rx.subjects.PublishSubject -import timber.log.Timber +import uy.kohesive.injekt.injectLazy import java.io.File import java.util.* -import javax.inject.Inject +/** + * Presenter of [ReaderActivity]. + */ class ReaderPresenter : BasePresenter() { - @Inject lateinit var prefs: PreferencesHelper - @Inject lateinit var db: DatabaseHelper - @Inject lateinit var downloadManager: DownloadManager - @Inject lateinit var syncManager: MangaSyncManager - @Inject lateinit var sourceManager: SourceManager - @Inject lateinit var chapterCache: ChapterCache + /** + * Preferences. + */ + val prefs: PreferencesHelper by injectLazy() + /** + * 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 private set - lateinit var chapter: Chapter + /** + * Active chapter. + */ + lateinit var chapter: ReaderChapter 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 - private var nextChapter: Chapter? = null - private var previousChapter: Chapter? = null + /** + * Next chapter of the active. + */ + 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 { 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? = null - private val retryPageSubject by lazy { PublishSubject.create() } - private val pageInitializerSubject by lazy { PublishSubject.create() } - - val isSeamlessMode by lazy { prefs.seamlessMode() } + /** + * Chapter loader whose job is to obtain the chapter list and initialize every page. + */ + 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 val PREPARE_READER = 1 - private val GET_PAGE_LIST = 2 - private val GET_ADJACENT_CHAPTERS = 3 - private val GET_MANGA_SYNC = 4 - private val PRELOAD_NEXT_CHAPTER = 5 + /** + * Subscription for retrieving the adjacent chapters to the current one. + */ + private var adjacentChaptersSubscription: Subscription? = null - private val MANGA_KEY = "manga_key" - private val CHAPTER_KEY = "chapter_key" - private val PAGE_KEY = "page_key" + companion object { + /** + * Id of the restartable that loads the active chapter. + */ + private const val LOAD_ACTIVE_CHAPTER = 1 + } override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) @@ -75,306 +136,287 @@ class ReaderPresenter : BasePresenter() { if (savedState == null) { val event = SharedData.get(ReaderEvent::class.java) ?: return manga = event.manga - chapter = event.chapter + chapter = event.chapter.toModel() } else { - manga = savedState.getSerializable(MANGA_KEY) as Manga - chapter = savedState.getSerializable(CHAPTER_KEY) as Chapter - requestedPage = savedState.getInt(PAGE_KEY) + manga = savedState.getSerializable(ReaderPresenter::manga.name) as Manga + chapter = savedState.getSerializable(ReaderPresenter::chapter.name) as ReaderChapter } - 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, - { Observable.just(manga) }, - { view, manga -> view.onMangaOpen(manga) }) - - 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) }, + restartableLatestCache(LOAD_ACTIVE_CHAPTER, + { loadChapterObservable(chapter) }, + { view, chapter -> view.onChapterReady(this.chapter) }, { view, error -> view.onChapterError(error) }) if (savedState == null) { - start(PREPARE_READER) loadChapter(chapter) - if (prefs.autoUpdateMangaSync()) { - start(GET_MANGA_SYNC) - } } } override fun onSave(state: Bundle) { + chapter.requestedPage = chapter.last_page_read onChapterLeft() - state.putSerializable(MANGA_KEY, manga) - state.putSerializable(CHAPTER_KEY, chapter) - state.putSerializable(PAGE_KEY, currentPage?.pageNumber ?: 0) + state.putSerializable(ReaderPresenter::manga.name, manga) + state.putSerializable(ReaderPresenter::chapter.name, chapter) super.onSave(state) } - private fun initializeSubjects() { - // Listen for pages initialization events - add(pageInitializerSubject.observeOn(Schedulers.io()) - .concatMap { ch -> - val observable: Observable - 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()) + override fun onDestroy() { + loader.cleanup() + super.onDestroy() } - // Returns the page list of a chapter - private fun getPageListObservable(chapter: Chapter): Observable { - val observable: Observable> = if (chapter.isDownloaded) - // Fetch the page list from disk - Observable.just(downloadManager.getSavedPageList(source, manga, chapter)!!) - else - // Fetch the page list from cache or fallback to network - source.fetchPageList(chapter) - .subscribeOn(Schedulers.io()) - .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 - } + /** + * Converts a chapter to a [ReaderChapter] if needed. + */ + private fun Chapter.toModel(): ReaderChapter { + if (this is ReaderChapter) return this + return ReaderChapter(this) } - private fun getAdjacentChaptersObservable(): Observable> { - val strategy = getAdjacentChaptersStrategy() - return Observable.zip(strategy.first, strategy.second) { prev, next -> Pair(prev, next) } - .doOnNext { pair -> - previousChapter = pair.first - 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 { - 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) } + /** + * Returns an observable that loads the given chapter, discarding any previous work. + * + * @param chapter the now active chapter. + */ + private fun loadChapterObservable(chapter: ReaderChapter): Observable { + loader.restart() + return loader.loadChapter(chapter) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .doOnCompleted { stopPreloadingNextChapter() } } - private fun getMangaSyncObservable(): Observable> { - return db.getMangasSync(manga).asRxObservable() - .take(1) - .doOnNext { mangaSyncList = it } + /** + * Obtains the adjacent chapters of the given one in a background thread, and notifies the view + * 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 -> + prevChapter = pair.first + nextChapter = pair.second + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeLatestCache({ view, pair -> + view.onAdjacentChapters(pair.first, pair.second) + }) } - // Loads the given chapter - private fun loadChapter(chapter: Chapter, requestedPage: Int = 0) { - if (isSeamlessMode) { - if (appenderSubscription != null) - remove(appenderSubscription) - } else { - stopPreloadingNextChapter() + /** + * Returns the previous and next chapters of the given one in a [Pair] according to the sorting + * strategy set for the manga. + * + * @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 + } + } + + var nextChapter: ReaderChapter? = null + for (i in (currChapterIndex + 1) until chapterList.size) { + val c = chapterList[i] + if (c.chapter_number > chapterNumber && c.chapter_number <= chapterNumber + 1) { + nextChapter = c + break + } + } + 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 - 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 (!chapter.read && chapter.last_page_read != 0) - this.requestedPage = chapter.last_page_read - else - this.requestedPage = requestedPage + // otherwise use the requested page. + chapter.requestedPage = if (!chapter.read) chapter.last_page_read else requestedPage // Reset next and previous chapter. They have to be fetched again nextChapter = null - previousChapter = null + prevChapter = null - start(GET_PAGE_LIST) - start(GET_ADJACENT_CHAPTERS) + start(LOAD_ACTIVE_CHAPTER) + 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() this.chapter = chapter nextChapter = null - previousChapter = null - start(GET_ADJACENT_CHAPTERS) + prevChapter = null + getAdjacentChapters(chapter) } + /** + * Appends the next chapter to the reader, if possible. + */ fun appendNextChapter() { - if (nextChapter == null) - return + appenderSubscription?.let { remove(it) } - if (appenderSubscription != null) - remove(appenderSubscription) + val nextChapter = nextChapter ?: return - nextChapter?.let { - if (appenderSubscription != null) - remove(appenderSubscription) - - it.status = if (isChapterDownloaded(it)) Download.DOWNLOADED else Download.NOT_DOWNLOADED - - appenderSubscription = getPageListObservable(it).subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .compose(deliverLatestCache()) - .subscribe(split({ view, chapter -> - view.onAppendChapter(chapter) - }, { view, error -> - view.onChapterAppendError() - })) - - add(appenderSubscription) - - } - } - - - // Check whether the given chapter is downloaded - fun isChapterDownloaded(chapter: Chapter): Boolean { - return downloadManager.isChapterDownloaded(source, manga, chapter) + appenderSubscription = loader.loadChapter(nextChapter) + .subscribeOn(Schedulers.io()) + .retryWhen(RetryWithDelay(1, { 3000 })) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeLatestCache({ view, chapter -> + view.onAppendChapter(chapter) + }, { view, error -> + view.onChapterAppendError() + }) } + /** + * Retries a page that failed to load due to network error or corruption. + * + * @param page the page that failed. + */ fun retryPage(page: Page?) { - if (page != null) { + if (page != null && source is OnlineSource) { page.status = Page.QUEUE if (page.imagePath != null) { val file = File(page.imagePath) 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() { val pages = chapter.pages ?: return - // Get the last page read - var activePageNumber = chapter.last_page_read + // Reference these locally because they are needed later from another thread. + val chapter = chapter + val prevChapter = prevChapter - // Just in case, avoid out of index exceptions - if (activePageNumber >= pages.size) { - activePageNumber = pages.size - 1 - } - val activePage = pages[activePageNumber] - - // Cache current page list progress for online chapters to allow a faster reopen - if (!chapter.isDownloaded) { - source.let { if (it is OnlineSource) it.savePageList(chapter, pages) } - } - - // Save current progress of the chapter. Mark as read if the chapter is finished - if (activePage.isLastPage) { - chapter.read = true - - // Check if remove after read is selected by user - if (prefs.removeAfterRead()) { - if (prefs.removeAfterReadPrevious() ) { - if (previousChapter != null) { - deleteChapter(previousChapter!!, manga) + Observable + .fromCallable { + if (!chapter.isDownloaded) { + source.let { if (it is OnlineSource) it.savePageList(chapter, pages) } } - } else { - deleteChapter(chapter, manga) + + // Cache current page list progress for online chapters to allow a faster reopen + if (chapter.read) { + // Check if remove after read is selected by user + if (prefs.removeAfterRead()) { + if (prefs.removeAfterReadPrevious() ) { + if (prevChapter != null) { + deleteChapter(prevChapter, manga) + } + } else { + deleteChapter(chapter, manga) + } + } + } + + db.updateChapterProgress(chapter).executeAsBlocking() + + val history = History.create(chapter).apply { last_read = Date().time } + db.updateHistoryLastRead(history).executeAsBlocking() } - } - } - db.updateChapterProgress(chapter).asRxObservable().subscribe() - // Update last read data - db.updateHistoryLastRead(History.create(chapter) - .apply { last_read = Date().time }) - .asRxObservable() - .doOnError { Timber.e(it.message) } + .subscribeOn(Schedulers.io()) .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 + * * @param chapter chapter that is selected - * * * @param manga manga that belongs to chapter */ - fun deleteChapter(chapter: Chapter, manga: Manga) { - val source = sourceManager.get(manga.source)!! + fun deleteChapter(chapter: ReaderChapter, manga: Manga) { + chapter.isDownloaded = false + chapter.pages?.forEach { it.status == Page.QUEUE } 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 - // 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 + /** + * Returns the chapter to be marked as last read in sync services or 0 if no update required. + */ fun getMangaSyncChapterToUpdate(): Int { if (chapter.pages == null || mangaSyncList == null || mangaSyncList!!.isEmpty()) return 0 var lastChapterReadLocal = 0 + + // If the current chapter has been read, we check with this one if (chapter.read) lastChapterReadLocal = Math.floor(chapter.chapter_number.toDouble()).toInt() - else if (previousChapter != null && previousChapter!!.read) - lastChapterReadLocal = Math.floor(previousChapter!!.chapter_number.toDouble()).toInt() + // If not, we check if the previous chapter has been read + 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 for (mangaSync in mangaSyncList!!) { @@ -387,6 +429,9 @@ class ReaderPresenter : BasePresenter() { return if (hasToUpdate) lastChapterReadLocal else 0 } + /** + * Starts the service that updates the last chapter read in sync services + */ fun updateMangaSyncLastChapterRead() { for (mangaSync in mangaSyncList ?: emptyList()) { val service = syncManager.getService(mangaSync.sync_id) ?: continue @@ -396,6 +441,11 @@ class ReaderPresenter : BasePresenter() { } } + /** + * Loads the next chapter. + * + * @return true if the next chapter is being loaded, false if there is no next chapter. + */ fun loadNextChapter(): Boolean { nextChapter?.let { onChapterLeft() @@ -405,44 +455,42 @@ class ReaderPresenter : BasePresenter() { 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 { - previousChapter?.let { + prevChapter?.let { onChapterLeft() - loadChapter(it, 0) + loadChapter(it, if (it.read) -1 else 0) return true } return false } + /** + * Returns true if there's a next chapter. + */ fun hasNextChapter(): Boolean { return nextChapter != null } + /** + * Returns true if there's a previous chapter. + */ fun hasPreviousChapter(): Boolean { - return previousChapter != 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) } - } - } - } + return prevChapter != null } + /** + * Updates the viewer for this manga. + * + * @param viewer the id of the viewer to set. + */ fun updateMangaViewer(viewer: Int) { manga.viewer = viewer db.insertManga(manga).executeAsBlocking() } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt index 7897b563fe..360696b62d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt @@ -1,11 +1,11 @@ package eu.kanade.tachiyomi.ui.reader.viewer.base 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.source.model.Page import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.ui.reader.ReaderChapter import java.util.* /** @@ -29,7 +29,7 @@ abstract class BaseReader : BaseFragment() { /** * List of chapters added in the reader. */ - private var chapters = ArrayList() + private val chapters = ArrayList() /** * 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() { val activePage = getActivePage() if (activePage != null) { - readerActivity.onPageChanged(activePage.pageNumber, activePage.chapter.pages.size) + readerActivity.onPageChanged(activePage.pageNumber, activePage.chapter.pages!!.size) } } @@ -91,23 +91,22 @@ abstract class BaseReader : BaseFragment() { fun onPageChanged(position: Int) { val oldPage = pages[currentPage] val newPage = pages[position] - newPage.chapter.last_page_read = newPage.pageNumber + readerActivity.presenter.onPageChanged(newPage) - if (readerActivity.presenter.isSeamlessMode) { - val oldChapter = oldPage.chapter - val newChapter = newPage.chapter + val oldChapter = oldPage.chapter + val newChapter = newPage.chapter - // Active chapter has changed. - if (oldChapter.id != newChapter.id) { - readerActivity.onEnterChapter(newPage.chapter, newPage.pageNumber) - } - // Request next chapter only when the conditions are met. - if (pages.size - position < 5 && chapters.last().id == newChapter.id - && readerActivity.presenter.hasNextChapter() && !hasRequestedNextChapter) { - hasRequestedNextChapter = true - readerActivity.presenter.appendNextChapter() - } + // Active chapter has changed. + if (oldChapter.id != newChapter.id) { + readerActivity.onEnterChapter(newPage.chapter, newPage.pageNumber) } + // Request next chapter only when the conditions are met. + if (pages.size - position < 5 && chapters.last().id == newChapter.id + && readerActivity.presenter.hasNextChapter() && !hasRequestedNextChapter) { + hasRequestedNextChapter = true + readerActivity.presenter.appendNextChapter() + } + currentPage = position updatePageNumber() } @@ -144,10 +143,10 @@ abstract class BaseReader : BaseFragment() { * @param chapter the chapter to set. * @param currentPage the initial page to display. */ - fun onPageListReady(chapter: Chapter, currentPage: Page) { + fun onPageListReady(chapter: ReaderChapter, currentPage: Page) { if (!chapters.contains(chapter)) { // if we reset the loaded page we also need to reset the loaded chapters - chapters = ArrayList() + chapters.clear() chapters.add(chapter) pages = ArrayList(chapter.pages) onChapterSet(chapter, currentPage) @@ -162,11 +161,11 @@ abstract class BaseReader : BaseFragment() { * * @param chapter the chapter to append. */ - fun onPageListAppendReady(chapter: Chapter) { + fun onPageListAppendReady(chapter: ReaderChapter) { if (!chapters.contains(chapter)) { hasRequestedNextChapter = false chapters.add(chapter) - pages.addAll(chapter.pages) + pages.addAll(chapter.pages!!) onChapterAppended(chapter) } } @@ -184,14 +183,14 @@ abstract class BaseReader : BaseFragment() { * @param chapter the chapter set. * @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]. * * @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...). diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt index f49126cc19..f1c0e32a58 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt @@ -5,8 +5,8 @@ import android.view.MotionEvent import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT 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.ui.reader.ReaderChapter 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.RightToLeftReader @@ -181,7 +181,7 @@ abstract class PagerReader : BaseReader() { * @param chapter the chapter set. * @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 // Make sure the view is already initialized. @@ -195,7 +195,7 @@ abstract class PagerReader : BaseReader() { * * @param chapter the chapter appended. */ - override fun onChapterAppended(chapter: Chapter) { + override fun onChapterAppended(chapter: ReaderChapter) { // Make sure the view is already initialized. if (view != null) { adapter.pages = pages diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt index 43adbb5169..7565f8d5ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt @@ -6,8 +6,8 @@ import android.view.* import android.view.GestureDetector.SimpleOnGestureListener import android.view.ViewGroup.LayoutParams.MATCH_PARENT 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.ui.reader.ReaderChapter import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.widget.PreCachingLayoutManager import rx.subscriptions.CompositeSubscription @@ -147,7 +147,7 @@ class WebtoonReader : BaseReader() { * @param chapter the chapter set. * @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 // this.currentPage = currentPage; @@ -162,11 +162,11 @@ class WebtoonReader : BaseReader() { * * @param chapter the chapter appended. */ - override fun onChapterAppended(chapter: Chapter) { + override fun onChapterAppended(chapter: ReaderChapter) { // Make sure the view is already initialized. if (view != null) { - val insertStart = pages.size - chapter.pages.size - adapter.notifyItemRangeInserted(insertStart, chapter.pages.size) + val insertStart = pages.size - chapter.pages!!.size + adapter.notifyItemRangeInserted(insertStart, chapter.pages!!.size) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapter.kt new file mode 100644 index 0000000000..8c32ef2c56 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapter.kt @@ -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 + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt index 20436ea823..802cd4141f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt @@ -5,7 +5,6 @@ import android.view.View import android.view.ViewGroup import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.MangaChapter import eu.kanade.tachiyomi.util.inflate import java.util.* @@ -18,7 +17,8 @@ import java.util.* * @constructor creates an instance of the adapter. */ -class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdapter() { +class RecentChaptersAdapter(val fragment: RecentChaptersFragment) +: FlexibleAdapter() { /** * The id of the view type */ @@ -45,7 +45,7 @@ class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdap val item = getItem(position) when (holder.itemViewType) { VIEW_TYPE_CHAPTER -> { - if (item is MangaChapter) { + if (item is RecentChapter) { (holder as RecentChaptersHolder).onSetValues(item) } } @@ -89,7 +89,7 @@ class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdap * @param position position of item */ 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 { val item = getItem(position) - if (item is MangaChapter) - return item.chapter.id + if (item is RecentChapter) + return item.id!! else return item.hashCode().toLong() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt index 40110bd731..35fb685401 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt @@ -7,14 +7,12 @@ import android.view.* import com.afollestad.materialdialogs.MaterialDialog import eu.davidea.flexibleadapter.FlexibleAdapter 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.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.getResourceDrawable -import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.widget.DeletingChaptersDialog import eu.kanade.tachiyomi.widget.DividerItemDecoration import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager @@ -28,7 +26,9 @@ import timber.log.Timber * UI related actions should be called from here. */ @RequiresPresenter(RecentChaptersPresenter::class) -class RecentChaptersFragment : BaseRxFragment(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener { +class RecentChaptersFragment +: BaseRxFragment(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener { + companion object { /** * Create new RecentChaptersFragment. @@ -40,6 +40,230 @@ class RecentChaptersFragment : BaseRxFragment(), 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 { + 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) { + destroyActionModeIfNeeded() + presenter.downloadChapters(chapters) + } + + /** + * Populate adapter with chapters + * @param chapters list of [Any] + */ + fun onNextRecentChapters(chapters: List) { + (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) { + presenter.markChapterRead(chapters, true) + if (presenter.preferences.removeAfterMarkedAsRead()) { + deleteChapters(chapters) + } + } + + /** + * Delete selected chapters + * @param chapters list of [RecentChapter] objects + */ + fun deleteChapters(chapters: List) { + 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) { + 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 { return false } @@ -88,229 +312,4 @@ class RecentChaptersFragment : BaseRxFragment(), Action 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 { - 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) { - destroyActionModeIfNeeded() - presenter.downloadChapters(mangaChapters) - } - - /** - * Populate adapter with chapters - * @param chapters list of [Any] - */ - fun onNextMangaChapters(chapters: List) { - (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) { - presenter.markChapterRead(mangaChapters, true) - if (presenter.preferences.removeAfterMarkedAsRead()) { - deleteChapters(mangaChapters) - } - } - - /** - * Delete selected chapters - * @param mangaChapters list of [MangaChapter] objects - */ - fun deleteChapters(mangaChapters: List) { - 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) { - 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() - } - } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersHolder.kt index 592f0fabe3..8465adf0a8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersHolder.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.recent_updates import android.view.View import android.widget.PopupMenu 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.ui.base.adapter.FlexibleViewHolder 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. * @constructor creates a new recent chapter holder. */ -class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapter, listener: OnListItemClickListener) : - FlexibleViewHolder(view, adapter, listener) { +class RecentChaptersHolder( + private val view: View, + private val adapter: RecentChaptersAdapter, + listener: OnListItemClickListener) +: FlexibleViewHolder(view, adapter, listener) { /** * Color of read chapter */ @@ -34,100 +36,97 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte /** * Object containing chapter information */ - private var mangaChapter: MangaChapter? = null + private var chapter: RecentChapter? = null init { // 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 // PopupMenu is shown. - itemView.chapter_menu.setOnClickListener { it.post({ showPopupMenu(it) }) } + view.chapter_menu.setOnClickListener { it.post({ showPopupMenu(it) }) } } /** * Set values of view * - * @param item item containing chapter information + * @param chapter item containing chapter information */ - fun onSetValues(item: MangaChapter) { - this.mangaChapter = item + fun onSetValues(chapter: RecentChapter) { + this.chapter = chapter // Set chapter title - itemView.chapter_title.text = item.chapter.name + view.chapter_title.text = chapter.name // 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 - if (item.chapter.read) { - itemView.chapter_title.setTextColor(readColor) - itemView.manga_title.setTextColor(readColor) + if (chapter.read) { + view.chapter_title.setTextColor(readColor) + view.manga_title.setTextColor(readColor) } else { - itemView.chapter_title.setTextColor(unreadColor) - itemView.manga_title.setTextColor(unreadColor) + view.chapter_title.setTextColor(unreadColor) + view.manga_title.setTextColor(unreadColor) } // Set chapter status - onStatusChange(item.chapter.status) + notifyStatus(chapter.status) } /** * Updates chapter status in view. - + * * @param status download status */ - fun onStatusChange(status: Int) { + fun notifyStatus(status: Int) = with(view.download_text) { when (status) { - Download.QUEUE -> itemView.download_text.setText(R.string.chapter_queued) - Download.DOWNLOADING -> itemView.download_text.setText(R.string.chapter_downloading) - Download.DOWNLOADED -> itemView.download_text.setText(R.string.chapter_downloaded) - Download.ERROR -> itemView.download_text.setText(R.string.chapter_error) - else -> itemView.download_text.text = "" + Download.QUEUE -> setText(R.string.chapter_queued) + Download.DOWNLOADING -> setText(R.string.chapter_downloading) + Download.DOWNLOADED -> setText(R.string.chapter_downloaded) + Download.ERROR -> setText(R.string.chapter_error) + else -> text = "" } } /** * Show pop up 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 - val popup = PopupMenu(adapter.fragment.activity, view) + val popup = PopupMenu(view.context, view) // Inflate our menu resource into the PopupMenu's Menu popup.menuInflater.inflate(R.menu.chapter_recent, popup.menu) - mangaChapter?.let { + // Hide download and show delete if the chapter is downloaded and + if (chapter.isDownloaded) { + popup.menu.findItem(R.id.action_download).isVisible = false + popup.menu.findItem(R.id.action_delete).isVisible = true + } - // Hide download and show delete if the chapter is downloaded and - if (it.chapter.isDownloaded) { - val menu = popup.menu - menu.findItem(R.id.action_download).isVisible = false - menu.findItem(R.id.action_delete).isVisible = true - } + // Hide mark as unread when the chapter is unread + if (!chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) { + popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false + } - // Hide mark as unread when the chapter is unread - if (!it.chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) { - popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false - } - - // Hide mark as read when the chapter is read - if (it.chapter.read) { - popup.menu.findItem(R.id.action_mark_as_read).isVisible = false - } - - - // Set a listener so we are notified if a menu item is clicked - popup.setOnMenuItemClickListener { menuItem -> + // Hide mark as read when the chapter is read + if (chapter.read) { + popup.menu.findItem(R.id.action_mark_as_read).isVisible = false + } + // Set a listener so we are notified if a menu item is clicked + popup.setOnMenuItemClickListener { menuItem -> + with(adapter.fragment) { when (menuItem.itemId) { - R.id.action_download -> adapter.fragment.downloadChapter(it) - R.id.action_delete -> adapter.fragment.deleteChapter(it) - R.id.action_mark_as_read -> adapter.fragment.markAsRead(listOf(it)) - R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(listOf(it)) + R.id.action_download -> downloadChapter(chapter) + R.id.action_delete -> deleteChapter(chapter) + R.id.action_mark_as_read -> markAsRead(listOf(chapter)) + R.id.action_mark_as_unread -> markAsUnread(listOf(chapter)) } - true } + true } // Finally show the PopupMenu diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt index f133213e8a..f08af45996 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt @@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.ui.recent_updates import android.os.Bundle 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.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadService @@ -15,34 +13,34 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import timber.log.Timber +import uy.kohesive.injekt.injectLazy import java.util.* -import javax.inject.Inject class RecentChaptersPresenter : BasePresenter() { /** * Used to connect to database */ - @Inject lateinit var db: DatabaseHelper + val db: DatabaseHelper by injectLazy() /** * Used to get settings */ - @Inject lateinit var preferences: PreferencesHelper + val preferences: PreferencesHelper by injectLazy() /** * Used to get information from download manager */ - @Inject lateinit var downloadManager: DownloadManager + val downloadManager: DownloadManager by injectLazy() /** * Used to get source from source id */ - @Inject lateinit var sourceManager: SourceManager + val sourceManager: SourceManager by injectLazy() /** * List containing chapter and manga information */ - private var mangaChapters: List? = null + private var chapters: List? = null /** * The id of the restartable. @@ -60,139 +58,26 @@ class RecentChaptersPresenter : BasePresenter() { // Used to get recent chapters restartableLatestCache(GET_RECENT_CHAPTERS, { getRecentChaptersObservable() }, - { fragment, chapters -> + { view, chapters -> // Update adapter to show recent manga's - fragment.onNextMangaChapters(chapters) - // Update download status - updateChapterStatus(convertToMangaChaptersList(chapters)) + view.onNextRecentChapters(chapters) } ) // Used to update download status - startableLatestCache(CHAPTER_STATUS_CHANGES, - { getChapterStatusObs() }, - { recentChaptersFragment, download -> + restartableLatestCache(CHAPTER_STATUS_CHANGES, + { getChapterStatusObservable() }, + { view, download -> // Set chapter status - recentChaptersFragment.onChapterStatusChange(download) + view.onChapterStatusChange(download) }, { view, error -> Timber.e(error.cause, error.message) } ) - if (savedState == null) { // Start fetching recent chapters start(GET_RECENT_CHAPTERS) - } - } - - - /** - * Returns observable containing chapter status. - * @return download object containing download progress. - */ - private fun getChapterStatusObs(): Observable { - 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): List { - // Create temp list - val tempMangaChapterList = ArrayList() - - // 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) { - // 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) - } - - /** - * 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 + start(CHAPTER_STATUS_CHANGES) } } @@ -200,33 +85,89 @@ class RecentChaptersPresenter : BasePresenter() { * Get observable containing recent chapters and date * @return observable containing recent chapters and date */ - fun getRecentChaptersObservable(): Observable>? { + fun getRecentChaptersObservable(): Observable> { // Set date for recent chapters - val cal = Calendar.getInstance() - cal.time = Date() - cal.add(Calendar.MONTH, -1) + val cal = Calendar.getInstance().apply { + time = Date() + add(Calendar.MONTH, -1) + } 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. .flatMap { recentItems -> Observable.from(recentItems) .toMultimap( - { getMapKey(it.chapter.date_fetch) }, + { getMapKey(it.date_fetch) }, { it }, { TreeMap { d1, d2 -> d2.compareTo(d1) } }) } // Add every day and all its chapters to a single list. .map { recentItems -> - val items = ArrayList() - recentItems.entries.forEach { recent -> - items.add(recent.key) - items.addAll(recent.value) + ArrayList().apply { + for ((key, value) in recentItems) { + add(key) + addAll(value) + } } - items } .observeOn(AndroidSchedulers.mainThread()) } + /** + * Returns observable containing chapter status. + * @return download object containing download progress. + */ + private fun getChapterStatusObservable(): Observable { + 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 * @param date desired date @@ -244,18 +185,17 @@ class RecentChaptersPresenter : BasePresenter() { /** * Mark selected chapter as read - * @param mangaChapters list of selected MangaChapters + * @param chapters list of selected chapters * @param read read status */ - fun markChapterRead(mangaChapters: List, read: Boolean) { - Observable.from(mangaChapters) - .doOnNext { mangaChapter -> - mangaChapter.chapter.read = read + fun markChapterRead(chapters: List, read: Boolean) { + Observable.from(chapters) + .doOnNext { chapter -> + chapter.read = read if (!read) { - mangaChapter.chapter.last_page_read = 0 + chapter.last_page_read = 0 } } - .map { mangaChapter -> mangaChapter.chapter } .toList() .flatMap { db.updateChaptersProgress(it).asRxObservable() } .subscribeOn(Schedulers.io()) @@ -264,9 +204,9 @@ class RecentChaptersPresenter : BasePresenter() { /** * Delete selected chapters - * @param chapters list of MangaChapters + * @param chapters list of chapters */ - fun deleteChapters(chapters: List) { + fun deleteChapters(chapters: List) { val wasRunning = downloadManager.isRunning if (wasRunning) { DownloadService.stop(context) @@ -288,11 +228,11 @@ class RecentChaptersPresenter : BasePresenter() { /** * Download selected chapters - * @param mangaChapters [MangaChapter] that is selected + * @param chapters list of recent chapters seleted. */ - fun downloadChapters(mangaChapters: List) { + fun downloadChapters(chapters: List) { DownloadService.start(context) - Observable.from(mangaChapters) + Observable.from(chapters) .doOnNext { downloadChapter(it) } .subscribeOn(AndroidSchedulers.mainThread()) .subscribe() @@ -300,47 +240,23 @@ class RecentChaptersPresenter : BasePresenter() { /** * 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) - downloadManager.downloadChapters(item.manga, listOf(item.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) - }) + downloadManager.downloadChapters(chapter.manga, listOf(chapter)) } /** * Delete selected chapter * @param chapter chapter that is selected - * @param manga manga that belongs to chapter */ - private fun deleteChapter(chapter: Chapter, manga: Manga) { - val source = sourceManager.get(manga.source) ?: return + private fun deleteChapter(chapter: RecentChapter) { + val source = sourceManager.get(chapter.manga.source) ?: return downloadManager.queue.del(chapter) - downloadManager.deleteChapter(source, manga, chapter) + downloadManager.deleteChapter(source, chapter.manga, chapter) chapter.status = Download.NOT_DOWNLOADED + chapter.download = null } - } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/SectionViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/SectionViewHolder.kt index bbf37c978d..d1f5e9cea2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/SectionViewHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/SectionViewHolder.kt @@ -2,11 +2,12 @@ package eu.kanade.tachiyomi.ui.recent_updates import android.support.v7.widget.RecyclerView import android.text.format.DateUtils +import android.text.format.DateUtils.DAY_IN_MILLIS import android.view.View import kotlinx.android.synthetic.main.item_recent_chapter_section.view.* import java.util.* -class SectionViewHolder(view: View) : RecyclerView.ViewHolder(view) { +class SectionViewHolder(private val view: View) : RecyclerView.ViewHolder(view) { /** * Current date @@ -19,8 +20,6 @@ class SectionViewHolder(view: View) : RecyclerView.ViewHolder(view) { * @param date of section header */ fun onSetValues(date: Date) { - val s = DateUtils.getRelativeTimeSpanString( - date.time, now, DateUtils.DAY_IN_MILLIS) - itemView.section_text.text = s + view.section_text.text = DateUtils.getRelativeTimeSpanString(date.time, now, DAY_IN_MILLIS) } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt index cdde7d3567..7d4f9774bc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt @@ -37,8 +37,8 @@ class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter) */ fun onSetValues(item: MangaChapterHistory) { // Retrieve objects - val manga = item.mangaChapter.manga - val chapter = item.mangaChapter.chapter + val manga = item.manga + val chapter = item.chapter val history = item.history // Set manga title @@ -71,7 +71,7 @@ class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter) .onPositive { materialDialog, dialogAction -> // Check if user wants all chapters reset if (materialDialog.customView?.removeAll?.isChecked as Boolean) { - adapter.fragment.removeAllFromHistory(manga.id) + adapter.fragment.removeAllFromHistory(manga.id!!) } else { adapter.fragment.removeFromHistory(history) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt index c0b837c88f..ca77ca3369 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt @@ -9,9 +9,9 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import timber.log.Timber +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.* -import javax.inject.Inject /** * Presenter of RecentlyReadFragment. @@ -30,7 +30,7 @@ class RecentlyReadPresenter : BasePresenter() { /** * Used to connect to database */ - @Inject lateinit var db: DatabaseHelper + val db: DatabaseHelper by injectLazy() override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt index f98955cea4..08fc09daab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.setting import android.os.Bundle import android.support.v14.preference.PreferenceFragment -import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache 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.ui.base.activity.BaseActivity import kotlinx.android.synthetic.main.toolbar.* -import javax.inject.Inject +import uy.kohesive.injekt.injectLazy class SettingsActivity : BaseActivity() { - @Inject lateinit var preferences: PreferencesHelper - @Inject lateinit var chapterCache: ChapterCache - @Inject lateinit var db: DatabaseHelper - @Inject lateinit var sourceManager: SourceManager - @Inject lateinit var syncManager: MangaSyncManager - @Inject lateinit var networkHelper: NetworkHelper + val preferences: PreferencesHelper by injectLazy() + val chapterCache: ChapterCache by injectLazy() + val db: DatabaseHelper by injectLazy() + val sourceManager: SourceManager by injectLazy() + val syncManager: MangaSyncManager by injectLazy() + val networkHelper: NetworkHelper by injectLazy() override fun onCreate(savedState: Bundle?) { setAppTheme() super.onCreate(savedState) setContentView(R.layout.activity_preferences) - App.get(this).component.inject(this) setupToolbar(toolbar) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt b/app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt new file mode 100644 index 0000000000..237bfcbdc1 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt @@ -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<*>> { + + private var retryCount = 0 + + override fun call(attempts: Observable) = attempts.flatMap { error -> + val count = ++retryCount + if (count <= maxRetries) { + Observable.timer(retryStrategy(count).toLong(), MILLISECONDS) + } else { + Observable.error(error as Throwable) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index 1d66c42507..9b0ba28e0b 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -30,7 +30,6 @@ pref_custom_brightness_value_key pref_reader_theme_key pref_image_decoder_key - pref_seamless_mode_key reader_volume_keys reader_tap reencode_image diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index de372754c6..83aa3376b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -101,7 +101,6 @@ Enable transitions Show page number Use custom brightness - Seamless chapter transitions Keep screen on Navigation Volume keys diff --git a/app/src/main/res/xml/pref_reader.xml b/app/src/main/res/xml/pref_reader.xml index 900037e69f..7a716c91fb 100644 --- a/app/src/main/res/xml/pref_reader.xml +++ b/app/src/main/res/xml/pref_reader.xml @@ -75,11 +75,6 @@ android:key="@string/pref_keep_screen_on_key" android:defaultValue="true" /> - - diff --git a/app/src/test/java/eu/kanade/tachiyomi/BackupTest.java b/app/src/test/java/eu/kanade/tachiyomi/BackupTest.java deleted file mode 100644 index f90c75142e..0000000000 --- a/app/src/test/java/eu/kanade/tachiyomi/BackupTest.java +++ /dev/null @@ -1,573 +0,0 @@ -package eu.kanade.tachiyomi; - -import android.app.Application; -import android.os.Build; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import java.util.ArrayList; -import java.util.List; - -import eu.kanade.tachiyomi.data.backup.BackupManager; -import eu.kanade.tachiyomi.data.database.DatabaseHelper; -import eu.kanade.tachiyomi.data.database.models.Category; -import eu.kanade.tachiyomi.data.database.models.Chapter; -import eu.kanade.tachiyomi.data.database.models.Manga; -import eu.kanade.tachiyomi.data.database.models.MangaCategory; -import eu.kanade.tachiyomi.data.database.models.MangaSync; - -import static org.assertj.core.api.Assertions.assertThat; - -@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP) -@RunWith(CustomRobolectricGradleTestRunner.class) -public class BackupTest { - - DatabaseHelper db; - BackupManager backupManager; - Gson gson; - JsonObject root; - - @Before - public void setup() { - Application app = RuntimeEnvironment.application; - db = new DatabaseHelper(app); - backupManager = new BackupManager(db); - gson = new Gson(); - root = new JsonObject(); - } - - @Test - public void testRestoreCategory() { - String catName = "cat"; - root = createRootJson(null, toJson(createCategories(catName))); - backupManager.restoreFromJson(root); - - List dbCats = db.getCategories().executeAsBlocking(); - assertThat(dbCats).hasSize(1); - assertThat(dbCats.get(0).name).isEqualTo(catName); - } - - @Test - public void testRestoreEmptyCategory() { - root = createRootJson(null, toJson(new ArrayList<>())); - backupManager.restoreFromJson(root); - List dbCats = db.getCategories().executeAsBlocking(); - assertThat(dbCats).isEmpty(); - } - - @Test - public void testRestoreExistingCategory() { - String catName = "cat"; - db.insertCategory(createCategory(catName)).executeAsBlocking(); - - root = createRootJson(null, toJson(createCategories(catName))); - backupManager.restoreFromJson(root); - - List dbCats = db.getCategories().executeAsBlocking(); - assertThat(dbCats).hasSize(1); - assertThat(dbCats.get(0).name).isEqualTo(catName); - } - - @Test - public void testRestoreCategories() { - root = createRootJson(null, toJson(createCategories("cat", "cat2", "cat3"))); - backupManager.restoreFromJson(root); - - List dbCats = db.getCategories().executeAsBlocking(); - assertThat(dbCats).hasSize(3); - } - - @Test - public void testRestoreExistingCategories() { - db.insertCategories(createCategories("cat", "cat2")).executeAsBlocking(); - - root = createRootJson(null, toJson(createCategories("cat", "cat2", "cat3"))); - backupManager.restoreFromJson(root); - - List dbCats = db.getCategories().executeAsBlocking(); - assertThat(dbCats).hasSize(3); - } - - @Test - public void testRestoreExistingCategoriesAlt() { - db.insertCategories(createCategories("cat", "cat2", "cat3")).executeAsBlocking(); - - root = createRootJson(null, toJson(createCategories("cat", "cat2"))); - backupManager.restoreFromJson(root); - - List dbCats = db.getCategories().executeAsBlocking(); - assertThat(dbCats).hasSize(3); - } - - @Test - public void testRestoreManga() { - String mangaName = "title"; - List mangas = createMangas(mangaName); - List elements = new ArrayList<>(); - for (Manga manga : mangas) { - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - elements.add(entry); - } - root = createRootJson(toJson(elements), null); - backupManager.restoreFromJson(root); - - List dbMangas = db.getMangas().executeAsBlocking(); - assertThat(dbMangas).hasSize(1); - assertThat(dbMangas.get(0).title).isEqualTo(mangaName); - } - - @Test - public void testRestoreExistingManga() { - String mangaName = "title"; - Manga manga = createManga(mangaName); - - db.insertManga(manga).executeAsBlocking(); - - List elements = new ArrayList<>(); - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - elements.add(entry); - - root = createRootJson(toJson(elements), null); - backupManager.restoreFromJson(root); - - List dbMangas = db.getMangas().executeAsBlocking(); - assertThat(dbMangas).hasSize(1); - } - - @Test - public void testRestoreExistingMangaWithUpdatedFields() { - // Store a manga in db - String mangaName = "title"; - String updatedThumbnailUrl = "updated thumbnail url"; - Manga manga = createManga(mangaName); - manga.chapter_flags = 1024; - manga.thumbnail_url = updatedThumbnailUrl; - db.insertManga(manga).executeAsBlocking(); - - // Add an entry for a new manga with different attributes - manga = createManga(mangaName); - manga.chapter_flags = 512; - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - - // Append the entry to the backup list - List elements = new ArrayList<>(); - elements.add(entry); - - // Restore from json - root = createRootJson(toJson(elements), null); - backupManager.restoreFromJson(root); - - List dbMangas = db.getMangas().executeAsBlocking(); - assertThat(dbMangas).hasSize(1); - assertThat(dbMangas.get(0).thumbnail_url).isEqualTo(updatedThumbnailUrl); - assertThat(dbMangas.get(0).chapter_flags).isEqualTo(512); - } - - @Test - public void testRestoreChaptersForManga() { - // Create a manga and 3 chapters - Manga manga = createManga("title"); - manga.id = 1L; - List chapters = createChapters(manga, "1", "2", "3"); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("chapters", toJson(chapters)); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), null); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(1).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - List dbChapters = db.getChapters(dbManga).executeAsBlocking(); - assertThat(dbChapters).hasSize(3); - } - - @Test - public void testRestoreChaptersForExistingManga() { - long mangaId = 3; - // Create a manga and 3 chapters - Manga manga = createManga("title"); - manga.id = mangaId; - List chapters = createChapters(manga, "1", "2", "3"); - db.insertManga(manga).executeAsBlocking(); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("chapters", toJson(chapters)); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), null); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(mangaId).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - List dbChapters = db.getChapters(dbManga).executeAsBlocking(); - assertThat(dbChapters).hasSize(3); - } - - @Test - public void testRestoreExistingChaptersForExistingManga() { - long mangaId = 5; - // Store a manga and 3 chapters - Manga manga = createManga("title"); - manga.id = mangaId; - List chapters = createChapters(manga, "1", "2", "3"); - db.insertManga(manga).executeAsBlocking(); - db.insertChapters(chapters).executeAsBlocking(); - - // The backup contains a existing chapter and a new one, so it should have 4 chapters - chapters = createChapters(manga, "3", "4"); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("chapters", toJson(chapters)); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), null); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(mangaId).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - List dbChapters = db.getChapters(dbManga).executeAsBlocking(); - assertThat(dbChapters).hasSize(4); - } - - @Test - public void testRestoreCategoriesForManga() { - // Create a manga - Manga manga = createManga("title"); - - // Create categories - List categories = createCategories("cat1", "cat2", "cat3"); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("categories", toJson(createStringCategories("cat1"))); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), toJson(categories)); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(1).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - assertThat(db.getCategoriesForManga(dbManga).executeAsBlocking()) - .hasSize(1) - .contains(Category.create("cat1")) - .doesNotContain(Category.create("cat2")); - } - - @Test - public void testRestoreCategoriesForExistingManga() { - // Store a manga - Manga manga = createManga("title"); - db.insertManga(manga).executeAsBlocking(); - - // Create categories - List categories = createCategories("cat1", "cat2", "cat3"); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("categories", toJson(createStringCategories("cat1"))); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), toJson(categories)); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(1).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - assertThat(db.getCategoriesForManga(dbManga).executeAsBlocking()) - .hasSize(1) - .contains(Category.create("cat1")) - .doesNotContain(Category.create("cat2")); - } - - @Test - public void testRestoreMultipleCategoriesForManga() { - // Create a manga - Manga manga = createManga("title"); - - // Create categories - List categories = createCategories("cat1", "cat2", "cat3"); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("categories", toJson(createStringCategories("cat1", "cat3"))); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), toJson(categories)); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(1).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - assertThat(db.getCategoriesForManga(dbManga).executeAsBlocking()) - .hasSize(2) - .contains(Category.create("cat1"), Category.create("cat3")) - .doesNotContain(Category.create("cat2")); - } - - @Test - public void testRestoreMultipleCategoriesForExistingMangaAndCategory() { - // Store a manga and a category - Manga manga = createManga("title"); - manga.id = 1L; - db.insertManga(manga).executeAsBlocking(); - - Category cat = createCategory("cat1"); - cat.id = 1; - db.insertCategory(cat).executeAsBlocking(); - db.insertMangaCategory(MangaCategory.create(manga, cat)).executeAsBlocking(); - - // Create categories - List categories = createCategories("cat1", "cat2", "cat3"); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("categories", toJson(createStringCategories("cat1", "cat2"))); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), toJson(categories)); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(1).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - assertThat(db.getCategoriesForManga(dbManga).executeAsBlocking()) - .hasSize(2) - .contains(Category.create("cat1"), Category.create("cat2")) - .doesNotContain(Category.create("cat3")); - } - - @Test - public void testRestoreSyncForManga() { - // Create a manga and mangaSync - Manga manga = createManga("title"); - manga.id = 1L; - - List mangaSync = createMangaSync(manga, 1, 2, 3); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("sync", toJson(mangaSync)); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), null); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(1).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - List dbSync = db.getMangasSync(dbManga).executeAsBlocking(); - assertThat(dbSync).hasSize(3); - } - - @Test - public void testRestoreSyncForExistingManga() { - long mangaId = 3; - // Create a manga and 3 sync - Manga manga = createManga("title"); - manga.id = mangaId; - List mangaSync = createMangaSync(manga, 1, 2, 3); - db.insertManga(manga).executeAsBlocking(); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("sync", toJson(mangaSync)); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), null); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(mangaId).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - List dbSync = db.getMangasSync(dbManga).executeAsBlocking(); - assertThat(dbSync).hasSize(3); - } - - @Test - public void testRestoreExistingSyncForExistingManga() { - long mangaId = 5; - // Store a manga and 3 sync - Manga manga = createManga("title"); - manga.id = mangaId; - List mangaSync = createMangaSync(manga, 1, 2, 3); - db.insertManga(manga).executeAsBlocking(); - db.insertMangasSync(mangaSync).executeAsBlocking(); - - // The backup contains a existing sync and a new one, so it should have 4 sync - mangaSync = createMangaSync(manga, 3, 4); - - // Add an entry for the manga - JsonObject entry = new JsonObject(); - entry.add("manga", toJson(manga)); - entry.add("sync", toJson(mangaSync)); - - // Append the entry to the backup list - List mangas = new ArrayList<>(); - mangas.add(entry); - - // Restore from json - root = createRootJson(toJson(mangas), null); - backupManager.restoreFromJson(root); - - Manga dbManga = db.getManga(mangaId).executeAsBlocking(); - assertThat(dbManga).isNotNull(); - - List dbSync = db.getMangasSync(dbManga).executeAsBlocking(); - assertThat(dbSync).hasSize(4); - } - - private JsonObject createRootJson(JsonElement mangas, JsonElement categories) { - JsonObject root = new JsonObject(); - if (mangas != null) - root.add("mangas", mangas); - if (categories != null) - root.add("categories", categories); - return root; - } - - private Category createCategory(String name) { - Category c = new Category(); - c.name = name; - return c; - } - - private List createCategories(String... names) { - List cats = new ArrayList<>(); - for (String name : names) { - cats.add(createCategory(name)); - } - return cats; - } - - private List createStringCategories(String... names) { - List cats = new ArrayList<>(); - for (String name : names) { - cats.add(name); - } - return cats; - } - - private Manga createManga(String title) { - Manga m = new Manga(); - m.title = title; - m.author = ""; - m.artist = ""; - m.thumbnail_url = ""; - m.genre = "a list of genres"; - m.description = "long description"; - m.url = "url to manga"; - m.favorite = true; - m.source = 1; - return m; - } - - private List createMangas(String... titles) { - List mangas = new ArrayList<>(); - for (String title : titles) { - mangas.add(createManga(title)); - } - return mangas; - } - - private Chapter createChapter(Manga manga, String url) { - Chapter c = Chapter.create(); - c.url = url; - c.name = url; - c.manga_id = manga.id; - return c; - } - - private List createChapters(Manga manga, String... urls) { - List chapters = new ArrayList<>(); - for (String url : urls) { - chapters.add(createChapter(manga, url)); - } - return chapters; - } - - private MangaSync createMangaSync(Manga manga, int syncId) { - MangaSync m = MangaSync.create(); - m.manga_id = manga.id; - m.sync_id = syncId; - m.title = "title"; - return m; - } - - private List createMangaSync(Manga manga, Integer... syncIds) { - List ms = new ArrayList<>(); - for (int title : syncIds) { - ms.add(createMangaSync(manga, title)); - } - return ms; - } - - private JsonElement toJson(Object element) { - return gson.toJsonTree(element); - } - -} \ No newline at end of file diff --git a/app/src/test/java/eu/kanade/tachiyomi/CategoryTest.java b/app/src/test/java/eu/kanade/tachiyomi/CategoryTest.java deleted file mode 100644 index 4f7325d3a1..0000000000 --- a/app/src/test/java/eu/kanade/tachiyomi/CategoryTest.java +++ /dev/null @@ -1,115 +0,0 @@ -package eu.kanade.tachiyomi; - -import android.app.Application; -import android.os.Build; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import java.util.List; - -import eu.kanade.tachiyomi.data.database.DatabaseHelper; -import eu.kanade.tachiyomi.data.database.models.Category; -import eu.kanade.tachiyomi.data.database.models.Manga; -import eu.kanade.tachiyomi.data.database.models.MangaCategory; - -import static org.assertj.core.api.Assertions.assertThat; - -@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP) -@RunWith(CustomRobolectricGradleTestRunner.class) -public class CategoryTest { - - DatabaseHelper db; - - @Before - public void setup() { - Application app = RuntimeEnvironment.application; - db = new DatabaseHelper(app); - - // Create 5 mangas - createManga("a"); - createManga("b"); - createManga("c"); - createManga("d"); - createManga("e"); - } - - @Test - public void testHasCategories() { - // Create 2 categories - createCategory("Reading"); - createCategory("Hold"); - - List categories = db.getCategories().executeAsBlocking(); - assertThat(categories).hasSize(2); - } - - @Test - public void testHasLibraryMangas() { - List mangas = db.getLibraryMangas().executeAsBlocking(); - assertThat(mangas).hasSize(5); - } - - @Test - public void testHasCorrectFavorites() { - Manga m = new Manga(); - m.title = "title"; - m.author = ""; - m.artist = ""; - m.thumbnail_url = ""; - m.genre = "a list of genres"; - m.description = "long description"; - m.url = "url to manga"; - m.favorite = false; - db.insertManga(m).executeAsBlocking(); - List mangas = db.getLibraryMangas().executeAsBlocking(); - assertThat(mangas).hasSize(5); - } - - @Test - public void testMangaInCategory() { - // Create 2 categories - createCategory("Reading"); - createCategory("Hold"); - - // It should not have 0 as id - Category c = db.getCategories().executeAsBlocking().get(0); - assertThat(c.id).isNotZero(); - - // Add a manga to a category - Manga m = db.getMangas().executeAsBlocking().get(0); - MangaCategory mc = MangaCategory.create(m, c); - db.insertMangaCategory(mc).executeAsBlocking(); - - // Get mangas from library and assert manga category is the same - List mangas = db.getLibraryMangas().executeAsBlocking(); - for (Manga manga : mangas) { - if (manga.id.equals(m.id)) { - assertThat(manga.category).isEqualTo(c.id); - } - } - } - - private void createManga(String title) { - Manga m = new Manga(); - m.title = title; - m.author = ""; - m.artist = ""; - m.thumbnail_url = ""; - m.genre = "a list of genres"; - m.description = "long description"; - m.url = "url to manga"; - m.favorite = true; - db.insertManga(m).executeAsBlocking(); - } - - private void createCategory(String name) { - Category c = new Category(); - c.name = name; - db.insertCategory(c).executeAsBlocking(); - } - -} diff --git a/app/src/test/java/eu/kanade/tachiyomi/CategoryTest.kt b/app/src/test/java/eu/kanade/tachiyomi/CategoryTest.kt new file mode 100644 index 0000000000..f0b251699e --- /dev/null +++ b/app/src/test/java/eu/kanade/tachiyomi/CategoryTest.kt @@ -0,0 +1,109 @@ +package eu.kanade.tachiyomi + +import android.os.Build +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.CategoryImpl +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.database.models.MangaCategory +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP)) +@RunWith(CustomRobolectricGradleTestRunner::class) +class CategoryTest { + + lateinit var db: DatabaseHelper + + @Before + fun setup() { + val app = RuntimeEnvironment.application + db = DatabaseHelper(app) + + // Create 5 manga + createManga("a") + createManga("b") + createManga("c") + createManga("d") + createManga("e") + } + + @Test + fun testHasCategories() { + // Create 2 categories + createCategory("Reading") + createCategory("Hold") + + val categories = db.getCategories().executeAsBlocking() + assertThat(categories).hasSize(2) + } + + @Test + fun testHasLibraryMangas() { + val mangas = db.getLibraryMangas().executeAsBlocking() + assertThat(mangas).hasSize(5) + } + + @Test + fun testHasCorrectFavorites() { + val m = Manga.create(0) + m.title = "title" + m.author = "" + m.artist = "" + m.thumbnail_url = "" + m.genre = "a list of genres" + m.description = "long description" + m.url = "url to manga" + m.favorite = false + db.insertManga(m).executeAsBlocking() + val mangas = db.getLibraryMangas().executeAsBlocking() + assertThat(mangas).hasSize(5) + } + + @Test + fun testMangaInCategory() { + // Create 2 categories + createCategory("Reading") + createCategory("Hold") + + // It should not have 0 as id + val c = db.getCategories().executeAsBlocking()[0] + assertThat(c.id).isNotZero() + + // Add a manga to a category + val m = db.getMangas().executeAsBlocking()[0] + val mc = MangaCategory.create(m, c) + db.insertMangaCategory(mc).executeAsBlocking() + + // Get mangas from library and assert manga category is the same + val mangas = db.getLibraryMangas().executeAsBlocking() + for (manga in mangas) { + if (manga.id == m.id) { + assertThat(manga.category).isEqualTo(c.id) + } + } + } + + private fun createManga(title: String) { + val m = Manga.create(0) + m.title = title + m.author = "" + m.artist = "" + m.thumbnail_url = "" + m.genre = "a list of genres" + m.description = "long description" + m.url = "url to manga" + m.favorite = true + db.insertManga(m).executeAsBlocking() + } + + private fun createCategory(name: String) { + val c = CategoryImpl() + c.name = name + db.insertCategory(c).executeAsBlocking() + } + +} diff --git a/app/src/test/java/eu/kanade/tachiyomi/ChapterRecognitionTest.kt b/app/src/test/java/eu/kanade/tachiyomi/ChapterRecognitionTest.kt index 5d80daa52d..e13cd15cf1 100644 --- a/app/src/test/java/eu/kanade/tachiyomi/ChapterRecognitionTest.kt +++ b/app/src/test/java/eu/kanade/tachiyomi/ChapterRecognitionTest.kt @@ -43,7 +43,7 @@ class ChapterRecognitionTest { * Called before test */ @Before fun setup() { - manga = Manga().apply { title = "random" } + manga = Manga.create(0).apply { title = "random" } chapter = Chapter.create() } diff --git a/app/src/test/java/eu/kanade/tachiyomi/TestApp.kt b/app/src/test/java/eu/kanade/tachiyomi/TestApp.kt index ac710e95d0..35a74ceb22 100644 --- a/app/src/test/java/eu/kanade/tachiyomi/TestApp.kt +++ b/app/src/test/java/eu/kanade/tachiyomi/TestApp.kt @@ -1,19 +1,7 @@ package eu.kanade.tachiyomi -import eu.kanade.tachiyomi.injection.component.AppComponent -import eu.kanade.tachiyomi.injection.component.DaggerAppComponent -import eu.kanade.tachiyomi.injection.module.AppModule -import eu.kanade.tachiyomi.injection.module.TestDataModule - open class TestApp : App() { - override fun createAppComponent(): AppComponent { - return DaggerAppComponent.builder() - .appModule(AppModule(this)) - .dataModule(TestDataModule()) - .build() - } - override fun setupAcra() { // Do nothing } diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt new file mode 100644 index 0000000000..37b358e86e --- /dev/null +++ b/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt @@ -0,0 +1,568 @@ +package eu.kanade.tachiyomi.data.backup + +import android.os.Build +import com.google.gson.Gson +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.CustomRobolectricGradleTestRunner +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.* +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import uy.kohesive.injekt.injectLazy +import java.util.* + +@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP)) +@RunWith(CustomRobolectricGradleTestRunner::class) +class BackupTest { + + val gson: Gson by injectLazy() + + lateinit var db: DatabaseHelper + + lateinit var backupManager: BackupManager + + lateinit var root: JsonObject + + @Before + fun setup() { + val app = RuntimeEnvironment.application + db = DatabaseHelper(app) + backupManager = BackupManager(db) + root = JsonObject() + } + + @Test + fun testRestoreCategory() { + val catName = "cat" + root = createRootJson(null, toJson(createCategories(catName))) + backupManager.restoreFromJson(root) + + val dbCats = db.getCategories().executeAsBlocking() + assertThat(dbCats).hasSize(1) + assertThat(dbCats[0].name).isEqualTo(catName) + } + + @Test + fun testRestoreEmptyCategory() { + root = createRootJson(null, toJson(ArrayList())) + backupManager.restoreFromJson(root) + val dbCats = db.getCategories().executeAsBlocking() + assertThat(dbCats).isEmpty() + } + + @Test + fun testRestoreExistingCategory() { + val catName = "cat" + db.insertCategory(createCategory(catName)).executeAsBlocking() + + root = createRootJson(null, toJson(createCategories(catName))) + backupManager.restoreFromJson(root) + + val dbCats = db.getCategories().executeAsBlocking() + assertThat(dbCats).hasSize(1) + assertThat(dbCats[0].name).isEqualTo(catName) + } + + @Test + fun testRestoreCategories() { + root = createRootJson(null, toJson(createCategories("cat", "cat2", "cat3"))) + backupManager.restoreFromJson(root) + + val dbCats = db.getCategories().executeAsBlocking() + assertThat(dbCats).hasSize(3) + } + + @Test + fun testRestoreExistingCategories() { + db.insertCategories(createCategories("cat", "cat2")).executeAsBlocking() + + root = createRootJson(null, toJson(createCategories("cat", "cat2", "cat3"))) + backupManager.restoreFromJson(root) + + val dbCats = db.getCategories().executeAsBlocking() + assertThat(dbCats).hasSize(3) + } + + @Test + fun testRestoreExistingCategoriesAlt() { + db.insertCategories(createCategories("cat", "cat2", "cat3")).executeAsBlocking() + + root = createRootJson(null, toJson(createCategories("cat", "cat2"))) + backupManager.restoreFromJson(root) + + val dbCats = db.getCategories().executeAsBlocking() + assertThat(dbCats).hasSize(3) + } + + @Test + fun testRestoreManga() { + val mangaName = "title" + val mangas = createMangas(mangaName) + val elements = ArrayList() + for (manga in mangas) { + val entry = JsonObject() + entry.add("manga", toJson(manga)) + elements.add(entry) + } + root = createRootJson(toJson(elements), null) + backupManager.restoreFromJson(root) + + val dbMangas = db.getMangas().executeAsBlocking() + assertThat(dbMangas).hasSize(1) + assertThat(dbMangas[0].title).isEqualTo(mangaName) + } + + @Test + fun testRestoreExistingManga() { + val mangaName = "title" + val manga = createManga(mangaName) + + db.insertManga(manga).executeAsBlocking() + + val elements = ArrayList() + val entry = JsonObject() + entry.add("manga", toJson(manga)) + elements.add(entry) + + root = createRootJson(toJson(elements), null) + backupManager.restoreFromJson(root) + + val dbMangas = db.getMangas().executeAsBlocking() + assertThat(dbMangas).hasSize(1) + } + + @Test + fun testRestoreExistingMangaWithUpdatedFields() { + // Store a manga in db + val mangaName = "title" + val updatedThumbnailUrl = "updated thumbnail url" + var manga = createManga(mangaName) + manga.chapter_flags = 1024 + manga.thumbnail_url = updatedThumbnailUrl + db.insertManga(manga).executeAsBlocking() + + // Add an entry for a new manga with different attributes + manga = createManga(mangaName) + manga.chapter_flags = 512 + val entry = JsonObject() + entry.add("manga", toJson(manga)) + + // Append the entry to the backup list + val elements = ArrayList() + elements.add(entry) + + // Restore from json + root = createRootJson(toJson(elements), null) + backupManager.restoreFromJson(root) + + val dbMangas = db.getMangas().executeAsBlocking() + assertThat(dbMangas).hasSize(1) + assertThat(dbMangas[0].thumbnail_url).isEqualTo(updatedThumbnailUrl) + assertThat(dbMangas[0].chapter_flags).isEqualTo(512) + } + + @Test + fun testRestoreChaptersForManga() { + // Create a manga and 3 chapters + val manga = createManga("title") + manga.id = 1L + val chapters = createChapters(manga, "1", "2", "3") + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("chapters", toJson(chapters)) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), null) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(1).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val dbChapters = db.getChapters(dbManga!!).executeAsBlocking() + assertThat(dbChapters).hasSize(3) + } + + @Test + fun testRestoreChaptersForExistingManga() { + val mangaId: Long = 3 + // Create a manga and 3 chapters + val manga = createManga("title") + manga.id = mangaId + val chapters = createChapters(manga, "1", "2", "3") + db.insertManga(manga).executeAsBlocking() + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("chapters", toJson(chapters)) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), null) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(mangaId).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val dbChapters = db.getChapters(dbManga!!).executeAsBlocking() + assertThat(dbChapters).hasSize(3) + } + + @Test + fun testRestoreExistingChaptersForExistingManga() { + val mangaId: Long = 5 + // Store a manga and 3 chapters + val manga = createManga("title") + manga.id = mangaId + var chapters = createChapters(manga, "1", "2", "3") + db.insertManga(manga).executeAsBlocking() + db.insertChapters(chapters).executeAsBlocking() + + // The backup contains a existing chapter and a new one, so it should have 4 chapters + chapters = createChapters(manga, "3", "4") + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("chapters", toJson(chapters)) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), null) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(mangaId).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val dbChapters = db.getChapters(dbManga!!).executeAsBlocking() + assertThat(dbChapters).hasSize(4) + } + + @Test + fun testRestoreCategoriesForManga() { + // Create a manga + val manga = createManga("title") + + // Create categories + val categories = createCategories("cat1", "cat2", "cat3") + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("categories", toJson(createStringCategories("cat1"))) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), toJson(categories)) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(1).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val result = db.getCategoriesForManga(dbManga!!).executeAsBlocking() + + assertThat(result).hasSize(1) + assertThat(result).contains(Category.create("cat1")) + assertThat(result).doesNotContain(Category.create("cat2")) + } + + @Test + fun testRestoreCategoriesForExistingManga() { + // Store a manga + val manga = createManga("title") + db.insertManga(manga).executeAsBlocking() + + // Create categories + val categories = createCategories("cat1", "cat2", "cat3") + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("categories", toJson(createStringCategories("cat1"))) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), toJson(categories)) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(1).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val result = db.getCategoriesForManga(dbManga!!).executeAsBlocking() + + assertThat(result).hasSize(1) + assertThat(result).contains(Category.create("cat1")) + assertThat(result).doesNotContain(Category.create("cat2")) + } + + @Test + fun testRestoreMultipleCategoriesForManga() { + // Create a manga + val manga = createManga("title") + + // Create categories + val categories = createCategories("cat1", "cat2", "cat3") + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("categories", toJson(createStringCategories("cat1", "cat3"))) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), toJson(categories)) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(1).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val result = db.getCategoriesForManga(dbManga!!).executeAsBlocking() + + assertThat(result).hasSize(2) + assertThat(result).contains(Category.create("cat1"), Category.create("cat3")) + assertThat(result).doesNotContain(Category.create("cat2")) + } + + @Test + fun testRestoreMultipleCategoriesForExistingMangaAndCategory() { + // Store a manga and a category + val manga = createManga("title") + manga.id = 1L + db.insertManga(manga).executeAsBlocking() + + val cat = createCategory("cat1") + cat.id = 1 + db.insertCategory(cat).executeAsBlocking() + db.insertMangaCategory(MangaCategory.create(manga, cat)).executeAsBlocking() + + // Create categories + val categories = createCategories("cat1", "cat2", "cat3") + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("categories", toJson(createStringCategories("cat1", "cat2"))) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), toJson(categories)) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(1).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val result = db.getCategoriesForManga(dbManga!!).executeAsBlocking() + + assertThat(result).hasSize(2) + assertThat(result).contains(Category.create("cat1"), Category.create("cat2")) + assertThat(result).doesNotContain(Category.create("cat3")) + } + + @Test + fun testRestoreSyncForManga() { + // Create a manga and mangaSync + val manga = createManga("title") + manga.id = 1L + + val mangaSync = createMangaSync(manga, 1, 2, 3) + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("sync", toJson(mangaSync)) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), null) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(1).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val dbSync = db.getMangasSync(dbManga!!).executeAsBlocking() + assertThat(dbSync).hasSize(3) + } + + @Test + fun testRestoreSyncForExistingManga() { + val mangaId: Long = 3 + // Create a manga and 3 sync + val manga = createManga("title") + manga.id = mangaId + val mangaSync = createMangaSync(manga, 1, 2, 3) + db.insertManga(manga).executeAsBlocking() + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("sync", toJson(mangaSync)) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), null) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(mangaId).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val dbSync = db.getMangasSync(dbManga!!).executeAsBlocking() + assertThat(dbSync).hasSize(3) + } + + @Test + fun testRestoreExistingSyncForExistingManga() { + val mangaId: Long = 5 + // Store a manga and 3 sync + val manga = createManga("title") + manga.id = mangaId + var mangaSync = createMangaSync(manga, 1, 2, 3) + db.insertManga(manga).executeAsBlocking() + db.insertMangasSync(mangaSync).executeAsBlocking() + + // The backup contains a existing sync and a new one, so it should have 4 sync + mangaSync = createMangaSync(manga, 3, 4) + + // Add an entry for the manga + val entry = JsonObject() + entry.add("manga", toJson(manga)) + entry.add("sync", toJson(mangaSync)) + + // Append the entry to the backup list + val mangas = ArrayList() + mangas.add(entry) + + // Restore from json + root = createRootJson(toJson(mangas), null) + backupManager.restoreFromJson(root) + + val dbManga = db.getManga(mangaId).executeAsBlocking() + assertThat(dbManga).isNotNull() + + val dbSync = db.getMangasSync(dbManga!!).executeAsBlocking() + assertThat(dbSync).hasSize(4) + } + + private fun createRootJson(mangas: JsonElement?, categories: JsonElement?): JsonObject { + val root = JsonObject() + if (mangas != null) + root.add("mangas", mangas) + if (categories != null) + root.add("categories", categories) + return root + } + + private fun createCategory(name: String): Category { + val c = CategoryImpl() + c.name = name + return c + } + + private fun createCategories(vararg names: String): List { + val cats = ArrayList() + for (name in names) { + cats.add(createCategory(name)) + } + return cats + } + + private fun createStringCategories(vararg names: String): List { + val cats = ArrayList() + for (name in names) { + cats.add(name) + } + return cats + } + + private fun createManga(title: String): Manga { + val m = Manga.create(1) + m.title = title + m.author = "" + m.artist = "" + m.thumbnail_url = "" + m.genre = "a list of genres" + m.description = "long description" + m.url = "url to manga" + m.favorite = true + return m + } + + private fun createMangas(vararg titles: String): List { + val mangas = ArrayList() + for (title in titles) { + mangas.add(createManga(title)) + } + return mangas + } + + private fun createChapter(manga: Manga, url: String): Chapter { + val c = Chapter.create() + c.url = url + c.name = url + c.manga_id = manga.id + return c + } + + private fun createChapters(manga: Manga, vararg urls: String): List { + val chapters = ArrayList() + for (url in urls) { + chapters.add(createChapter(manga, url)) + } + return chapters + } + + private fun createMangaSync(manga: Manga, syncId: Int): MangaSync { + val m = MangaSync.create(syncId) + m.manga_id = manga.id!! + m.title = "title" + return m + } + + private fun createMangaSync(manga: Manga, vararg syncIds: Int): List { + val ms = ArrayList() + for (title in syncIds) { + ms.add(createMangaSync(manga, title)) + } + return ms + } + + private fun toJson(element: Any): JsonElement { + return gson.toJsonTree(element) + } + +} \ No newline at end of file diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.java b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.java deleted file mode 100644 index 0b392df1a7..0000000000 --- a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.java +++ /dev/null @@ -1,115 +0,0 @@ -package eu.kanade.tachiyomi.data.library; - -import android.content.Context; -import android.os.Build; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.Robolectric; -import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; - -import java.util.ArrayList; -import java.util.List; - -import eu.kanade.tachiyomi.BuildConfig; -import eu.kanade.tachiyomi.CustomRobolectricGradleTestRunner; -import eu.kanade.tachiyomi.data.database.models.Chapter; -import eu.kanade.tachiyomi.data.database.models.Manga; -import eu.kanade.tachiyomi.data.source.online.OnlineSource; -import rx.Observable; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP) -@RunWith(CustomRobolectricGradleTestRunner.class) -public class LibraryUpdateServiceTest { - - ShadowApplication app; - Context context; - LibraryUpdateService service; - OnlineSource source; - - @Before - public void setup() { - app = ShadowApplication.getInstance(); - context = app.getApplicationContext(); - service = Robolectric.setupService(LibraryUpdateService.class); - source = mock(OnlineSource.class); - when(service.sourceManager.get(anyInt())).thenReturn(source); - } - - @Test - public void testLifecycle() { - // Smoke test - Robolectric.buildService(LibraryUpdateService.class) - .attach() - .create() - .startCommand(0, 0) - .destroy() - .get(); - } - - @Test - public void testUpdateManga() { - Manga manga = createManga("/manga1").get(0); - manga.id = 1L; - service.db.insertManga(manga).executeAsBlocking(); - - List sourceChapters = createChapters("/chapter1", "/chapter2"); - - when(source.fetchChapterList(manga)).thenReturn(Observable.just(sourceChapters)); - - service.updateManga(manga).subscribe(); - - assertThat(service.db.getChapters(manga).executeAsBlocking()).hasSize(2); - } - - @Test - public void testContinuesUpdatingWhenAMangaFails() { - List favManga = createManga("/manga1", "/manga2", "/manga3"); - service.db.insertMangas(favManga).executeAsBlocking(); - favManga = service.db.getFavoriteMangas().executeAsBlocking(); - - List chapters = createChapters("/chapter1", "/chapter2"); - List chapters3 = createChapters("/achapter1", "/achapter2"); - - // One of the updates will fail - when(source.fetchChapterList(favManga.get(0))).thenReturn(Observable.just(chapters)); - when(source.fetchChapterList(favManga.get(1))).thenReturn(Observable.>error(new Exception())); - when(source.fetchChapterList(favManga.get(2))).thenReturn(Observable.just(chapters3)); - - service.updateMangaList(service.getMangaToUpdate(null)).subscribe(); - - // There are 3 network attempts and 2 insertions (1 request failed) - assertThat(service.db.getChapters(favManga.get(0)).executeAsBlocking()).hasSize(2); - assertThat(service.db.getChapters(favManga.get(1)).executeAsBlocking()).hasSize(0); - assertThat(service.db.getChapters(favManga.get(2)).executeAsBlocking()).hasSize(2); - } - - private List createChapters(String... urls) { - List list = new ArrayList<>(); - for (String url : urls) { - Chapter c = Chapter.create(); - c.url = url; - c.name = url.substring(1); - list.add(c); - } - return list; - } - - private List createManga(String... urls) { - List list = new ArrayList<>(); - for (String url : urls) { - Manga m = Manga.create(url); - m.title = url.substring(1); - m.favorite = true; - list.add(m); - } - return list; - } -} diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt new file mode 100644 index 0000000000..49c1d5de2b --- /dev/null +++ b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt @@ -0,0 +1,137 @@ +package eu.kanade.tachiyomi.data.library + +import android.app.Application +import android.content.Context +import android.os.Build +import eu.kanade.tachiyomi.AppModule +import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.CustomRobolectricGradleTestRunner +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.source.SourceManager +import eu.kanade.tachiyomi.data.source.online.OnlineSource +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Matchers.anyInt +import org.mockito.Mockito +import org.mockito.Mockito.* +import org.robolectric.Robolectric +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.InjektModule +import uy.kohesive.injekt.api.InjektRegistrar +import uy.kohesive.injekt.api.InjektScope +import uy.kohesive.injekt.api.addSingleton +import uy.kohesive.injekt.registry.default.DefaultRegistrar +import java.util.* + +@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP)) +@RunWith(CustomRobolectricGradleTestRunner::class) +class LibraryUpdateServiceTest { + + lateinit var app: Application + lateinit var context: Context + lateinit var service: LibraryUpdateService + lateinit var source: OnlineSource + + @Before + fun setup() { + app = RuntimeEnvironment.application + context = app.applicationContext + + // Mock the source manager + val module = object : InjektModule { + override fun InjektRegistrar.registerInjectables() { + addSingleton(Mockito.mock(SourceManager::class.java, RETURNS_DEEP_STUBS)) + } + } + + // Restart injections for each test + Injekt = InjektScope(DefaultRegistrar()) + Injekt.importModule(AppModule(app)) + Injekt.importModule(module) + + service = Robolectric.setupService(LibraryUpdateService::class.java) + source = mock(OnlineSource::class.java) + `when`(service.sourceManager.get(anyInt())).thenReturn(source) + } + + @Test + fun testLifecycle() { + println(service.db) + + // Smoke test + Robolectric.buildService(LibraryUpdateService::class.java) + .attach() + .create() + .startCommand(0, 0) + .destroy() + .get() + } + + @Test + fun testUpdateManga() { + println(service.db) + + val manga = createManga("/manga1")[0] + manga.id = 1L + service.db.insertManga(manga).executeAsBlocking() + + val sourceChapters = createChapters("/chapter1", "/chapter2") + + `when`(source.fetchChapterList(manga)).thenReturn(Observable.just(sourceChapters)) + + service.updateManga(manga).subscribe() + + assertThat(service.db.getChapters(manga).executeAsBlocking()).hasSize(2) + } + + @Test + fun testContinuesUpdatingWhenAMangaFails() { + var favManga = createManga("/manga1", "/manga2", "/manga3") + println(service.db) + service.db.insertMangas(favManga).executeAsBlocking() + favManga = service.db.getFavoriteMangas().executeAsBlocking() + + val chapters = createChapters("/chapter1", "/chapter2") + val chapters3 = createChapters("/achapter1", "/achapter2") + + // One of the updates will fail + `when`(source.fetchChapterList(favManga[0])).thenReturn(Observable.just(chapters)) + `when`(source.fetchChapterList(favManga[1])).thenReturn(Observable.error>(Exception())) + `when`(source.fetchChapterList(favManga[2])).thenReturn(Observable.just(chapters3)) + + service.updateMangaList(service.getMangaToUpdate(null)).subscribe() + + // There are 3 network attempts and 2 insertions (1 request failed) + assertThat(service.db.getChapters(favManga[0]).executeAsBlocking()).hasSize(2) + assertThat(service.db.getChapters(favManga[1]).executeAsBlocking()).hasSize(0) + assertThat(service.db.getChapters(favManga[2]).executeAsBlocking()).hasSize(2) + } + + private fun createChapters(vararg urls: String): List { + val list = ArrayList() + for (url in urls) { + val c = Chapter.create() + c.url = url + c.name = url.substring(1) + list.add(c) + } + return list + } + + private fun createManga(vararg urls: String): List { + val list = ArrayList() + for (url in urls) { + val m = Manga.create(url) + m.title = url.substring(1) + m.favorite = true + list.add(m) + } + return list + } +} diff --git a/app/src/test/java/eu/kanade/tachiyomi/injection/module/TestDataModule.kt b/app/src/test/java/eu/kanade/tachiyomi/injection/module/TestDataModule.kt deleted file mode 100644 index 3d98dc6c07..0000000000 --- a/app/src/test/java/eu/kanade/tachiyomi/injection/module/TestDataModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.injection.module - -import android.app.Application -import eu.kanade.tachiyomi.data.network.NetworkHelper -import eu.kanade.tachiyomi.data.source.SourceManager -import org.mockito.Mockito - -class TestDataModule : DataModule() { - - override fun provideNetworkHelper(app: Application): NetworkHelper { - return Mockito.mock(NetworkHelper::class.java) - } - - override fun provideSourceManager(app: Application): SourceManager { - return Mockito.mock(SourceManager::class.java, Mockito.RETURNS_DEEP_STUBS) - } - -}