瀏覽代碼

Merge pull request #350 from inorichi/dev

Rewrite DB models, tests and add a chapter loader.
inorichi 8 年之前
父節點
當前提交
5c98e020f4
共有 100 個文件被更改,包括 1775 次插入1934 次删除
  1. 24 28
      app/build.gradle
  2. 2 17
      app/src/main/java/eu/kanade/tachiyomi/App.kt
  3. 41 0
      app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
  4. 7 24
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
  5. 2 1
      app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt
  6. 2 1
      app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt
  7. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt
  8. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt
  9. 2 1
      app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaSyncTypeMapping.kt
  10. 2 1
      app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt
  11. 0 57
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.java
  12. 27 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
  13. 26 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
  14. 0 94
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.java
  15. 36 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt
  16. 39 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt
  17. 0 58
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.java
  18. 44 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt
  19. 0 213
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.java
  20. 131 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
  21. 0 29
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.java
  22. 21 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt
  23. 0 12
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.java
  24. 3 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt
  25. 0 27
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.java
  26. 10 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
  27. 53 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
  28. 0 78
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.java
  29. 40 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.kt
  30. 43 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSyncImpl.kt
  31. 0 75
      app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt
  32. 1 5
      app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt
  33. 10 10
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
  34. 3 5
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
  35. 5 7
      app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt
  36. 4 9
      app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt
  37. 5 6
      app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
  38. 3 8
      app/src/main/java/eu/kanade/tachiyomi/data/mangasync/MangaSyncService.kt
  39. 3 5
      app/src/main/java/eu/kanade/tachiyomi/data/mangasync/UpdateMangaSyncService.kt
  40. 4 4
      app/src/main/java/eu/kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt
  41. 0 2
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
  42. 0 2
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
  43. 4 10
      app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.java
  44. 14 10
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt
  45. 2 4
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt
  46. 5 7
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt
  47. 4 6
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt
  48. 2 2
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt
  49. 3 3
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt
  50. 3 3
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt
  51. 3 3
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt
  52. 2 2
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt
  53. 2 2
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt
  54. 2 2
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt
  55. 2 6
      app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt
  56. 0 16
      app/src/main/java/eu/kanade/tachiyomi/injection/AppComponentFactory.java
  57. 0 97
      app/src/main/java/eu/kanade/tachiyomi/injection/ComponentReflectionInjector.java
  58. 0 67
      app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt
  59. 0 21
      app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.kt
  60. 0 70
      app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.kt
  61. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupPresenter.kt
  62. 0 1
      app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt
  63. 0 1
      app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.kt
  64. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueAdapter.kt
  65. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt
  66. 5 5
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt
  67. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt
  68. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt
  69. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt
  70. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt
  71. 3 3
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt
  72. 7 7
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
  73. 2 5
      app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
  74. 3 3
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
  75. 19 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterModel.kt
  76. 3 4
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt
  77. 11 12
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt
  78. 29 36
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt
  79. 230 75
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt
  80. 6 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt
  81. 4 4
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt
  82. 4 4
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt
  83. 138 0
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt
  84. 12 19
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
  85. 13 0
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt
  86. 313 265
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
  87. 22 23
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt
  88. 3 3
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt
  89. 5 5
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt
  90. 22 0
      app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapter.kt
  91. 6 6
      app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt
  92. 82 83
      app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt
  93. 49 50
      app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersHolder.kt
  94. 94 178
      app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt
  95. 3 4
      app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/SectionViewHolder.kt
  96. 3 3
      app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt
  97. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt
  98. 7 9
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt
  99. 22 0
      app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt
  100. 0 1
      app/src/main/res/values/keys.xml

+ 24 - 28
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"
 }
 

+ 2 - 17
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<AppComponent>
-        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)
     }

+ 41 - 0
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() }
+
+    }
+
+}

+ 7 - 24
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<Category>(jsonCategories,
-                object : TypeToken<List<Category>>() {}.type)
+        val backupCategories = gson.fromJson<List<CategoryImpl>>(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<List<Chapter>>() {}.type
-        val mangaSyncToken = object : TypeToken<List<MangaSync>>() {}.type
-        val categoriesNamesToken = object : TypeToken<List<String>>() {}.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<Chapter>(element.get(CHAPTERS), chapterToken)
-            val sync = getArrayOrEmpty<MangaSync>(element.get(MANGA_SYNC), mangaSyncToken)
-            val categories = getArrayOrEmpty<String>(element.get(CATEGORIES), categoriesNamesToken)
+            val manga = gson.fromJson(element.get(MANGA), MangaImpl::class.java)
+            val chapters = gson.fromJson<List<ChapterImpl>>(element.get(CHAPTERS) ?: JsonArray())
+            val sync = gson.fromJson<List<MangaSyncImpl>>(element.get(MANGA_SYNC) ?: JsonArray())
+            val categories = gson.fromJson<List<String>>(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<MangaSync>) {
         // 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 <T> getArrayOrEmpty(element: JsonElement?, type: Type): List<T> {
-        return gson.fromJson<List<T>>(element, type) ?: ArrayList<T>()
-    }
-
 }

+ 2 - 1
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<Category>() {
 
 class CategoryGetResolver : DefaultGetResolver<Category>() {
 
-    override fun mapFromCursor(cursor: Cursor) = Category().apply {
+    override fun mapFromCursor(cursor: Cursor): Category = CategoryImpl().apply {
         id = cursor.getInt(cursor.getColumnIndex(COL_ID))
         name = cursor.getString(cursor.getColumnIndex(COL_NAME))
         order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))

+ 2 - 1
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<Chapter>() {
 
 class ChapterGetResolver : DefaultGetResolver<Chapter>() {
 
-    override fun mapFromCursor(cursor: Cursor) = Chapter().apply {
+    override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply {
         id = cursor.getLong(cursor.getColumnIndex(COL_ID))
         manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
         url = cursor.getString(cursor.getColumnIndex(COL_URL))

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt

@@ -44,7 +44,7 @@ class HistoryPutResolver : DefaultPutResolver<History>() {
 
 class HistoryGetResolver : DefaultGetResolver<History>() {
 
-    override fun mapFromCursor(cursor: Cursor) = History().apply {
+    override fun mapFromCursor(cursor: Cursor): History = History().apply {
         id = cursor.getLong(cursor.getColumnIndex(COL_ID))
         chapter_id = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_ID))
         last_read = cursor.getLong(cursor.getColumnIndex(COL_LAST_READ))

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt

@@ -42,7 +42,7 @@ class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
 
 class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
 
-    override fun mapFromCursor(cursor: Cursor) = MangaCategory().apply {
+    override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply {
         id = cursor.getLong(cursor.getColumnIndex(COL_ID))
         manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
         category_id = cursor.getInt(cursor.getColumnIndex(COL_CATEGORY_ID))

+ 2 - 1
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<MangaSync>() {
 
 class MangaSyncGetResolver : DefaultGetResolver<MangaSync>() {
 
-    override fun mapFromCursor(cursor: Cursor) = MangaSync().apply {
+    override fun mapFromCursor(cursor: Cursor): MangaSync = MangaSyncImpl().apply {
         id = cursor.getLong(cursor.getColumnIndex(COL_ID))
         manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
         sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID))

+ 2 - 1
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<Manga>() {
 
 open class MangaGetResolver : DefaultGetResolver<Manga>() {
 
-    override fun mapFromCursor(cursor: Cursor) = Manga().apply {
+    override fun mapFromCursor(cursor: Cursor): Manga = MangaImpl().apply {
         id = cursor.getLong(cursor.getColumnIndex(COL_ID))
         source = cursor.getInt(cursor.getColumnIndex(COL_SOURCE))
         url = cursor.getString(cursor.getColumnIndex(COL_URL))

+ 0 - 57
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.java

@@ -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();
-    }
-}

+ 27 - 0
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 }
+    }
+
+}

+ 26 - 0
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()
+    }
+
+}

+ 0 - 94
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.java

@@ -1,94 +0,0 @@
-package eu.kanade.tachiyomi.data.database.models;
-
-import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
-import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
-
-import java.io.Serializable;
-import java.util.List;
-
-import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
-import eu.kanade.tachiyomi.data.download.model.Download;
-import eu.kanade.tachiyomi.data.source.model.Page;
-import eu.kanade.tachiyomi.util.UrlUtil;
-
-@StorIOSQLiteType(table = ChapterTable.TABLE)
-public class Chapter implements Serializable {
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_ID, key = true)
-    public Long id;
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_MANGA_ID)
-    public Long manga_id;
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_URL)
-    public String url;
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_NAME)
-    public String name;
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_READ)
-    public boolean read;
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_LAST_PAGE_READ)
-    public int last_page_read;
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_DATE_FETCH)
-    public long date_fetch;
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_DATE_UPLOAD)
-    public long date_upload;
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_CHAPTER_NUMBER)
-    public float chapter_number;
-
-    @StorIOSQLiteColumn(name = ChapterTable.COL_SOURCE_ORDER)
-    public int source_order;
-
-    public int status;
-
-    private transient List<Page> pages;
-
-    public Chapter() {}
-
-    public void setUrl(String url) {
-        this.url = UrlUtil.getPath(url);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        Chapter chapter = (Chapter) o;
-
-        return url.equals(chapter.url);
-
-    }
-
-    @Override
-    public int hashCode() {
-        return url.hashCode();
-    }
-
-    public static Chapter create() {
-        Chapter chapter = new Chapter();
-        chapter.chapter_number = -1;
-        return chapter;
-    }
-
-    public List<Page> getPages() {
-        return pages;
-    }
-
-    public void setPages(List<Page> pages) {
-        this.pages = pages;
-    }
-
-    public boolean isDownloaded() {
-        return status == Download.DOWNLOADED;
-    }
-
-    public boolean isRecognizedNumber() {
-        return chapter_number >= 0f;
-    }
-}

+ 36 - 0
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
+        }
+    }
+}

+ 39 - 0
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()
+    }
+
+}

+ 0 - 58
app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.java

@@ -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;
-    }
-}
-

+ 44 - 0
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
+        }
+    }
+}

+ 0 - 213
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.java

@@ -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();
-    }
-}

+ 131 - 0
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
+        }
+    }
+
+}

+ 0 - 29
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.java

@@ -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;
-    }
-
-}

+ 21 - 0
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
+        }
+    }
+
+}

+ 0 - 12
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.java

@@ -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;
-    }
-}

+ 3 - 0
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)

+ 0 - 27
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.java

@@ -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;
-    }
-}

+ 10 - 0
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)

+ 53 - 0
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()
+    }
+
+}

+ 0 - 78
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.java

@@ -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;
-    }
-}

+ 40 - 0
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
+        }
+    }
+
+}

+ 43 - 0
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
+    }
+
+}

+ 0 - 75
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<Chapter> {
-        // Add a delta to the chapter number, because binary decimal representation
-        // can retrieve the same chapter again
-        val chapterNumber = chapter.chapter_number + 0.00001
-
-        return db.get()
-                .`object`(Chapter::class.java)
-                .withQuery(Query.builder()
-                        .table(ChapterTable.TABLE)
-                        .where("${ChapterTable.COL_MANGA_ID} = ? AND " +
-                                "${ChapterTable.COL_CHAPTER_NUMBER} > ? AND " +
-                                "${ChapterTable.COL_CHAPTER_NUMBER} <= ?")
-                        .whereArgs(chapter.manga_id, chapterNumber, chapterNumber + 1)
-                        .orderBy(ChapterTable.COL_CHAPTER_NUMBER)
-                        .limit(1)
-                        .build())
-                .prepare()
-    }
-
-    fun getNextChapterBySource(chapter: Chapter) = db.get()
-            .`object`(Chapter::class.java)
-            .withQuery(Query.builder()
-                    .table(ChapterTable.TABLE)
-                    .where("""${ChapterTable.COL_MANGA_ID} = ? AND
-                            ${ChapterTable.COL_SOURCE_ORDER} < ?""")
-                    .whereArgs(chapter.manga_id, chapter.source_order)
-                    .orderBy("${ChapterTable.COL_SOURCE_ORDER} DESC")
-                    .limit(1)
-                    .build())
-            .prepare()
-
-    fun getPreviousChapter(chapter: Chapter): PreparedGetObject<Chapter> {
-        // Add a delta to the chapter number, because binary decimal representation
-        // can retrieve the same chapter again
-        val chapterNumber = chapter.chapter_number - 0.00001
-
-        return db.get()
-                .`object`(Chapter::class.java)
-                .withQuery(Query.builder().table(ChapterTable.TABLE)
-                        .where("${ChapterTable.COL_MANGA_ID} = ? AND " +
-                                "${ChapterTable.COL_CHAPTER_NUMBER} < ? AND " +
-                                "${ChapterTable.COL_CHAPTER_NUMBER} >= ?")
-                        .whereArgs(chapter.manga_id, chapterNumber, chapterNumber - 1)
-                        .orderBy("${ChapterTable.COL_CHAPTER_NUMBER} DESC")
-                        .limit(1)
-                        .build())
-                .prepare()
-    }
-
-    fun getPreviousChapterBySource(chapter: Chapter) = db.get()
-            .`object`(Chapter::class.java)
-            .withQuery(Query.builder()
-                    .table(ChapterTable.TABLE)
-                    .where("""${ChapterTable.COL_MANGA_ID} = ? AND
-                            ${ChapterTable.COL_SOURCE_ORDER} > ?""")
-                    .whereArgs(chapter.manga_id, chapter.source_order)
-                    .orderBy(ChapterTable.COL_SOURCE_ORDER)
-                    .limit(1)
-                    .build())
-            .prepare()
-
-    fun getNextUnreadChapter(manga: Manga) = db.get()
-            .`object`(Chapter::class.java)
-            .withQuery(Query.builder()
-                    .table(ChapterTable.TABLE)
-                    .where("${ChapterTable.COL_MANGA_ID} = ? AND " +
-                            "${ChapterTable.COL_READ} = ? AND " +
-                            "${ChapterTable.COL_CHAPTER_NUMBER} >= ?")
-                    .whereArgs(manga.id, 0, 0)
-                    .orderBy(ChapterTable.COL_CHAPTER_NUMBER)
-                    .limit(1)
-                    .build())
-            .prepare()
-
     fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
 
     fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()

+ 1 - 5
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<MangaChapterHistory>() {
@@ -46,10 +45,7 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>()
         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)
     }
 }

+ 10 - 10
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

+ 3 - 5
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()
 

+ 5 - 7
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<NetworkHelper>().client)
+
+        glide.register(GlideUrl::class.java, InputStream::class.java, networkFactory)
         glide.register(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory())
     }
 }

+ 4 - 9
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<Manga> {
     /**
      * 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<Manga> {
      */
     private val cachedHeaders = hashMapOf<Int, LazyHeaders>()
 
-    init {
-        App.get(context).component.inject(this)
-    }
-
     /**
      * Factory class for creating [MangaModelLoader] instances.
      */
@@ -88,7 +83,7 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
         // Obtain the request url and the file for this url from the LRU cache, or calculate it
         // 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)
             }
 

+ 5 - 6
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()
     }
 

+ 3 - 8
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

+ 3 - 5
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()
     }
 

+ 4 - 4
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")

+ 0 - 2
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)

+ 0 - 2
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"))

+ 4 - 10
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<Page> chapterPages = chapter.getPages();
-        return chapterPages.size() -1 == pageNumber;
-    }
 }

+ 14 - 10
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) {
 

+ 2 - 4
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 = [email protected]
+            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 = [email protected]
+            Manga.create(id).apply {
                 searchMangaFromElement(element, this)
                 page.mangas.add(this)
             }

+ 5 - 7
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 = [email protected]
+            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 = [email protected]
+            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

+ 4 - 6
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 = [email protected]
+            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 = [email protected]
+            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)

+ 2 - 2
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

+ 3 - 3
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
     }

+ 3 - 3
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
     }

+ 3 - 3
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
     }

+ 2 - 2
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

+ 2 - 2
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

+ 2 - 2
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

+ 2 - 6
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<String, Int, UpdateDownloader.DownloadResult>() {
@@ -40,7 +39,7 @@ class UpdateDownloader(private val context: Context) :
         }
     }
 
-    @Inject lateinit var network: NetworkHelper
+    val network: NetworkHelper by injectLazy()
 
     /**
      * Default download dir
@@ -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

+ 0 - 16
app/src/main/java/eu/kanade/tachiyomi/injection/AppComponentFactory.java

@@ -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();
-    }
-}
-
-

+ 0 - 97
app/src/main/java/eu/kanade/tachiyomi/injection/ComponentReflectionInjector.java

@@ -1,97 +0,0 @@
-package eu.kanade.tachiyomi.injection;
-
-import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * This class allows to inject into objects through a base class,
- * so we don't have to repeat injection code everywhere.
- *
- * The performance drawback is about 0.013 ms per injection on a very slow device,
- * which is negligible in most cases.
- *
- * Example:
- * <pre>{@code
- * Component {
- *     void inject(B b);
- * }
- *
- * class A {
- *     void onCreate() {
- *         componentReflectionInjector.inject(this);
- *     }
- * }
- *
- * class B extends A {
- *     @Inject MyDependency dependency;
- * }
- *
- * new B().onCreate() // dependency will be injected at this point
- *
- * class C extends B {
- *
- * }
- *
- * new C().onCreate() // dependency will be injected at this point as well
- * }</pre>
- *
- * @param <T> a type of dagger 2 component.
- */
-public final class ComponentReflectionInjector<T> {
-
-    private static final ConcurrentHashMap<Class<?>, HashMap<Class<?>, Method>> cache = new ConcurrentHashMap<>();
-
-    private final Class<T> componentClass;
-    private final T component;
-    private final HashMap<Class<?>, Method> methods;
-
-    public ComponentReflectionInjector(Class<T> componentClass, T component) {
-        this.componentClass = componentClass;
-        this.component = component;
-        this.methods = getMethods(componentClass);
-    }
-
-    public T getComponent() {
-        return component;
-    }
-
-    public void inject(Object target) {
-
-        Class targetClass = target.getClass();
-        Method method = methods.get(targetClass);
-        while (method == null && targetClass != null) {
-            targetClass = targetClass.getSuperclass();
-            method = methods.get(targetClass);
-        }
-
-        if (method == null)
-            throw new RuntimeException(String.format("No %s injecting method exists in %s component", target.getClass(), componentClass));
-
-        try {
-            method.invoke(component, target);
-        }
-        catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static HashMap<Class<?>, Method> getMethods(Class componentClass) {
-        HashMap<Class<?>, Method> methods = cache.get(componentClass);
-        if (methods == null) {
-            synchronized (cache) {
-                methods = cache.get(componentClass);
-                if (methods == null) {
-                    methods = new HashMap<>();
-                    for (Method method : componentClass.getMethods()) {
-                        Class<?>[] params = method.getParameterTypes();
-                        if (params.length == 1)
-                            methods.put(params[0], method);
-                    }
-                    cache.put(componentClass, methods);
-                }
-            }
-        }
-        return methods;
-    }
-}

+ 0 - 67
app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt

@@ -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
-
-}

+ 0 - 21
app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.kt

@@ -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
-    }
-
-}

+ 0 - 70
app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.kt

@@ -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)
-    }
-
-}

+ 2 - 2
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<BackupFragment>() {
     /**
      * Database.
      */
-    @Inject lateinit var db: DatabaseHelper
+    val db: DatabaseHelper by injectLazy()
 
     /**
      * Backup manager.

+ 0 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt

@@ -12,7 +12,6 @@ abstract class BaseRxActivity<P : BasePresenter<*>> : NucleusAppCompatActivity<P
         setPresenterFactory {
             superFactory.createPresenter().apply {
                 val app = application as App
-                app.componentReflection.inject(this)
                 context = app.applicationContext
             }
         }

+ 0 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.kt

@@ -12,7 +12,6 @@ abstract class BaseRxFragment<P : BasePresenter<*>> : NucleusSupportFragment<P>(
         setPresenterFactory {
             superFactory.createPresenter().apply {
                 val app = activity.application as App
-                app.componentReflection.inject(this)
                 context = app.applicationContext
             }
         }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueAdapter.kt

@@ -56,7 +56,7 @@ class CatalogueAdapter(val fragment: CatalogueFragment) : FlexibleAdapter<Catalo
      * @return an identifier for the item.
      */
     override fun getItemId(position: Int): Long {
-        return mItems[position].id
+        return mItems[position].id!!
     }
 
     /**

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt

@@ -383,7 +383,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), 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
     }
 
     /**

+ 5 - 5
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<CatalogueFragment>() {
     /**
      * 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.

+ 2 - 2
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<CategoryActivity>() {
     /**
      * Used to connect to database
      */
-    @Inject lateinit var db: DatabaseHelper
+    val db: DatabaseHelper by injectLazy()
 
     /**
      * List containing categories

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt

@@ -35,7 +35,7 @@ class DownloadAdapter(private val context: Context) : FlexibleAdapter<DownloadHo
      * @return an identifier for the item.
      */
     override fun getItemId(position: Int): Long {
-        return getItem(position).chapter.id
+        return getItem(position).chapter.id!!
     }
 
     /**

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt

@@ -262,7 +262,7 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
      * @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
     }
 
     /**

+ 2 - 2
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<DownloadFragment>() {
     /**
      * Download manager.
      */
-    @Inject lateinit var downloadManager: DownloadManager
+    val downloadManager: DownloadManager by injectLazy()
 
     /**
      * Property to get the queue from the download manager.

+ 3 - 3
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)
     }
 
     /**

+ 7 - 7
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<LibraryFragment>() {
     /**
      * 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<LibraryFragment>() {
     @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

+ 2 - 5
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)
 

+ 3 - 3
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<MangaActivity>() {
     /**
      * 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.

+ 19 - 0
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
+
+}

+ 3 - 4
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<ChaptersHolder, Chapter>() {
+class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<ChaptersHolder, ChapterModel>() {
 
     init {
         setHasStableIds(true)
@@ -30,10 +29,10 @@ class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<Chapters
     }
 
     override fun getItemId(position: Int): Long {
-        return mItems[position].id
+        return mItems[position].id!!
     }
 
-    fun setItems(chapters: List<Chapter>) {
+    fun setItems(chapters: List<ChapterModel>) {
         mItems = chapters
         notifyDataSetChanged()
     }

+ 11 - 12
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt

@@ -134,8 +134,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), 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<ChaptersPresenter>(), ActionMode.Callbac
         setDownloadedFilter()
     }
 
-    fun onNextChapters(chapters: List<Chapter>) {
+    fun onNextChapters(chapters: List<ChapterModel>) {
         // If the list is empty, fetch chapters from source if the conditions are met
         // We use presenter chapters instead because they are always unfiltered
         if (presenter.chapters.isEmpty())
@@ -206,7 +205,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), 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<ChaptersPresenter>(), 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<ChaptersPresenter>(), ActionMode.Callbac
         actionMode = null
     }
 
-    fun getSelectedChapters(): List<Chapter> {
+    fun getSelectedChapters(): List<ChapterModel> {
         return adapter.selectedItems.map { adapter.getItem(it) }
     }
 
@@ -322,27 +321,27 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
         setContextTitle(adapter.selectedItemCount)
     }
 
-    fun markAsRead(chapters: List<Chapter>) {
+    fun markAsRead(chapters: List<ChapterModel>) {
         presenter.markChaptersRead(chapters, true)
         if (presenter.preferences.removeAfterMarkedAsRead()) {
             deleteChapters(chapters)
         }
     }
 
-    fun markAsUnread(chapters: List<Chapter>) {
+    fun markAsUnread(chapters: List<ChapterModel>) {
         presenter.markChaptersRead(chapters, false)
     }
 
-    fun markPreviousAsRead(chapter: Chapter) {
+    fun markPreviousAsRead(chapter: ChapterModel) {
         presenter.markPreviousChaptersAsRead(chapter)
     }
 
-    fun downloadChapters(chapters: List<Chapter>) {
+    fun downloadChapters(chapters: List<ChapterModel>) {
         destroyActionModeIfNeeded()
         presenter.downloadChapters(chapters)
     }
 
-    fun deleteChapters(chapters: List<Chapter>) {
+    fun deleteChapters(chapters: List<ChapterModel>) {
         destroyActionModeIfNeeded()
         DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
         presenter.deleteChapters(chapters)
@@ -350,7 +349,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
 
     fun onChaptersDeleted() {
         dismissDeletingDialog()
-        adapter.notifyDataSetChanged()
+        adapter.notifyItemRangeChanged(0, adapter.itemCount)
     }
 
     fun onChaptersDeletedError(error: Throwable) {

+ 29 - 36
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)
-
-            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)
+            val chapterList = listOf(chapter)
+
+            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
         }
 

+ 230 - 75
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<ChaptersFragment>() {
 
-    @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<Chapter>
+    /**
+     * List of chapters of the manga. It's always unfiltered and unsorted.
+     */
+    lateinit var chapters: List<ChapterModel>
         private set
 
-    lateinit var chaptersSubject: PublishSubject<List<Chapter>>
-        private set
+    /**
+     * Subject of list of chapters to allow updating the view without going to DB.
+     */
+    val chaptersSubject by lazy { PublishSubject.create<List<ChapterModel>>() }
 
-    var hasRequested: Boolean = false
+    /**
+     * Whether the chapter list has been requested to the source.
+     */
+    var hasRequested = false
         private set
 
-    private val DB_CHAPTERS = 1
-    private val FETCH_CHAPTERS = 2
-    private val CHAPTER_STATUS_CHANGES = 3
+    companion object {
+        /**
+         * Id of the restartable which sends a filtered and ordered list of chapters to the view.
+         */
+        private const val GET_CHAPTERS = 1
+
+        /**
+         * 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<Pair<Int, Int>> {
-        return source.fetchChapterList(manga)
-                .subscribeOn(Schedulers.io())
-                .map { syncChaptersWithSource(db, it, manga, source) }
-                .observeOn(AndroidSchedulers.mainThread())
-    }
-
-    fun getDbChaptersObs(): Observable<List<Chapter>> {
-        return chaptersSubject
-                .flatMap { applyChapterFilters(it) }
-                .observeOn(AndroidSchedulers.mainThread())
-    }
-
-    fun getChapterStatusObs(): Observable<Download> {
-        return downloadManager.queue.getStatusObservable()
-                .observeOn(AndroidSchedulers.mainThread())
-                .filter { download -> download.manga.id == manga.id }
-                .doOnNext { updateChapterStatus(it) }
-    }
-
-    private fun applyChapterFilters(chapters: List<Chapter>): Observable<List<Chapter>> {
+    /**
+     * 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())
+
+    /**
+     * 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) }
+
+    /**
+     * Applies the view filters to the list of chapters obtained from the database.
+     *
+     * @param chapters the list of chapters from the database
+     * @return an observable of the list of chapters filtered and sorted.
+     */
+    private fun applyChapterFilters(chapters: List<ChapterModel>): Observable<List<ChapterModel>> {
         var observable = Observable.from(chapters).subscribeOn(Schedulers.io())
         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<ChaptersFragment>() {
         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<Chapter>, read: Boolean) {
+    /**
+     * Mark the selected chapter list as read/unread.
+     *
+     * @param selectedChapters the list of selected chapters.
+     * @param read whether to mark chapters as read or unread.
+     */
+    fun markChaptersRead(selectedChapters: List<ChapterModel>, read: Boolean) {
         Observable.from(selectedChapters)
                 .doOnNext { chapter ->
                     chapter.read = read
@@ -181,21 +273,36 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
                 .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<Chapter>) {
+    /**
+     * Downloads the given list of chapters with the manager.
+     *
+     * @param chapters the list of chapters to download.
+     */
+    fun downloadChapters(chapters: List<ChapterModel>) {
         DownloadService.start(context)
         downloadManager.downloadChapters(manga, chapters)
     }
 
-    fun deleteChapters(chapters: List<Chapter>) {
+    /**
+     * Deletes the given list of chapter.
+     *
+     * @param chapters the list of chapters to delete.
+     */
+    fun deleteChapters(chapters: List<ChapterModel>) {
         val wasRunning = downloadManager.isRunning
         if (wasRunning) {
             DownloadService.stop(context)
@@ -216,49 +323,97 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
                 })
     }
 
-    private fun deleteChapter(chapter: Chapter) {
+    /**
+     * Deletes a chapter from disk. This method is called in a background thread.
+     *
+     * @param chapter the chapter to delete.
+     */
+    private fun deleteChapter(chapter: ChapterModel) {
         downloadManager.queue.del(chapter)
         downloadManager.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()
     }

+ 6 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt

@@ -104,7 +104,12 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
         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

+ 4 - 4
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<MangaInfoFragment>() {
     /**
      * 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.

+ 4 - 4
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<MyAnimeListFragment>() {
 
-    @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<MyAnimeListFragment>() {
 
     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())

+ 138 - 0
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<PriorityPage>()
+    private val subscriptions = CompositeSubscription()
+
+    fun init() {
+        prepareOnlineReading()
+    }
+
+    fun restart() {
+        cleanup()
+        init()
+    }
+
+    fun cleanup() {
+        subscriptions.clear()
+        queue.clear()
+    }
+
+    private fun prepareOnlineReading() {
+        subscriptions += Observable.defer { Observable.just(queue.take().page) }
+                .filter { it.status == Page.QUEUE }
+                .concatMap { source.fetchImage(it) }
+                .repeat()
+                .subscribeOn(Schedulers.io())
+                .subscribe({
+                }, {
+                    if (it !is InterruptedException) {
+                        Timber.e(it, it.message)
+                    }
+                })
+    }
+
+    fun loadChapter(chapter: ReaderChapter) = Observable.just(chapter)
+            .flatMap {
+                if (chapter.pages == null)
+                    retrievePageList(chapter)
+                else
+                    Observable.just(chapter.pages!!)
+            }
+            .doOnNext { pages ->
+                // Now that the number of pages is known, fix the requested page if the last one
+                // was requested.
+                if (chapter.requestedPage == -1) {
+                    chapter.requestedPage = pages.lastIndex
+                }
+
+                loadPages(chapter)
+            }
+            .map { chapter }
+
+    private fun retrievePageList(chapter: ReaderChapter) = Observable.just(chapter)
+            .flatMap {
+                // Check if the chapter is downloaded.
+                chapter.isDownloaded = downloadManager.isChapterDownloaded(source, manga, chapter)
+
+                // Fetch the page list from disk.
+                if (chapter.isDownloaded)
+                    Observable.just(downloadManager.getSavedPageList(source, manga, chapter)!!)
+                // Fetch the page list from cache or fallback to network
+                else
+                    source.fetchPageList(chapter)
+            }
+            .doOnNext { pages ->
+                chapter.pages = pages
+                pages.forEach { it.chapter = chapter }
+            }
+
+    private fun loadPages(chapter: ReaderChapter) {
+        if (chapter.isDownloaded) {
+            loadDownloadedPages(chapter)
+        } else {
+            loadOnlinePages(chapter)
+        }
+    }
+
+    private fun loadDownloadedPages(chapter: ReaderChapter) {
+        val chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter)
+        subscriptions += Observable.from(chapter.pages!!)
+                .flatMap { downloadManager.getDownloadedImage(it, chapterDir) }
+                .subscribeOn(Schedulers.io())
+                .subscribe()
+    }
+
+    private fun loadOnlinePages(chapter: ReaderChapter) {
+        chapter.pages?.let { pages ->
+            val startPage = chapter.requestedPage
+            val pagesToLoad = if (startPage == 0)
+                pages
+            else
+                pages.drop(startPage)
+
+            pagesToLoad.forEach { queue.offer(PriorityPage(it, 0)) }
+        }
+    }
+
+    fun loadPriorizedPage(page: Page) {
+        queue.offer(PriorityPage(page, 1))
+    }
+
+    fun retryPage(page: Page) {
+        queue.offer(PriorityPage(page, 2))
+    }
+
+    private data class PriorityPage(val page: Page, val priority: Int): Comparable<PriorityPage> {
+
+        companion object {
+            private val idGenerator = AtomicInteger()
+        }
+
+        private val identifier = idGenerator.incrementAndGet()
+
+        override fun compareTo(other: PriorityPage): Int {
+            val p = other.priority.compareTo(priority)
+            return if (p != 0) p else identifier.compareTo(other.identifier)
+        }
+
+    }
+
+}

+ 12 - 19
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<ReaderPresenter>() {
         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<ReaderPresenter>() {
         // 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<ReaderPresenter>() {
         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<ReaderPresenter>() {
             chapter.name)
     }
 
-    fun onAppendChapter(chapter: Chapter) {
+    fun onAppendChapter(chapter: ReaderChapter) {
         viewer?.onPageListAppendReady(chapter)
     }
 
@@ -324,7 +317,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
         viewer?.let {
             val activePage = it.getActivePage()
             if (activePage != null) {
-                val requestedPage = activePage.chapter.pages[pageIndex]
+                val requestedPage = activePage.chapter.pages!![pageIndex]
                 it.setActivePage(requestedPage)
             }
 

+ 13 - 0
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<Page>? = null
+
+    var isDownloaded: Boolean = false
+
+    var requestedPage: Int = 0
+}

+ 313 - 265
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<ReaderActivity>() {
 
-    @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
-    private var mangaSyncList: List<MangaSync>? = null
+    /**
+     * Next chapter of the active.
+     */
+    private var nextChapter: ReaderChapter? = null
+
+    /**
+     * Source of the manga.
+     */
+    private val source by lazy { sourceManager.get(manga.source)!! }
 
-    private val retryPageSubject by lazy { PublishSubject.create<Page>() }
-    private val pageInitializerSubject by lazy { PublishSubject.create<Chapter>() }
+    /**
+     * Chapter list for the active manga. It's retrieved lazily and should be accessed for the first
+     * time in a background thread to avoid blocking the UI.
+     */
+    private val chapterList by lazy {
+        val dbChapters = db.getChapters(manga).executeAsBlocking().map { it.toModel() }
+
+        val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
+            Manga.SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
+            Manga.SORTING_NUMBER -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
+            else -> throw NotImplementedError("Unknown sorting method")
+        }
+
+        dbChapters.sortedWith(Comparator<Chapter> { c1, c2 -> sortFunction(c1, c2) })
+    }
 
-    val isSeamlessMode by lazy { prefs.seamlessMode() }
+    /**
+     * List of manga services linked to the active manga, or null if auto syncing is not enabled.
+     */
+    private var mangaSyncList: List<MangaSync>? = null
 
+    /**
+     * 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<ReaderActivity>() {
         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)!!
-
-        initializeSubjects()
-
-        restartableLatestCache(PREPARE_READER,
-                { Observable.just(manga) },
-                { view, manga -> view.onMangaOpen(manga) })
-
-        startableLatestCache(GET_ADJACENT_CHAPTERS,
-                { getAdjacentChaptersObservable() },
-                { view, pair -> view.onAdjacentChapters(pair.first, pair.second) })
+        // Send the active manga to the view to initialize the reader.
+        Observable.just(manga)
+                .subscribeLatestCache({ view, manga -> view.onMangaOpen(manga) })
 
-        startable(PRELOAD_NEXT_CHAPTER,
-                { getPreloadNextChapterObservable() },
-                { },
-                { error -> Timber.e("Error preloading chapter") })
-
-
-        restartable(GET_MANGA_SYNC,
-                { getMangaSyncObservable().subscribe() })
+        // Retrieve the sync list if auto syncing is enabled.
+        if (prefs.autoUpdateMangaSync()) {
+            add(db.getMangasSync(manga).asRxSingle()
+                    .subscribe({ mangaSyncList = it }))
+        }
 
-        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<Page>
-                    if (ch.isDownloaded) {
-                        val chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, ch)
-                        observable = Observable.from(ch.pages)
-                                .flatMap { downloadManager.getDownloadedImage(it, chapterDir) }
-                    } else {
-                        observable = source.let { source ->
-                            if (source is OnlineSource) {
-                                source.fetchAllImageUrlsFromPageList(ch.pages)
-                                        .flatMap({ source.getCachedImage(it) }, 2)
-                                        .doOnCompleted { source.savePageList(ch, ch.pages) }
-                            } else {
-                                Observable.from(ch.pages)
-                                        .flatMap { source.fetchImage(it) }
-                            }
-                        }
-                    }
-                    observable.doOnCompleted {
-                        if (!isSeamlessMode && chapter === ch) {
-                            preloadNextChapter()
-                        }
-                    }
-                }.subscribe())
-
-        // Listen por retry events
-        add(retryPageSubject.observeOn(Schedulers.io())
-                .flatMap { source.fetchImage(it) }
-                .subscribe())
+    override fun onDestroy() {
+        loader.cleanup()
+        super.onDestroy()
     }
 
-    // Returns the page list of a chapter
-    private fun getPageListObservable(chapter: Chapter): Observable<Chapter> {
-        val observable: Observable<List<Page>> = 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<Pair<Chapter, Chapter>> {
-        val strategy = getAdjacentChaptersStrategy()
-        return Observable.zip(strategy.first, strategy.second) { prev, next -> Pair(prev, next) }
-                .doOnNext { pair ->
-                    previousChapter = pair.first
-                    nextChapter = pair.second
-                }
+    /**
+     * Returns an observable that loads the given chapter, discarding any previous work.
+     *
+     * @param chapter the now active chapter.
+     */
+    private fun loadChapterObservable(chapter: ReaderChapter): Observable<ReaderChapter> {
+        loader.restart()
+        return loader.loadChapter(chapter)
+                .subscribeOn(Schedulers.io())
                 .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")
-    }
+    /**
+     * 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) }
 
-    // Preload the first pages of the next chapter. Only for non seamless mode
-    private fun getPreloadNextChapterObservable(): Observable<Page> {
-        val nextChapter = nextChapter ?: return Observable.error(Exception("No next chapter"))
-        return source.fetchPageList(nextChapter)
-                .flatMap { pages ->
-                    nextChapter.pages = pages
-                    val pagesToPreload = Math.min(pages.size, 5)
-                    Observable.from(pages).take(pagesToPreload)
+        adjacentChaptersSubscription = Observable
+                .fromCallable { getAdjacentChaptersStrategy(chapter) }
+                .doOnNext { pair ->
+                    prevChapter = pair.first
+                    nextChapter = pair.second
                 }
-                // Preload up to 5 images
-                .concatMap { source.fetchImage(it) }
                 .subscribeOn(Schedulers.io())
                 .observeOn(AndroidSchedulers.mainThread())
-                .doOnCompleted { stopPreloadingNextChapter() }
+                .subscribeLatestCache({ view, pair ->
+                    view.onAdjacentChapters(pair.first, pair.second)
+                })
     }
 
-    private fun getMangaSyncObservable(): Observable<List<MangaSync>> {
-        return db.getMangasSync(manga).asRxObservable()
-                .take(1)
-                .doOnNext { mangaSyncList = it }
-    }
+    /**
+     * 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
+                }
+            }
 
-    // Loads the given chapter
-    private fun loadChapter(chapter: Chapter, requestedPage: Int = 0) {
-        if (isSeamlessMode) {
-            if (appenderSubscription != null)
-                remove(appenderSubscription)
-        } else {
-            stopPreloadingNextChapter()
+            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
-
-        if (appenderSubscription != null)
-            remove(appenderSubscription)
-
-        nextChapter?.let {
-            if (appenderSubscription != null)
-                remove(appenderSubscription)
-
-            it.status = if (isChapterDownloaded(it)) Download.DOWNLOADED else Download.NOT_DOWNLOADED
+        appenderSubscription?.let { remove(it) }
 
-            appenderSubscription = getPageListObservable(it).subscribeOn(Schedulers.io())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .compose(deliverLatestCache<Chapter>())
-                    .subscribe(split({ view, chapter ->
-                        view.onAppendChapter(chapter)
-                    }, { view, error ->
-                        view.onChapterAppendError()
-                    }))
+        val nextChapter = nextChapter ?: return
 
-            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]
+        Observable
+                .fromCallable {
+                    if (!chapter.isDownloaded) {
+                        source.let { if (it is OnlineSource) it.savePageList(chapter, pages) }
+                    }
 
-        // 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) }
-        }
+                    // 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)
+                            }
+                        }
+                    }
 
-        // Save current progress of the chapter. Mark as read if the chapter is finished
-        if (activePage.isLastPage) {
-            chapter.read = true
+                    db.updateChapterProgress(chapter).executeAsBlocking()
 
-            // Check if remove after read is selected by user
-            if (prefs.removeAfterRead()) {
-                if (prefs.removeAfterReadPrevious() ) {
-                    if (previousChapter != null) {
-                        deleteChapter(previousChapter!!, manga)
-                    }
-                } else {
-                    deleteChapter(chapter, manga)
+                    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<ReaderActivity>() {
         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<ReaderActivity>() {
         }
     }
 
+    /**
+     * 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<ReaderActivity>() {
         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()
     }
+
 }

+ 22 - 23
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<Chapter>()
+    private val chapters = ArrayList<ReaderChapter>()
 
     /**
      * 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<Chapter>()
+            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...).

+ 3 - 3
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

+ 5 - 5
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)
         }
     }
 

+ 22 - 0
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
+
+}

+ 6 - 6
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<RecyclerView.ViewHolder, Any>() {
+class RecentChaptersAdapter(val fragment: RecentChaptersFragment)
+: FlexibleAdapter<RecyclerView.ViewHolder, Any>() {
     /**
      * 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()
     }

+ 82 - 83
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<RecentChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
+class RecentChaptersFragment
+: BaseRxFragment<RecentChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
+
     companion object {
         /**
          * Create new RecentChaptersFragment.
@@ -40,54 +40,6 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
         }
     }
 
-    override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
-        return false
-    }
-
-    /**
-     * Called when ActionMode item clicked
-     * @param mode the ActionMode object
-     * @param item item from ActionMode.
-     */
-    override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
-        when (item.itemId) {
-            R.id.action_mark_as_read -> markAsRead(getSelectedChapters())
-            R.id.action_mark_as_unread -> markAsUnread(getSelectedChapters())
-            R.id.action_download -> downloadChapters(getSelectedChapters())
-            R.id.action_delete -> {
-                MaterialDialog.Builder(activity)
-                        .content(R.string.confirm_delete_chapters)
-                        .positiveText(android.R.string.yes)
-                        .negativeText(android.R.string.no)
-                        .onPositive { dialog, action -> deleteChapters(getSelectedChapters()) }
-                        .show()
-            }
-            else -> return false
-        }
-        return true
-    }
-
-    /**
-     * Called when ActionMode created.
-     * @param mode the ActionMode object
-     * @param menu menu object of ActionMode
-     */
-    override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
-        mode.menuInflater.inflate(R.menu.chapter_recent_selection, menu)
-        adapter.mode = FlexibleAdapter.MODE_MULTI
-        return true
-    }
-
-    /**
-     * Called when ActionMode destroyed
-     * @param mode the ActionMode object
-     */
-    override fun onDestroyActionMode(mode: ActionMode?) {
-        adapter.mode = FlexibleAdapter.MODE_SINGLE
-        adapter.clearSelection()
-        actionMode = null
-    }
-
     /**
      * Action mode for multiple selection.
      */
@@ -105,7 +57,7 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
      * @param container view group
      * @param savedState status of saved state
      */
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View {
         // Inflate view
         return inflater.inflate(R.layout.fragment_recent_chapters, container, false)
     }
@@ -115,7 +67,7 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
      * @param view created view
      * @param savedInstanceState status of saved sate
      */
-    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
+    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)))
@@ -129,10 +81,10 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
 
     /**
      * Returns selected chapters
-     * @return list of [MangaChapter]s
+     * @return list of selected chapters
      */
-    fun getSelectedChapters(): List<MangaChapter> {
-        return adapter.selectedItems.map { adapter.getItem(it) as? MangaChapter }.filterNotNull()
+    fun getSelectedChapters(): List<RecentChapter> {
+        return adapter.selectedItems.map { adapter.getItem(it) as? RecentChapter }.filterNotNull()
     }
 
     /**
@@ -142,7 +94,7 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
     override fun onListItemClick(position: Int): Boolean {
         // Get item from position
         val item = adapter.getItem(position)
-        if (item is MangaChapter) {
+        if (item is RecentChapter) {
             if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) {
                 toggleSelection(position)
                 return true
@@ -191,27 +143,27 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
 
     /**
      * Open chapter in reader
-     * @param mangaChapter selected [MangaChapter]
+     * @param chapter selected chapter
      */
-    private fun openChapter(mangaChapter: MangaChapter) {
-        val intent = ReaderActivity.newIntent(activity, mangaChapter.manga, mangaChapter.chapter)
+    private fun openChapter(chapter: RecentChapter) {
+        val intent = ReaderActivity.newIntent(activity, chapter.manga, chapter)
         startActivity(intent)
     }
 
     /**
      * Download selected items
-     * @param mangaChapters list of selected [MangaChapter]s
+     * @param chapters list of selected [RecentChapter]s
      */
-    fun downloadChapters(mangaChapters: List<MangaChapter>) {
+    fun downloadChapters(chapters: List<RecentChapter>) {
         destroyActionModeIfNeeded()
-        presenter.downloadChapters(mangaChapters)
+        presenter.downloadChapters(chapters)
     }
 
     /**
      * Populate adapter with chapters
      * @param chapters list of [Any]
      */
-    fun onNextMangaChapters(chapters: List<Any>) {
+    fun onNextRecentChapters(chapters: List<Any>) {
         (activity as MainActivity).updateEmptyView(chapters.isEmpty(),
                 R.string.information_no_recent, R.drawable.ic_update_black_128dp)
 
@@ -224,8 +176,7 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
      * @param download [Download] object containing download progress.
      */
     fun onChapterStatusChange(download: Download) {
-        getHolder(download)?.onStatusChange(download.status)
-
+        getHolder(download)?.notifyStatus(download.status)
     }
 
     /**
@@ -233,28 +184,28 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
      * @param download [Download] object containing download progress.
      */
     private fun getHolder(download: Download): RecentChaptersHolder? {
-        return recycler.findViewHolderForItemId(download.chapter.id) as? RecentChaptersHolder
+        return recycler.findViewHolderForItemId(download.chapter.id!!) as? RecentChaptersHolder
     }
 
     /**
      * Mark chapter as read
-     * @param mangaChapters list of [MangaChapter] objects
+     * @param chapters list of chapters
      */
-    fun markAsRead(mangaChapters: List<MangaChapter>) {
-        presenter.markChapterRead(mangaChapters, true)
+    fun markAsRead(chapters: List<RecentChapter>) {
+        presenter.markChapterRead(chapters, true)
         if (presenter.preferences.removeAfterMarkedAsRead()) {
-            deleteChapters(mangaChapters)
+            deleteChapters(chapters)
         }
     }
 
     /**
      * Delete selected chapters
-     * @param mangaChapters list of [MangaChapter] objects
+     * @param chapters list of [RecentChapter] objects
      */
-    fun deleteChapters(mangaChapters: List<MangaChapter>) {
+    fun deleteChapters(chapters: List<RecentChapter>) {
         destroyActionModeIfNeeded()
         DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
-        presenter.deleteChapters(mangaChapters)
+        presenter.deleteChapters(chapters)
     }
 
     /**
@@ -266,27 +217,27 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
 
     /**
      * Mark chapter as unread
-     * @param mangaChapters list of selected [MangaChapter]
+     * @param chapters list of selected [RecentChapter]
      */
-    fun markAsUnread(mangaChapters: List<MangaChapter>) {
-        presenter.markChapterRead(mangaChapters, false)
+    fun markAsUnread(chapters: List<RecentChapter>) {
+        presenter.markChapterRead(chapters, false)
     }
 
     /**
      * Start downloading chapter
-     * @param item selected chapter with manga
+     * @param chapter selected chapter with manga
      */
-    fun downloadChapter(item: MangaChapter) {
-        presenter.downloadChapter(item)
+    fun downloadChapter(chapter: RecentChapter) {
+        presenter.downloadChapter(chapter)
     }
 
     /**
      * Start deleting chapter
-     * @param item selected chapter with manga
+     * @param chapter selected chapter with manga
      */
-    fun deleteChapter(item: MangaChapter) {
+    fun deleteChapter(chapter: RecentChapter) {
         DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
-        presenter.deleteChapter(item)
+        presenter.deleteChapters(listOf(chapter))
     }
 
     /**
@@ -313,4 +264,52 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
         (childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
     }
 
+    override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
+        return false
+    }
+
+    /**
+     * Called when ActionMode item clicked
+     * @param mode the ActionMode object
+     * @param item item from ActionMode.
+     */
+    override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
+        when (item.itemId) {
+            R.id.action_mark_as_read -> markAsRead(getSelectedChapters())
+            R.id.action_mark_as_unread -> markAsUnread(getSelectedChapters())
+            R.id.action_download -> downloadChapters(getSelectedChapters())
+            R.id.action_delete -> {
+                MaterialDialog.Builder(activity)
+                        .content(R.string.confirm_delete_chapters)
+                        .positiveText(android.R.string.yes)
+                        .negativeText(android.R.string.no)
+                        .onPositive { dialog, action -> deleteChapters(getSelectedChapters()) }
+                        .show()
+            }
+            else -> return false
+        }
+        return true
+    }
+
+    /**
+     * Called when ActionMode created.
+     * @param mode the ActionMode object
+     * @param menu menu object of ActionMode
+     */
+    override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
+        mode.menuInflater.inflate(R.menu.chapter_recent_selection, menu)
+        adapter.mode = FlexibleAdapter.MODE_MULTI
+        return true
+    }
+
+    /**
+     * Called when ActionMode destroyed
+     * @param mode the ActionMode object
+     */
+    override fun onDestroyActionMode(mode: ActionMode?) {
+        adapter.mode = FlexibleAdapter.MODE_SINGLE
+        adapter.clearSelection()
+        actionMode = null
+    }
+
 }

+ 49 - 50
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 (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 (!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
-            }
+        // 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 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
+        }
 
-            // 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

+ 94 - 178
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<RecentChaptersFragment>() {
     /**
      * 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<MangaChapter>? = null
+    private var chapters: List<RecentChapter>? = null
 
     /**
      * The id of the restartable.
@@ -60,171 +58,114 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
         // 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)
+            start(CHAPTER_STATUS_CHANGES)
         }
     }
 
+    /**
+     * Get observable containing recent chapters and date
+     * @return observable containing recent chapters and date
+     */
+    fun getRecentChaptersObservable(): Observable<ArrayList<Any>> {
+        // Set date for recent chapters
+        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.date_fetch) },
+                                    { it },
+                                    { TreeMap { d1, d2 -> d2.compareTo(d1) } })
+                }
+                // Add every day and all its chapters to a single list.
+                .map { recentItems ->
+                    ArrayList<Any>().apply {
+                        for ((key, value) in recentItems) {
+                            add(key)
+                            addAll(value)
+                        }
+                    }
+                }
+                .observeOn(AndroidSchedulers.mainThread())
+    }
 
     /**
      * Returns observable containing chapter status.
      * @return download object containing download progress.
      */
-    private fun getChapterStatusObs(): Observable<Download> {
+    private fun getChapterStatusObservable(): Observable<Download> {
         return downloadManager.queue.getStatusObservable()
                 .observeOn(AndroidSchedulers.mainThread())
-                .filter { download: Download ->
-                    if (chapterIdEquals(download.chapter.id))
-                        true
-                    else
-                        false
-                }
-                .doOnNext { download1: Download -> updateChapterStatus(download1) }
-
+                .doOnNext { download -> onDownloadStatusChange(download) }
     }
 
     /**
-     * Function to check if chapter is in recent list
-     * @param chaptersId id of chapter
-     * @return exist in recent list
+     * Converts a chapter from the database to an extended model, allowing to store new fields.
      */
-    private fun chapterIdEquals(chaptersId: Long): Boolean {
-        mangaChapters!!.forEach { mangaChapter ->
-            if (chaptersId == mangaChapter.chapter.id) {
-                return true
-            }
-        }
-        return false
-    }
+    private fun MangaChapter.toModel(): RecentChapter {
+        // Create the model object.
+        val model = RecentChapter(this)
 
-    /**
-     * Returns a list only containing MangaChapter objects.
-     * @param input the list that will be converted.
-     * @return list containing MangaChapters objects.
-     */
-    private fun convertToMangaChaptersList(input: List<Any>): List<MangaChapter> {
-        // Create temp list
-        val tempMangaChapterList = ArrayList<MangaChapter>()
+        // Find an active download for this chapter.
+        val download = downloadManager.queue.find { it.chapter.id == chapter.id }
 
-        // Only add MangaChapter objects
-        //noinspection Convert2streamapi
-        input.forEach { `object` ->
-            if (`object` is MangaChapter) {
-                tempMangaChapterList.add(`object`)
-            }
-        }
+        // 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)!!
 
-        // Return temp list
-        return tempMangaChapterList
+            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 updateChapterStatus(download: Download) {
-        // Loop through list
-        mangaChapters?.let {
-            for (item in it) {
-                if (download.chapter.id == item.chapter.id) {
-                    // Update status.
-                    item.chapter.status = download.status
-                    break
-                }
-            }
-        }
-    }
-
-    /**
-     * Update status of chapters
-     * @param mangaChapters list containing recent chapters
-     */
-    private fun updateChapterStatus(mangaChapters: List<MangaChapter>) {
-        // Set global list of chapters.
-        this.mangaChapters = mangaChapters
-
-        // Update status.
-        //noinspection Convert2streamapi
-        for (mangaChapter in mangaChapters)
-            setChapterStatus(mangaChapter)
-
-        // Start onChapterStatusChange restartable.
-        start(CHAPTER_STATUS_CHANGES)
-    }
-
-    /**
-     * 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
+    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 source of chapter
-        val source = sourceManager.get(mangaChapter.manga.source)!!
-
-        // Check if chapter is downloaded
-        if (downloadManager.isChapterDownloaded(source, mangaChapter.manga, mangaChapter.chapter)) {
-            mangaChapter.chapter.status = Download.DOWNLOADED
-        } else {
-            mangaChapter.chapter.status = Download.NOT_DOWNLOADED
-        }
-    }
-
-    /**
-     * Get observable containing recent chapters and date
-     * @return observable containing recent chapters and date
-     */
-    fun getRecentChaptersObservable(): Observable<ArrayList<Any>>? {
-        // Set date for recent chapters
-        val cal = Calendar.getInstance()
-        cal.time = Date()
-        cal.add(Calendar.MONTH, -1)
-
-        return db.getRecentChapters(cal.time).asRxObservable()
-                // Group chapters by the date they were fetched on a ordered map.
-                .flatMap { recentItems ->
-                    Observable.from(recentItems)
-                            .toMultimap(
-                                    { getMapKey(it.chapter.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<Any>()
-                    recentItems.entries.forEach { recent ->
-                        items.add(recent.key)
-                        items.addAll(recent.value)
-                    }
-                    items
-                }
-                .observeOn(AndroidSchedulers.mainThread())
     }
 
     /**
@@ -244,18 +185,17 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
 
     /**
      * 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<MangaChapter>, read: Boolean) {
-        Observable.from(mangaChapters)
-                .doOnNext { mangaChapter ->
-                    mangaChapter.chapter.read = read
+    fun markChapterRead(chapters: List<RecentChapter>, 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<RecentChaptersFragment>() {
 
     /**
      * Delete selected chapters
-     * @param chapters list of MangaChapters
+     * @param chapters list of chapters
      */
-    fun deleteChapters(chapters: List<MangaChapter>) {
+    fun deleteChapters(chapters: List<RecentChapter>) {
         val wasRunning = downloadManager.isRunning
         if (wasRunning) {
             DownloadService.stop(context)
@@ -288,11 +228,11 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
 
     /**
      * Download selected chapters
-     * @param mangaChapters [MangaChapter] that is selected
+     * @param chapters list of recent chapters seleted.
      */
-    fun downloadChapters(mangaChapters: List<MangaChapter>) {
+    fun downloadChapters(chapters: List<RecentChapter>) {
         DownloadService.start(context)
-        Observable.from(mangaChapters)
+        Observable.from(chapters)
                 .doOnNext { downloadChapter(it) }
                 .subscribeOn(AndroidSchedulers.mainThread())
                 .subscribe()
@@ -300,47 +240,23 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
 
     /**
      * 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
     }
 
-
 }

+ 3 - 4
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)
     }
 }

+ 3 - 3
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)
                         }

+ 2 - 2
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<RecentlyReadFragment>() {
     /**
      * Used to connect to database
      */
-    @Inject lateinit var db: DatabaseHelper
+    val db: DatabaseHelper by injectLazy()
 
     override fun onCreate(savedState: Bundle?) {
         super.onCreate(savedState)

+ 7 - 9
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)
 

+ 22 - 0
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<out Throwable>, Observable<*>> {
+
+    private var retryCount = 0
+
+    override fun call(attempts: Observable<out Throwable>) = attempts.flatMap { error ->
+        val count = ++retryCount
+        if (count <= maxRetries) {
+            Observable.timer(retryStrategy(count).toLong(), MILLISECONDS)
+        } else {
+            Observable.error(error as Throwable)
+        }
+    }
+}

+ 0 - 1
app/src/main/res/values/keys.xml

@@ -30,7 +30,6 @@
     <string name="pref_custom_brightness_value_key">pref_custom_brightness_value_key</string>
     <string name="pref_reader_theme_key">pref_reader_theme_key</string>
     <string name="pref_image_decoder_key">pref_image_decoder_key</string>
-    <string name="pref_seamless_mode_key">pref_seamless_mode_key</string>
     <string name="pref_read_with_volume_keys_key">reader_volume_keys</string>
     <string name="pref_read_with_tapping_key">reader_tap</string>
     <string name="pref_reencode_key">reencode_image</string>

部分文件因文件數量過多而無法顯示