mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Fix dependency injection and use custom models extending DB ones
This commit is contained in:
		| @@ -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>() | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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() | ||||
|  | ||||
|   | ||||
| @@ -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()) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -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() | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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() | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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) { | ||||
|  | ||||
|   | ||||
| @@ -26,8 +26,7 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) { | ||||
|     override fun popularMangaParse(response: Response, page: MangasPage) { | ||||
|         val document = Jsoup.parse(response.body().string()) | ||||
|         for (element in document.select(popularMangaSelector())) { | ||||
|             Manga().apply { | ||||
|                 source = this@ParsedOnlineSource.id | ||||
|             Manga.create(id).apply { | ||||
|                 popularMangaFromElement(element, this) | ||||
|                 page.mangas.add(this) | ||||
|             } | ||||
| @@ -70,8 +69,7 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) { | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String) { | ||||
|         val document = Jsoup.parse(response.body().string()) | ||||
|         for (element in document.select(searchMangaSelector())) { | ||||
|             Manga().apply { | ||||
|                 source = this@ParsedOnlineSource.id | ||||
|             Manga.create(id).apply { | ||||
|                 searchMangaFromElement(element, this) | ||||
|                 page.mangas.add(this) | ||||
|             } | ||||
|   | ||||
| @@ -54,10 +54,9 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con | ||||
|     override fun popularMangaParse(response: Response, page: MangasPage) { | ||||
|         val document = Jsoup.parse(response.body().string()) | ||||
|         for (element in document.select(map.popular.manga_css)) { | ||||
|             Manga().apply { | ||||
|                 source = this@YamlOnlineSource.id | ||||
|             Manga.create(id).apply { | ||||
|                 title = element.text() | ||||
|                 setUrl(element.attr("href")) | ||||
|                 setUrlWithoutDomain(element.attr("href")) | ||||
|                 page.mangas.add(this) | ||||
|             } | ||||
|         } | ||||
| @@ -84,10 +83,9 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String) { | ||||
|         val document = Jsoup.parse(response.body().string()) | ||||
|         for (element in document.select(map.search.manga_css)) { | ||||
|             Manga().apply { | ||||
|                 source = this@YamlOnlineSource.id | ||||
|             Manga.create(id).apply { | ||||
|                 title = element.text() | ||||
|                 setUrl(element.attr("href")) | ||||
|                 setUrlWithoutDomain(element.attr("href")) | ||||
|                 page.mangas.add(this) | ||||
|             } | ||||
|         } | ||||
| @@ -123,7 +121,7 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con | ||||
|                 val chapter = Chapter.create() | ||||
|                 element.select(title).first().let { | ||||
|                     chapter.name = it.text() | ||||
|                     chapter.setUrl(it.attr("href")) | ||||
|                     chapter.setUrlWithoutDomain(it.attr("href")) | ||||
|                 } | ||||
|                 val dateElement = element.select(date?.select).first() | ||||
|                 chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0 | ||||
|   | ||||
| @@ -62,8 +62,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex | ||||
|     override fun popularMangaParse(response: Response, page: MangasPage) { | ||||
|         val document = Jsoup.parse(response.body().string()) | ||||
|         for (element in document.select(popularMangaSelector())) { | ||||
|             Manga().apply { | ||||
|                 source = this@Batoto.id | ||||
|             Manga.create(id).apply { | ||||
|                 popularMangaFromElement(element, this) | ||||
|                 page.mangas.add(this) | ||||
|             } | ||||
| @@ -78,7 +77,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex | ||||
|  | ||||
|     override fun popularMangaFromElement(element: Element, manga: Manga) { | ||||
|         element.select("a[href^=http://bato.to]").first().let { | ||||
|             manga.setUrl(it.attr("href")) | ||||
|             manga.setUrlWithoutDomain(it.attr("href")) | ||||
|             manga.title = it.text().trim() | ||||
|         } | ||||
|     } | ||||
| @@ -90,8 +89,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex | ||||
|     override fun searchMangaParse(response: Response, page: MangasPage, query: String) { | ||||
|         val document = Jsoup.parse(response.body().string()) | ||||
|         for (element in document.select(searchMangaSelector())) { | ||||
|             Manga().apply { | ||||
|                 source = this@Batoto.id | ||||
|             Manga.create(id).apply { | ||||
|                 searchMangaFromElement(element, this) | ||||
|                 page.mangas.add(this) | ||||
|             } | ||||
| @@ -156,7 +154,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex | ||||
|     override fun chapterFromElement(element: Element, chapter: Chapter) { | ||||
|         val urlElement = element.select("a[href^=http://bato.to/reader").first() | ||||
|  | ||||
|         chapter.setUrl(urlElement.attr("href")) | ||||
|         chapter.setUrlWithoutDomain(urlElement.attr("href")) | ||||
|         chapter.name = urlElement.text() | ||||
|         chapter.date_upload = element.select("td").getOrNull(4)?.let { | ||||
|             parseDateFromElement(it) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|     } | ||||
|   | ||||
| @@ -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 | ||||
|     } | ||||
|   | ||||
| @@ -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 | ||||
|     } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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 | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -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 | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -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!! | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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 | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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!! | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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 | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -0,0 +1,19 @@ | ||||
| package eu.kanade.tachiyomi.ui.manga.chapter | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.download.model.Download | ||||
|  | ||||
| class ChapterModel(c: Chapter) : Chapter by c { | ||||
|  | ||||
|     private var _status: Int = 0 | ||||
|  | ||||
|     var status: Int | ||||
|         get() = download?.status ?: _status | ||||
|         set(value) { _status = value } | ||||
|  | ||||
|     var download: Download? = null | ||||
|  | ||||
|     val isDownloaded: Boolean | ||||
|         get() = status == Download.DOWNLOADED | ||||
|  | ||||
| } | ||||
| @@ -3,10 +3,9 @@ package eu.kanade.tachiyomi.ui.manga.chapter | ||||
| import android.view.ViewGroup | ||||
| import 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() | ||||
|     } | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -1,10 +1,8 @@ | ||||
| package eu.kanade.tachiyomi.ui.manga.chapter | ||||
|  | ||||
| import android.content.Context | ||||
| import android.view.View | ||||
| import android.widget.PopupMenu | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.download.model.Download | ||||
| import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder | ||||
| @@ -26,7 +24,7 @@ class ChaptersHolder( | ||||
|     private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' }) | ||||
|     private val df = DateFormat.getDateInstance(DateFormat.SHORT) | ||||
|  | ||||
|     private var item: Chapter? = null | ||||
|     private var item: ChapterModel? = null | ||||
|  | ||||
|     init { | ||||
|         // We need to post a Runnable to show the popup to make sure that the PopupMenu is | ||||
| @@ -35,19 +33,16 @@ class ChaptersHolder( | ||||
|         view.chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } } | ||||
|     } | ||||
|  | ||||
|     fun onSetValues(chapter: Chapter, manga: Manga?) = with(view) { | ||||
|     fun onSetValues(chapter: ChapterModel, manga: Manga?) = with(view) { | ||||
|         item = chapter | ||||
|  | ||||
|         val name: String | ||||
|         when (manga?.displayMode) { | ||||
|         chapter_title.text = when (manga?.displayMode) { | ||||
|             Manga.DISPLAY_NUMBER -> { | ||||
|                 val formattedNumber = decimalFormat.format(chapter.chapter_number.toDouble()) | ||||
|                 name = context.getString(R.string.display_mode_chapter, formattedNumber) | ||||
|                 context.getString(R.string.display_mode_chapter, formattedNumber) | ||||
|             } | ||||
|             else -> name = chapter.name | ||||
|             else -> chapter.name | ||||
|         } | ||||
|  | ||||
|         chapter_title.text = name | ||||
|         chapter_title.setTextColor(if (chapter.read) readColor else unreadColor) | ||||
|  | ||||
|         if (chapter.date_upload > 0) { | ||||
| @@ -57,31 +52,26 @@ class ChaptersHolder( | ||||
|             chapter_date.text = "" | ||||
|         } | ||||
|  | ||||
|         if (!chapter.read && chapter.last_page_read > 0) { | ||||
|             chapter_pages.text = context.getString(R.string.chapter_progress, chapter.last_page_read + 1) | ||||
|         chapter_pages.text = if (!chapter.read && chapter.last_page_read > 0) { | ||||
|             context.getString(R.string.chapter_progress, chapter.last_page_read + 1) | ||||
|         } else { | ||||
|             chapter_pages.text = "" | ||||
|             "" | ||||
|         } | ||||
|  | ||||
|         notifyStatus(chapter.status) | ||||
|     } | ||||
|  | ||||
|     fun notifyStatus(status: Int) = with(view) { | ||||
|     fun notifyStatus(status: Int) = with(view.download_text) { | ||||
|         when (status) { | ||||
|             Download.QUEUE -> download_text.setText(R.string.chapter_queued) | ||||
|             Download.DOWNLOADING -> download_text.setText(R.string.chapter_downloading) | ||||
|             Download.DOWNLOADED -> download_text.setText(R.string.chapter_downloaded) | ||||
|             Download.ERROR -> download_text.setText(R.string.chapter_error) | ||||
|             else -> download_text.text = "" | ||||
|             Download.QUEUE -> setText(R.string.chapter_queued) | ||||
|             Download.DOWNLOADING -> setText(R.string.chapter_downloading) | ||||
|             Download.DOWNLOADED -> setText(R.string.chapter_downloaded) | ||||
|             Download.ERROR -> setText(R.string.chapter_error) | ||||
|             else -> text = "" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun onProgressChange(context: Context, downloaded: Int, total: Int) { | ||||
|         view.download_text.text = context.getString( | ||||
|                 R.string.chapter_downloading_progress, downloaded, total) | ||||
|     } | ||||
|  | ||||
|     private fun showPopupMenu(view: View) = item?.let { item -> | ||||
|     private fun showPopupMenu(view: View) = item?.let { chapter -> | ||||
|         // Create a PopupMenu, giving it the clicked view for an anchor | ||||
|         val popup = PopupMenu(view.context, view) | ||||
|  | ||||
| @@ -89,32 +79,35 @@ class ChaptersHolder( | ||||
|         popup.menuInflater.inflate(R.menu.chapter_single, popup.menu) | ||||
|  | ||||
|         // Hide download and show delete if the chapter is downloaded | ||||
|         if (item.isDownloaded) { | ||||
|         if (chapter.isDownloaded) { | ||||
|             popup.menu.findItem(R.id.action_download).isVisible = false | ||||
|             popup.menu.findItem(R.id.action_delete).isVisible = true | ||||
|         } | ||||
|  | ||||
|         // Hide mark as unread when the chapter is unread | ||||
|         if (!item.read && item.last_page_read == 0) { | ||||
|         if (!chapter.read && chapter.last_page_read == 0) { | ||||
|             popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false | ||||
|         } | ||||
|  | ||||
|         // Hide mark as read when the chapter is read | ||||
|         if (item.read) { | ||||
|         if (chapter.read) { | ||||
|             popup.menu.findItem(R.id.action_mark_as_read).isVisible = false | ||||
|         } | ||||
|  | ||||
|         // Set a listener so we are notified if a menu item is clicked | ||||
|         popup.setOnMenuItemClickListener { menuItem -> | ||||
|             val chapter = listOf(item) | ||||
|             val chapterList = listOf(chapter) | ||||
|  | ||||
|             when (menuItem.itemId) { | ||||
|                 R.id.action_download -> adapter.fragment.downloadChapters(chapter) | ||||
|                 R.id.action_delete -> adapter.fragment.deleteChapters(chapter) | ||||
|                 R.id.action_mark_as_read -> adapter.fragment.markAsRead(chapter) | ||||
|                 R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(chapter) | ||||
|                 R.id.action_mark_previous_as_read -> adapter.fragment.markPreviousAsRead(item) | ||||
|             with(adapter.fragment) { | ||||
|                 when (menuItem.itemId) { | ||||
|                     R.id.action_download -> downloadChapters(chapterList) | ||||
|                     R.id.action_delete -> deleteChapters(chapterList) | ||||
|                     R.id.action_mark_as_read -> markAsRead(chapterList) | ||||
|                     R.id.action_mark_as_unread -> markAsUnread(chapterList) | ||||
|                     R.id.action_mark_previous_as_read -> markPreviousAsRead(chapter) | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             true | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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>> | ||||
|     /** | ||||
|      * Subject of list of chapters to allow updating the view without going to DB. | ||||
|      */ | ||||
|     val chaptersSubject by lazy { PublishSubject.create<List<ChapterModel>>() } | ||||
|  | ||||
|     /** | ||||
|      * Whether the chapter list has been requested to the source. | ||||
|      */ | ||||
|     var hasRequested = false | ||||
|         private set | ||||
|  | ||||
|     var hasRequested: Boolean = false | ||||
|         private set | ||||
|     companion object { | ||||
|         /** | ||||
|          * Id of the restartable which sends a filtered and ordered list of chapters to the view. | ||||
|          */ | ||||
|         private const val GET_CHAPTERS = 1 | ||||
|  | ||||
|     private val DB_CHAPTERS = 1 | ||||
|     private val FETCH_CHAPTERS = 2 | ||||
|     private val CHAPTER_STATUS_CHANGES = 3 | ||||
|         /** | ||||
|          * Id of the restartable which requests an updated list of chapters to the source. | ||||
|          */ | ||||
|         private const val FETCH_CHAPTERS = 2 | ||||
|  | ||||
|         /** | ||||
|          * Id of the restartable which listens for download status changes. | ||||
|          */ | ||||
|         private const val CHAPTER_STATUS_CHANGES = 3 | ||||
|     } | ||||
|  | ||||
|     override fun onCreate(savedState: Bundle?) { | ||||
|         super.onCreate(savedState) | ||||
|  | ||||
|         chaptersSubject = PublishSubject.create() | ||||
|  | ||||
|         startableLatestCache(DB_CHAPTERS, | ||||
|                 { getDbChaptersObs() }, | ||||
|         startableLatestCache(GET_CHAPTERS, | ||||
|                 // On each subject emission, apply filters and sort then update the view. | ||||
|                 { chaptersSubject | ||||
|                         .flatMap { applyChapterFilters(it) } | ||||
|                         .observeOn(AndroidSchedulers.mainThread()) }, | ||||
|                 { view, chapters -> view.onNextChapters(chapters) }) | ||||
|  | ||||
|         startableFirst(FETCH_CHAPTERS, | ||||
|                 { getOnlineChaptersObs() }, | ||||
|                 { getRemoteChaptersObservable() }, | ||||
|                 { view, result -> view.onFetchChaptersDone() }, | ||||
|                 { view, error -> view.onFetchChaptersError(error) }) | ||||
|  | ||||
|         startableLatestCache(CHAPTER_STATUS_CHANGES, | ||||
|                 { getChapterStatusObs() }, | ||||
|                 { getChapterStatusObservable() }, | ||||
|                 { view, download -> view.onChapterStatusChange(download) }, | ||||
|                 { view, error -> Timber.e(error.cause, error.message) }) | ||||
|  | ||||
|         // Find the active manga from the shared data or return. | ||||
|         manga = SharedData.get(MangaEvent::class.java)?.manga ?: return | ||||
|         Observable.just(manga) | ||||
|                 .subscribeLatestCache({ view, manga -> view.onNextManga(manga) }) | ||||
|  | ||||
|         // Find the source for this manga. | ||||
|         source = sourceManager.get(manga.source)!! | ||||
|         start(DB_CHAPTERS) | ||||
|  | ||||
|         // Prepare the publish subject. | ||||
|         start(GET_CHAPTERS) | ||||
|  | ||||
|         // Add the subscription that retrieves the chapters from the database, keeps subscribed to | ||||
|         // changes, and sends the list of chapters to the publish subject. | ||||
|         add(db.getChapters(manga).asRxObservable() | ||||
|                 .map { chapters -> | ||||
|                     // Convert every chapter to a model. | ||||
|                     chapters.map { it.toModel() } | ||||
|                 } | ||||
|                 .doOnNext { chapters -> | ||||
|                     // Store the last emission | ||||
|                     this.chapters = chapters | ||||
|                     SharedData.get(ChapterCountEvent::class.java)?.emit(chapters.size) | ||||
|                     for (chapter in chapters) { | ||||
|                         setChapterStatus(chapter) | ||||
|                     } | ||||
|  | ||||
|                     // Listen for download status changes | ||||
|                     start(CHAPTER_STATUS_CHANGES) | ||||
|  | ||||
|                     // Emit the number of chapters to the info tab. | ||||
|                     SharedData.get(ChapterCountEvent::class.java)?.emit(chapters.size) | ||||
|                 } | ||||
|                 .subscribe { chaptersSubject.onNext(it) }) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Converts a chapter from the database to an extended model, allowing to store new fields. | ||||
|      */ | ||||
|     private fun Chapter.toModel(): ChapterModel { | ||||
|         // Create the model object. | ||||
|         val model = ChapterModel(this) | ||||
|  | ||||
|         // Find an active download for this chapter. | ||||
|         val download = downloadManager.queue.find { it.chapter.id == id } | ||||
|  | ||||
|         if (download != null) { | ||||
|             // If there's an active download, assign it. | ||||
|             model.download = download | ||||
|         } else { | ||||
|             // Otherwise ask the manager if the chapter is downloaded and assign it to the status. | ||||
|             model.status = if (downloadManager.isChapterDownloaded(source, manga, this)) | ||||
|                 Download.DOWNLOADED | ||||
|             else | ||||
|                 Download.NOT_DOWNLOADED | ||||
|         } | ||||
|         return model | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Requests an updated list of chapters from the source. | ||||
|      */ | ||||
|     fun fetchChaptersFromSource() { | ||||
|         hasRequested = true | ||||
|         start(FETCH_CHAPTERS) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Updates the UI after applying the filters. | ||||
|      */ | ||||
|     private fun refreshChapters() { | ||||
|         chaptersSubject.onNext(chapters) | ||||
|     } | ||||
|  | ||||
|     fun getOnlineChaptersObs(): Observable<Pair<Int, Int>> { | ||||
|         return source.fetchChapterList(manga) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .map { syncChaptersWithSource(db, it, manga, source) } | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|     } | ||||
|     /** | ||||
|      * Returns an observable that updates the chapter list with the latest from the source. | ||||
|      */ | ||||
|     fun getRemoteChaptersObservable() = source.fetchChapterList(manga) | ||||
|             .subscribeOn(Schedulers.io()) | ||||
|             .map { syncChaptersWithSource(db, it, manga, source) } | ||||
|             .observeOn(AndroidSchedulers.mainThread()) | ||||
|  | ||||
|     fun getDbChaptersObs(): Observable<List<Chapter>> { | ||||
|         return chaptersSubject | ||||
|                 .flatMap { applyChapterFilters(it) } | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|     } | ||||
|     /** | ||||
|      * Returns an observable that listens to download queue status changes. | ||||
|      */ | ||||
|     fun getChapterStatusObservable() = downloadManager.queue.getStatusObservable() | ||||
|             .observeOn(AndroidSchedulers.mainThread()) | ||||
|             .filter { download -> download.manga.id == manga.id } | ||||
|             .doOnNext { onDownloadStatusChange(it) } | ||||
|  | ||||
|     fun getChapterStatusObs(): Observable<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>> { | ||||
|     /** | ||||
|      * 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() | ||||
|     } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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()) | ||||
|   | ||||
| @@ -0,0 +1,22 @@ | ||||
| package eu.kanade.tachiyomi.ui.recent_updates | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaChapter | ||||
| import eu.kanade.tachiyomi.data.download.model.Download | ||||
|  | ||||
| class RecentChapter(mc: MangaChapter) : Chapter by mc.chapter { | ||||
|  | ||||
|     val manga = mc.manga | ||||
|  | ||||
|     private var _status: Int = 0 | ||||
|  | ||||
|     var status: Int | ||||
|         get() = download?.status ?: _status | ||||
|         set(value) { _status = value } | ||||
|  | ||||
|     var download: Download? = null | ||||
|  | ||||
|     val isDownloaded: Boolean | ||||
|         get() = status == Download.DOWNLOADED | ||||
|  | ||||
| } | ||||
| @@ -5,7 +5,6 @@ import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import 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() | ||||
|     } | ||||
|   | ||||
| @@ -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,6 +40,230 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Action mode for multiple selection. | ||||
|      */ | ||||
|     private var actionMode: ActionMode? = null | ||||
|  | ||||
|     /** | ||||
|      * Adapter containing the recent chapters. | ||||
|      */ | ||||
|     lateinit var adapter: RecentChaptersAdapter | ||||
|         private set | ||||
|  | ||||
|     /** | ||||
|      * Called when view gets created | ||||
|      * @param inflater layout inflater | ||||
|      * @param container view group | ||||
|      * @param savedState status of saved state | ||||
|      */ | ||||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View { | ||||
|         // Inflate view | ||||
|         return inflater.inflate(R.layout.fragment_recent_chapters, container, false) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when view is created | ||||
|      * @param view created view | ||||
|      * @param savedInstanceState status of saved sate | ||||
|      */ | ||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||
|         // Init RecyclerView and adapter | ||||
|         recycler.layoutManager = NpaLinearLayoutManager(activity) | ||||
|         recycler.addItemDecoration(DividerItemDecoration(context.theme.getResourceDrawable(R.attr.divider_drawable))) | ||||
|         recycler.setHasFixedSize(true) | ||||
|         adapter = RecentChaptersAdapter(this) | ||||
|         recycler.adapter = adapter | ||||
|  | ||||
|         // Update toolbar text | ||||
|         setToolbarTitle(R.string.label_recent_updates) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns selected chapters | ||||
|      * @return list of selected chapters | ||||
|      */ | ||||
|     fun getSelectedChapters(): List<RecentChapter> { | ||||
|         return adapter.selectedItems.map { adapter.getItem(it) as? RecentChapter }.filterNotNull() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when item in list is clicked | ||||
|      * @param position position of clicked item | ||||
|      */ | ||||
|     override fun onListItemClick(position: Int): Boolean { | ||||
|         // Get item from position | ||||
|         val item = adapter.getItem(position) | ||||
|         if (item is RecentChapter) { | ||||
|             if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) { | ||||
|                 toggleSelection(position) | ||||
|                 return true | ||||
|             } else { | ||||
|                 openChapter(item) | ||||
|                 return false | ||||
|             } | ||||
|         } | ||||
|         return false | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when item in list is long clicked | ||||
|      * @param position position of clicked item | ||||
|      */ | ||||
|     override fun onListItemLongClick(position: Int) { | ||||
|         if (actionMode == null) | ||||
|             actionMode = activity.startSupportActionMode(this) | ||||
|  | ||||
|         toggleSelection(position) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called to toggle selection | ||||
|      * @param position position of selected item | ||||
|      */ | ||||
|     private fun toggleSelection(position: Int) { | ||||
|         adapter.toggleSelection(position, false) | ||||
|  | ||||
|         val count = adapter.selectedItemCount | ||||
|         if (count == 0) { | ||||
|             actionMode?.finish() | ||||
|         } else { | ||||
|             setContextTitle(count) | ||||
|             actionMode?.invalidate() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set the context title | ||||
|      * @param count count of selected items | ||||
|      */ | ||||
|     private fun setContextTitle(count: Int) { | ||||
|         actionMode?.title = getString(R.string.label_selected, count) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Open chapter in reader | ||||
|      * @param chapter selected chapter | ||||
|      */ | ||||
|     private fun openChapter(chapter: RecentChapter) { | ||||
|         val intent = ReaderActivity.newIntent(activity, chapter.manga, chapter) | ||||
|         startActivity(intent) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Download selected items | ||||
|      * @param chapters list of selected [RecentChapter]s | ||||
|      */ | ||||
|     fun downloadChapters(chapters: List<RecentChapter>) { | ||||
|         destroyActionModeIfNeeded() | ||||
|         presenter.downloadChapters(chapters) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Populate adapter with chapters | ||||
|      * @param chapters list of [Any] | ||||
|      */ | ||||
|     fun onNextRecentChapters(chapters: List<Any>) { | ||||
|         (activity as MainActivity).updateEmptyView(chapters.isEmpty(), | ||||
|                 R.string.information_no_recent, R.drawable.ic_update_black_128dp) | ||||
|  | ||||
|         destroyActionModeIfNeeded() | ||||
|         adapter.setItems(chapters) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Update download status of chapter | ||||
|      * @param download [Download] object containing download progress. | ||||
|      */ | ||||
|     fun onChapterStatusChange(download: Download) { | ||||
|         getHolder(download)?.notifyStatus(download.status) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns holder belonging to chapter | ||||
|      * @param download [Download] object containing download progress. | ||||
|      */ | ||||
|     private fun getHolder(download: Download): RecentChaptersHolder? { | ||||
|         return recycler.findViewHolderForItemId(download.chapter.id!!) as? RecentChaptersHolder | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Mark chapter as read | ||||
|      * @param chapters list of chapters | ||||
|      */ | ||||
|     fun markAsRead(chapters: List<RecentChapter>) { | ||||
|         presenter.markChapterRead(chapters, true) | ||||
|         if (presenter.preferences.removeAfterMarkedAsRead()) { | ||||
|             deleteChapters(chapters) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Delete selected chapters | ||||
|      * @param chapters list of [RecentChapter] objects | ||||
|      */ | ||||
|     fun deleteChapters(chapters: List<RecentChapter>) { | ||||
|         destroyActionModeIfNeeded() | ||||
|         DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG) | ||||
|         presenter.deleteChapters(chapters) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Destory [ActionMode] if it's shown | ||||
|      */ | ||||
|     fun destroyActionModeIfNeeded() { | ||||
|         actionMode?.finish() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Mark chapter as unread | ||||
|      * @param chapters list of selected [RecentChapter] | ||||
|      */ | ||||
|     fun markAsUnread(chapters: List<RecentChapter>) { | ||||
|         presenter.markChapterRead(chapters, false) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Start downloading chapter | ||||
|      * @param chapter selected chapter with manga | ||||
|      */ | ||||
|     fun downloadChapter(chapter: RecentChapter) { | ||||
|         presenter.downloadChapter(chapter) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Start deleting chapter | ||||
|      * @param chapter selected chapter with manga | ||||
|      */ | ||||
|     fun deleteChapter(chapter: RecentChapter) { | ||||
|         DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG) | ||||
|         presenter.deleteChapters(listOf(chapter)) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when chapters are deleted | ||||
|      */ | ||||
|     fun onChaptersDeleted() { | ||||
|         dismissDeletingDialog() | ||||
|         adapter.notifyDataSetChanged() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when error while deleting | ||||
|      * @param error error message | ||||
|      */ | ||||
|     fun onChaptersDeletedError(error: Throwable) { | ||||
|         dismissDeletingDialog() | ||||
|         Timber.e(error, error.message) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called to dismiss deleting dialog | ||||
|      */ | ||||
|     fun dismissDeletingDialog() { | ||||
|         (childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss() | ||||
|     } | ||||
|  | ||||
|     override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { | ||||
|         return false | ||||
|     } | ||||
| @@ -88,229 +312,4 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action | ||||
|         actionMode = null | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Action mode for multiple selection. | ||||
|      */ | ||||
|     private var actionMode: ActionMode? = null | ||||
|  | ||||
|     /** | ||||
|      * Adapter containing the recent chapters. | ||||
|      */ | ||||
|     lateinit var adapter: RecentChaptersAdapter | ||||
|         private set | ||||
|  | ||||
|     /** | ||||
|      * Called when view gets created | ||||
|      * @param inflater layout inflater | ||||
|      * @param container view group | ||||
|      * @param savedState status of saved state | ||||
|      */ | ||||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { | ||||
|         // Inflate view | ||||
|         return inflater.inflate(R.layout.fragment_recent_chapters, container, false) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when view is created | ||||
|      * @param view created view | ||||
|      * @param savedInstanceState status of saved sate | ||||
|      */ | ||||
|     override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { | ||||
|         // Init RecyclerView and adapter | ||||
|         recycler.layoutManager = NpaLinearLayoutManager(activity) | ||||
|         recycler.addItemDecoration(DividerItemDecoration(context.theme.getResourceDrawable(R.attr.divider_drawable))) | ||||
|         recycler.setHasFixedSize(true) | ||||
|         adapter = RecentChaptersAdapter(this) | ||||
|         recycler.adapter = adapter | ||||
|  | ||||
|         // Update toolbar text | ||||
|         setToolbarTitle(R.string.label_recent_updates) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns selected chapters | ||||
|      * @return list of [MangaChapter]s | ||||
|      */ | ||||
|     fun getSelectedChapters(): List<MangaChapter> { | ||||
|         return adapter.selectedItems.map { adapter.getItem(it) as? MangaChapter }.filterNotNull() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when item in list is clicked | ||||
|      * @param position position of clicked item | ||||
|      */ | ||||
|     override fun onListItemClick(position: Int): Boolean { | ||||
|         // Get item from position | ||||
|         val item = adapter.getItem(position) | ||||
|         if (item is MangaChapter) { | ||||
|             if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) { | ||||
|                 toggleSelection(position) | ||||
|                 return true | ||||
|             } else { | ||||
|                 openChapter(item) | ||||
|                 return false | ||||
|             } | ||||
|         } | ||||
|         return false | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when item in list is long clicked | ||||
|      * @param position position of clicked item | ||||
|      */ | ||||
|     override fun onListItemLongClick(position: Int) { | ||||
|         if (actionMode == null) | ||||
|             actionMode = activity.startSupportActionMode(this) | ||||
|  | ||||
|         toggleSelection(position) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called to toggle selection | ||||
|      * @param position position of selected item | ||||
|      */ | ||||
|     private fun toggleSelection(position: Int) { | ||||
|         adapter.toggleSelection(position, false) | ||||
|  | ||||
|         val count = adapter.selectedItemCount | ||||
|         if (count == 0) { | ||||
|             actionMode?.finish() | ||||
|         } else { | ||||
|             setContextTitle(count) | ||||
|             actionMode?.invalidate() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set the context title | ||||
|      * @param count count of selected items | ||||
|      */ | ||||
|     private fun setContextTitle(count: Int) { | ||||
|         actionMode?.title = getString(R.string.label_selected, count) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Open chapter in reader | ||||
|      * @param mangaChapter selected [MangaChapter] | ||||
|      */ | ||||
|     private fun openChapter(mangaChapter: MangaChapter) { | ||||
|         val intent = ReaderActivity.newIntent(activity, mangaChapter.manga, mangaChapter.chapter) | ||||
|         startActivity(intent) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Download selected items | ||||
|      * @param mangaChapters list of selected [MangaChapter]s | ||||
|      */ | ||||
|     fun downloadChapters(mangaChapters: List<MangaChapter>) { | ||||
|         destroyActionModeIfNeeded() | ||||
|         presenter.downloadChapters(mangaChapters) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Populate adapter with chapters | ||||
|      * @param chapters list of [Any] | ||||
|      */ | ||||
|     fun onNextMangaChapters(chapters: List<Any>) { | ||||
|         (activity as MainActivity).updateEmptyView(chapters.isEmpty(), | ||||
|                 R.string.information_no_recent, R.drawable.ic_update_black_128dp) | ||||
|  | ||||
|         destroyActionModeIfNeeded() | ||||
|         adapter.setItems(chapters) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Update download status of chapter | ||||
|      * @param download [Download] object containing download progress. | ||||
|      */ | ||||
|     fun onChapterStatusChange(download: Download) { | ||||
|         getHolder(download)?.onStatusChange(download.status) | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns holder belonging to chapter | ||||
|      * @param download [Download] object containing download progress. | ||||
|      */ | ||||
|     private fun getHolder(download: Download): RecentChaptersHolder? { | ||||
|         return recycler.findViewHolderForItemId(download.chapter.id) as? RecentChaptersHolder | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Mark chapter as read | ||||
|      * @param mangaChapters list of [MangaChapter] objects | ||||
|      */ | ||||
|     fun markAsRead(mangaChapters: List<MangaChapter>) { | ||||
|         presenter.markChapterRead(mangaChapters, true) | ||||
|         if (presenter.preferences.removeAfterMarkedAsRead()) { | ||||
|             deleteChapters(mangaChapters) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Delete selected chapters | ||||
|      * @param mangaChapters list of [MangaChapter] objects | ||||
|      */ | ||||
|     fun deleteChapters(mangaChapters: List<MangaChapter>) { | ||||
|         destroyActionModeIfNeeded() | ||||
|         DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG) | ||||
|         presenter.deleteChapters(mangaChapters) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Destory [ActionMode] if it's shown | ||||
|      */ | ||||
|     fun destroyActionModeIfNeeded() { | ||||
|         actionMode?.finish() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Mark chapter as unread | ||||
|      * @param mangaChapters list of selected [MangaChapter] | ||||
|      */ | ||||
|     fun markAsUnread(mangaChapters: List<MangaChapter>) { | ||||
|         presenter.markChapterRead(mangaChapters, false) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Start downloading chapter | ||||
|      * @param item selected chapter with manga | ||||
|      */ | ||||
|     fun downloadChapter(item: MangaChapter) { | ||||
|         presenter.downloadChapter(item) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Start deleting chapter | ||||
|      * @param item selected chapter with manga | ||||
|      */ | ||||
|     fun deleteChapter(item: MangaChapter) { | ||||
|         DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG) | ||||
|         presenter.deleteChapter(item) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when chapters are deleted | ||||
|      */ | ||||
|     fun onChaptersDeleted() { | ||||
|         dismissDeletingDialog() | ||||
|         adapter.notifyDataSetChanged() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when error while deleting | ||||
|      * @param error error message | ||||
|      */ | ||||
|     fun onChaptersDeletedError(error: Throwable) { | ||||
|         dismissDeletingDialog() | ||||
|         Timber.e(error, error.message) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called to dismiss deleting dialog | ||||
|      */ | ||||
|     fun dismissDeletingDialog() { | ||||
|         (childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss() | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.recent_updates | ||||
| import android.view.View | ||||
| import android.widget.PopupMenu | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaChapter | ||||
| import eu.kanade.tachiyomi.data.download.model.Download | ||||
| import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder | ||||
| import eu.kanade.tachiyomi.util.getResourceColor | ||||
| @@ -19,8 +18,11 @@ import kotlinx.android.synthetic.main.item_recent_chapters.view.* | ||||
|  * @param listener a listener to react to single tap and long tap events. | ||||
|  * @constructor creates a new recent chapter holder. | ||||
|  */ | ||||
| class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapter, listener: OnListItemClickListener) : | ||||
|         FlexibleViewHolder(view, adapter, listener) { | ||||
| class RecentChaptersHolder( | ||||
|         private val view: View, | ||||
|         private val adapter: RecentChaptersAdapter, | ||||
|         listener: OnListItemClickListener) | ||||
| : FlexibleViewHolder(view, adapter, listener) { | ||||
|     /** | ||||
|      * Color of read chapter | ||||
|      */ | ||||
| @@ -34,100 +36,97 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte | ||||
|     /** | ||||
|      * Object containing chapter information | ||||
|      */ | ||||
|     private var mangaChapter: MangaChapter? = null | ||||
|     private var chapter: RecentChapter? = null | ||||
|  | ||||
|     init { | ||||
|         // We need to post a Runnable to show the popup to make sure that the PopupMenu is | ||||
|         // correctly positioned. The reason being that the view may change position before the | ||||
|         // PopupMenu is shown. | ||||
|         itemView.chapter_menu.setOnClickListener { it.post({ showPopupMenu(it) }) } | ||||
|         view.chapter_menu.setOnClickListener { it.post({ showPopupMenu(it) }) } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set values of view | ||||
|      * | ||||
|      * @param item item containing chapter information | ||||
|      * @param chapter item containing chapter information | ||||
|      */ | ||||
|     fun onSetValues(item: MangaChapter) { | ||||
|         this.mangaChapter = item | ||||
|     fun onSetValues(chapter: RecentChapter) { | ||||
|         this.chapter = chapter | ||||
|  | ||||
|         // Set chapter title | ||||
|         itemView.chapter_title.text = item.chapter.name | ||||
|         view.chapter_title.text = chapter.name | ||||
|  | ||||
|         // Set manga title | ||||
|         itemView.manga_title.text = item.manga.title | ||||
|         view.manga_title.text = chapter.manga.title | ||||
|  | ||||
|         // Check if chapter is read and set correct color | ||||
|         if (item.chapter.read) { | ||||
|             itemView.chapter_title.setTextColor(readColor) | ||||
|             itemView.manga_title.setTextColor(readColor) | ||||
|         if (chapter.read) { | ||||
|             view.chapter_title.setTextColor(readColor) | ||||
|             view.manga_title.setTextColor(readColor) | ||||
|         } else { | ||||
|             itemView.chapter_title.setTextColor(unreadColor) | ||||
|             itemView.manga_title.setTextColor(unreadColor) | ||||
|             view.chapter_title.setTextColor(unreadColor) | ||||
|             view.manga_title.setTextColor(unreadColor) | ||||
|         } | ||||
|  | ||||
|         // Set chapter status | ||||
|         onStatusChange(item.chapter.status) | ||||
|         notifyStatus(chapter.status) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Updates chapter status in view. | ||||
|  | ||||
|      * | ||||
|      * @param status download status | ||||
|      */ | ||||
|     fun onStatusChange(status: Int) { | ||||
|     fun notifyStatus(status: Int) = with(view.download_text) { | ||||
|         when (status) { | ||||
|             Download.QUEUE -> itemView.download_text.setText(R.string.chapter_queued) | ||||
|             Download.DOWNLOADING -> itemView.download_text.setText(R.string.chapter_downloading) | ||||
|             Download.DOWNLOADED -> itemView.download_text.setText(R.string.chapter_downloaded) | ||||
|             Download.ERROR -> itemView.download_text.setText(R.string.chapter_error) | ||||
|             else -> itemView.download_text.text = "" | ||||
|             Download.QUEUE -> setText(R.string.chapter_queued) | ||||
|             Download.DOWNLOADING -> setText(R.string.chapter_downloading) | ||||
|             Download.DOWNLOADED -> setText(R.string.chapter_downloaded) | ||||
|             Download.ERROR -> setText(R.string.chapter_error) | ||||
|             else -> text = "" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Show pop up menu | ||||
|      * | ||||
|      * @param view view containing popup menu. | ||||
|      */ | ||||
|     private fun showPopupMenu(view: View) { | ||||
|     private fun showPopupMenu(view: View) = chapter?.let { chapter -> | ||||
|         // Create a PopupMenu, giving it the clicked view for an anchor | ||||
|         val popup = PopupMenu(adapter.fragment.activity, view) | ||||
|         val popup = PopupMenu(view.context, view) | ||||
|  | ||||
|         // Inflate our menu resource into the PopupMenu's Menu | ||||
|         popup.menuInflater.inflate(R.menu.chapter_recent, popup.menu) | ||||
|  | ||||
|         mangaChapter?.let { | ||||
|         // Hide download and show delete if the chapter is downloaded and | ||||
|         if (chapter.isDownloaded) { | ||||
|             popup.menu.findItem(R.id.action_download).isVisible = false | ||||
|             popup.menu.findItem(R.id.action_delete).isVisible = true | ||||
|         } | ||||
|  | ||||
|             // Hide download and show delete if the chapter is downloaded and | ||||
|             if (it.chapter.isDownloaded) { | ||||
|                 val menu = popup.menu | ||||
|                 menu.findItem(R.id.action_download).isVisible = false | ||||
|                 menu.findItem(R.id.action_delete).isVisible = true | ||||
|             } | ||||
|         // Hide mark as unread when the chapter is unread | ||||
|         if (!chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) { | ||||
|             popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false | ||||
|         } | ||||
|  | ||||
|             // Hide mark as unread when the chapter is unread | ||||
|             if (!it.chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) { | ||||
|                 popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false | ||||
|             } | ||||
|  | ||||
|             // Hide mark as read when the chapter is read | ||||
|             if (it.chapter.read) { | ||||
|                 popup.menu.findItem(R.id.action_mark_as_read).isVisible = false | ||||
|             } | ||||
|  | ||||
|  | ||||
|             // Set a listener so we are notified if a menu item is clicked | ||||
|             popup.setOnMenuItemClickListener { menuItem -> | ||||
|         // Hide mark as read when the chapter is read | ||||
|         if (chapter.read) { | ||||
|             popup.menu.findItem(R.id.action_mark_as_read).isVisible = false | ||||
|         } | ||||
|  | ||||
|         // Set a listener so we are notified if a menu item is clicked | ||||
|         popup.setOnMenuItemClickListener { menuItem -> | ||||
|             with(adapter.fragment) { | ||||
|                 when (menuItem.itemId) { | ||||
|                     R.id.action_download -> adapter.fragment.downloadChapter(it) | ||||
|                     R.id.action_delete -> adapter.fragment.deleteChapter(it) | ||||
|                     R.id.action_mark_as_read -> adapter.fragment.markAsRead(listOf(it)) | ||||
|                     R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(listOf(it)) | ||||
|                     R.id.action_download -> downloadChapter(chapter) | ||||
|                     R.id.action_delete -> deleteChapter(chapter) | ||||
|                     R.id.action_mark_as_read -> markAsRead(listOf(chapter)) | ||||
|                     R.id.action_mark_as_unread -> markAsUnread(listOf(chapter)) | ||||
|                 } | ||||
|                 true | ||||
|             } | ||||
|  | ||||
|             true | ||||
|         } | ||||
|  | ||||
|         // Finally show the PopupMenu | ||||
|   | ||||
| @@ -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,139 +58,26 @@ 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) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Returns observable containing chapter status. | ||||
|      * @return download object containing download progress. | ||||
|      */ | ||||
|     private fun getChapterStatusObs(): Observable<Download> { | ||||
|         return downloadManager.queue.getStatusObservable() | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .filter { download: Download -> | ||||
|                     if (chapterIdEquals(download.chapter.id)) | ||||
|                         true | ||||
|                     else | ||||
|                         false | ||||
|                 } | ||||
|                 .doOnNext { download1: Download -> updateChapterStatus(download1) } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Function to check if chapter is in recent list | ||||
|      * @param chaptersId id of chapter | ||||
|      * @return exist in recent list | ||||
|      */ | ||||
|     private fun chapterIdEquals(chaptersId: Long): Boolean { | ||||
|         mangaChapters!!.forEach { mangaChapter -> | ||||
|             if (chaptersId == mangaChapter.chapter.id) { | ||||
|                 return true | ||||
|             } | ||||
|         } | ||||
|         return false | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a list only containing MangaChapter objects. | ||||
|      * @param input the list that will be converted. | ||||
|      * @return list containing MangaChapters objects. | ||||
|      */ | ||||
|     private fun convertToMangaChaptersList(input: List<Any>): List<MangaChapter> { | ||||
|         // Create temp list | ||||
|         val tempMangaChapterList = ArrayList<MangaChapter>() | ||||
|  | ||||
|         // Only add MangaChapter objects | ||||
|         //noinspection Convert2streamapi | ||||
|         input.forEach { `object` -> | ||||
|             if (`object` is MangaChapter) { | ||||
|                 tempMangaChapterList.add(`object`) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Return temp list | ||||
|         return tempMangaChapterList | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Update status of chapters. | ||||
|      * @param download download object containing progress. | ||||
|      */ | ||||
|     private fun updateChapterStatus(download: Download) { | ||||
|         // Loop through list | ||||
|         mangaChapters?.let { | ||||
|             for (item in it) { | ||||
|                 if (download.chapter.id == item.chapter.id) { | ||||
|                     // Update status. | ||||
|                     item.chapter.status = download.status | ||||
|                     break | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Update status of chapters | ||||
|      * @param mangaChapters list containing recent chapters | ||||
|      */ | ||||
|     private fun updateChapterStatus(mangaChapters: List<MangaChapter>) { | ||||
|         // Set global list of chapters. | ||||
|         this.mangaChapters = mangaChapters | ||||
|  | ||||
|         // Update status. | ||||
|         //noinspection Convert2streamapi | ||||
|         for (mangaChapter in mangaChapters) | ||||
|             setChapterStatus(mangaChapter) | ||||
|  | ||||
|         // Start onChapterStatusChange restartable. | ||||
|         start(CHAPTER_STATUS_CHANGES) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set the chapter status | ||||
|      * @param mangaChapter MangaChapter which status gets updated | ||||
|      */ | ||||
|     private fun setChapterStatus(mangaChapter: MangaChapter) { | ||||
|         // Check if chapter in queue | ||||
|         for (download in downloadManager.queue) { | ||||
|             if (mangaChapter.chapter.id == download.chapter.id) { | ||||
|                 mangaChapter.chapter.status = download.status | ||||
|                 return | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Get source of chapter | ||||
|         val source = sourceManager.get(mangaChapter.manga.source)!! | ||||
|  | ||||
|         // Check if chapter is downloaded | ||||
|         if (downloadManager.isChapterDownloaded(source, mangaChapter.manga, mangaChapter.chapter)) { | ||||
|             mangaChapter.chapter.status = Download.DOWNLOADED | ||||
|         } else { | ||||
|             mangaChapter.chapter.status = Download.NOT_DOWNLOADED | ||||
|             start(CHAPTER_STATUS_CHANGES) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -200,33 +85,89 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() { | ||||
|      * Get observable containing recent chapters and date | ||||
|      * @return observable containing recent chapters and date | ||||
|      */ | ||||
|     fun getRecentChaptersObservable(): Observable<ArrayList<Any>>? { | ||||
|     fun getRecentChaptersObservable(): Observable<ArrayList<Any>> { | ||||
|         // Set date for recent chapters | ||||
|         val cal = Calendar.getInstance() | ||||
|         cal.time = Date() | ||||
|         cal.add(Calendar.MONTH, -1) | ||||
|         val cal = Calendar.getInstance().apply { | ||||
|             time = Date() | ||||
|             add(Calendar.MONTH, -1) | ||||
|         } | ||||
|  | ||||
|         return db.getRecentChapters(cal.time).asRxObservable() | ||||
|                 // Convert to a list of recent chapters. | ||||
|                 .map { mangaChapters -> | ||||
|                     mangaChapters.map { it.toModel() } | ||||
|                 } | ||||
|                 .doOnNext { chapters = it } | ||||
|                 // Group chapters by the date they were fetched on a ordered map. | ||||
|                 .flatMap { recentItems -> | ||||
|                     Observable.from(recentItems) | ||||
|                             .toMultimap( | ||||
|                                     { getMapKey(it.chapter.date_fetch) }, | ||||
|                                     { getMapKey(it.date_fetch) }, | ||||
|                                     { it }, | ||||
|                                     { TreeMap { d1, d2 -> d2.compareTo(d1) } }) | ||||
|                 } | ||||
|                 // Add every day and all its chapters to a single list. | ||||
|                 .map { recentItems -> | ||||
|                     val items = ArrayList<Any>() | ||||
|                     recentItems.entries.forEach { recent -> | ||||
|                         items.add(recent.key) | ||||
|                         items.addAll(recent.value) | ||||
|                     ArrayList<Any>().apply { | ||||
|                         for ((key, value) in recentItems) { | ||||
|                             add(key) | ||||
|                             addAll(value) | ||||
|                         } | ||||
|                     } | ||||
|                     items | ||||
|                 } | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns observable containing chapter status. | ||||
|      * @return download object containing download progress. | ||||
|      */ | ||||
|     private fun getChapterStatusObservable(): Observable<Download> { | ||||
|         return downloadManager.queue.getStatusObservable() | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .doOnNext { download -> onDownloadStatusChange(download) } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Converts a chapter from the database to an extended model, allowing to store new fields. | ||||
|      */ | ||||
|     private fun MangaChapter.toModel(): RecentChapter { | ||||
|         // Create the model object. | ||||
|         val model = RecentChapter(this) | ||||
|  | ||||
|         // Find an active download for this chapter. | ||||
|         val download = downloadManager.queue.find { it.chapter.id == chapter.id } | ||||
|  | ||||
|         // If there's an active download, assign it, otherwise ask the manager if the chapter is | ||||
|         // downloaded and assign it to the status. | ||||
|         if (download != null) { | ||||
|             model.download = download | ||||
|         } else { | ||||
|             // Get source of chapter. | ||||
|             val source = sourceManager.get(manga.source)!! | ||||
|  | ||||
|             model.status = if (downloadManager.isChapterDownloaded(source, manga, chapter)) | ||||
|                 Download.DOWNLOADED | ||||
|             else | ||||
|                 Download.NOT_DOWNLOADED | ||||
|         } | ||||
|         return model | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Update status of chapters. | ||||
|      * @param download download object containing progress. | ||||
|      */ | ||||
|     private fun onDownloadStatusChange(download: Download) { | ||||
|         // Assign the download to the model object. | ||||
|         if (download.status == Download.QUEUE) { | ||||
|             val chapter = chapters?.find { it.id == download.chapter.id } | ||||
|             if (chapter != null && chapter.download == null) { | ||||
|                 chapter.download = download | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get date as time key | ||||
|      * @param date desired date | ||||
| @@ -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 | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
|                         } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user