Fix dependency injection and use custom models extending DB ones

This commit is contained in:
len 2016-06-14 15:17:37 +02:00
parent 658860fdff
commit 237af4b07d
53 changed files with 840 additions and 788 deletions

View File

@ -1,14 +1,13 @@
package eu.kanade.tachiyomi.data.backup package eu.kanade.tachiyomi.data.backup
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.* import com.google.gson.*
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import eu.kanade.tachiyomi.data.backup.serializer.IdExclusion import eu.kanade.tachiyomi.data.backup.serializer.IdExclusion
import eu.kanade.tachiyomi.data.backup.serializer.IntegerSerializer import eu.kanade.tachiyomi.data.backup.serializer.IntegerSerializer
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.* import eu.kanade.tachiyomi.data.database.models.*
import java.io.* import java.io.*
import java.lang.reflect.Type
import java.util.* import java.util.*
/** /**
@ -191,8 +190,7 @@ class BackupManager(private val db: DatabaseHelper) {
private fun restoreCategories(jsonCategories: JsonArray) { private fun restoreCategories(jsonCategories: JsonArray) {
// Get categories from file and from db // Get categories from file and from db
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = db.getCategories().executeAsBlocking()
val backupCategories = getArrayOrEmpty<Category>(jsonCategories, val backupCategories = gson.fromJson<List<CategoryImpl>>(jsonCategories)
object : TypeToken<List<Category>>() {}.type)
// Iterate over them // Iterate over them
for (category in backupCategories) { for (category in backupCategories) {
@ -224,17 +222,13 @@ class BackupManager(private val db: DatabaseHelper) {
* @param jsonMangas the mangas and its related data (chapters, sync, categories) from the json. * @param jsonMangas the mangas and its related data (chapters, sync, categories) from the json.
*/ */
private fun restoreMangas(jsonMangas: JsonArray) { private fun restoreMangas(jsonMangas: JsonArray) {
val chapterToken = object : TypeToken<List<Chapter>>() {}.type
val mangaSyncToken = object : TypeToken<List<MangaSync>>() {}.type
val categoriesNamesToken = object : TypeToken<List<String>>() {}.type
for (backupManga in jsonMangas) { for (backupManga in jsonMangas) {
// Map every entry to objects // Map every entry to objects
val element = backupManga.asJsonObject val element = backupManga.asJsonObject
val manga = gson.fromJson(element.get(MANGA), Manga::class.java) val manga = gson.fromJson(element.get(MANGA), MangaImpl::class.java)
val chapters = getArrayOrEmpty<Chapter>(element.get(CHAPTERS), chapterToken) val chapters = gson.fromJson<List<ChapterImpl>>(element.get(CHAPTERS) ?: JsonArray())
val sync = getArrayOrEmpty<MangaSync>(element.get(MANGA_SYNC), mangaSyncToken) val sync = gson.fromJson<List<MangaSyncImpl>>(element.get(MANGA_SYNC) ?: JsonArray())
val categories = getArrayOrEmpty<String>(element.get(CATEGORIES), categoriesNamesToken) val categories = gson.fromJson<List<String>>(element.get(CATEGORIES) ?: JsonArray())
// Restore everything related to this manga // Restore everything related to this manga
restoreManga(manga) restoreManga(manga)
@ -340,7 +334,7 @@ class BackupManager(private val db: DatabaseHelper) {
private fun restoreSyncForManga(manga: Manga, sync: List<MangaSync>) { private fun restoreSyncForManga(manga: Manga, sync: List<MangaSync>) {
// Fix foreign keys with the current manga id // Fix foreign keys with the current manga id
for (mangaSync in sync) { for (mangaSync in sync) {
mangaSync.manga_id = manga.id mangaSync.manga_id = manga.id!!
} }
val dbSyncs = db.getMangasSync(manga).executeAsBlocking() val dbSyncs = db.getMangasSync(manga).executeAsBlocking()
@ -367,15 +361,4 @@ class BackupManager(private val db: DatabaseHelper) {
} }
} }
/**
* Returns a list of items from a json element, or an empty list if the element is null.
*
* @param element the json to be mapped to a list of items.
* @param type the gson mapping to restore the list.
* @return a list of items.
*/
private fun <T> getArrayOrEmpty(element: JsonElement?, type: Type): List<T> {
return gson.fromJson<List<T>>(element, type) ?: ArrayList<T>()
}
} }

View File

@ -16,10 +16,7 @@ import eu.kanade.tachiyomi.data.source.Source
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.model.Page
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.OnlineSource
import eu.kanade.tachiyomi.util.DiskUtils import eu.kanade.tachiyomi.util.*
import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator
import eu.kanade.tachiyomi.util.UrlUtil
import eu.kanade.tachiyomi.util.saveImageTo
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -27,12 +24,17 @@ import rx.schedulers.Schedulers
import rx.subjects.BehaviorSubject import rx.subjects.BehaviorSubject
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.io.FileReader import java.io.FileReader
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
class DownloadManager(private val context: Context, private val sourceManager: SourceManager, private val preferences: PreferencesHelper) { class DownloadManager(
private val context: Context,
private val sourceManager: SourceManager = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get()
) {
private val gson = Gson() private val gson = Gson()
@ -270,10 +272,8 @@ class DownloadManager(private val context: Context, private val sourceManager: S
} }
page page
} }
.retryWhen { // Retry 3 times, waiting 2, 4 and 8 seconds between attempts.
it.zipWith(Observable.range(1, 3)) { errors, retries -> retries } .retryWhen(RetryWithDelay(3, { (2 shl it - 1) * 1000 }))
.flatMap { retries -> Observable.timer((retries * 2).toLong(), TimeUnit.SECONDS) }
}
} }
// Public method to get the image from the filesystem. It does NOT provide any way to download the image // Public method to get the image from the filesystem. It does NOT provide any way to download the image

View File

@ -7,14 +7,13 @@ import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
class DownloadService : Service() { class DownloadService : Service() {
@ -29,8 +28,8 @@ class DownloadService : Service() {
} }
} }
@Inject lateinit var downloadManager: DownloadManager val downloadManager: DownloadManager by injectLazy()
@Inject lateinit var preferences: PreferencesHelper val preferences: PreferencesHelper by injectLazy()
private var wakeLock: PowerManager.WakeLock? = null private var wakeLock: PowerManager.WakeLock? = null
private var networkChangeSubscription: Subscription? = null private var networkChangeSubscription: Subscription? = null
@ -39,7 +38,6 @@ class DownloadService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
App.get(this).component.inject(this)
createWakeLock() createWakeLock()

View File

@ -7,28 +7,26 @@ import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.GlideModule import com.bumptech.glide.module.GlideModule
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.data.network.NetworkHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.InputStream import java.io.InputStream
import javax.inject.Inject
/** /**
* Class used to update Glide module settings * Class used to update Glide module settings
*/ */
class AppGlideModule : GlideModule { class AppGlideModule : GlideModule {
@Inject lateinit var networkHelper: NetworkHelper
override fun applyOptions(context: Context, builder: GlideBuilder) { override fun applyOptions(context: Context, builder: GlideBuilder) {
// Set the cache size of Glide to 15 MiB // Set the cache size of Glide to 15 MiB
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 15 * 1024 * 1024)) builder.setDiskCache(InternalCacheDiskCacheFactory(context, 15 * 1024 * 1024))
} }
override fun registerComponents(context: Context, glide: Glide) { override fun registerComponents(context: Context, glide: Glide) {
App.get(context).component.inject(this) val networkFactory = OkHttpUrlLoader.Factory(Injekt.get<NetworkHelper>().client)
glide.register(GlideUrl::class.java, InputStream::class.java,
OkHttpUrlLoader.Factory(networkHelper.client)) glide.register(GlideUrl::class.java, InputStream::class.java, networkFactory)
glide.register(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory()) glide.register(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory())
} }
} }

View File

@ -5,14 +5,13 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.data.DataFetcher import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.model.* import com.bumptech.glide.load.model.*
import com.bumptech.glide.load.model.stream.StreamModelLoader import com.bumptech.glide.load.model.stream.StreamModelLoader
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.OnlineSource
import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import javax.inject.Inject
/** /**
* A class for loading a cover associated with a [Manga] that can be present in our own cache. * A class for loading a cover associated with a [Manga] that can be present in our own cache.
@ -30,12 +29,12 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
/** /**
* Cover cache where persistent covers are stored. * Cover cache where persistent covers are stored.
*/ */
@Inject lateinit var coverCache: CoverCache val coverCache: CoverCache by injectLazy()
/** /**
* Source manager. * Source manager.
*/ */
@Inject lateinit var sourceManager: SourceManager val sourceManager: SourceManager by injectLazy()
/** /**
* Base network loader. * Base network loader.
@ -54,10 +53,6 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
*/ */
private val cachedHeaders = hashMapOf<Int, LazyHeaders>() private val cachedHeaders = hashMapOf<Int, LazyHeaders>()
init {
App.get(context).component.inject(this)
}
/** /**
* Factory class for creating [MangaModelLoader] instances. * Factory class for creating [MangaModelLoader] instances.
*/ */
@ -88,7 +83,7 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
// Obtain the request url and the file for this url from the LRU cache, or calculate it // Obtain the request url and the file for this url from the LRU cache, or calculate it
// and add them to the cache. // and add them to the cache.
val (glideUrl, file) = modelCache.get(url, width, height) ?: val (glideUrl, file) = modelCache.get(url, width, height) ?:
Pair(GlideUrl(url, getHeaders(manga)), coverCache.getCoverFile(url)).apply { Pair(GlideUrl(url, getHeaders(manga)), coverCache.getCoverFile(url!!)).apply {
modelCache.put(url, width, height, this) modelCache.put(url, width, height, this)
} }

View File

@ -10,12 +10,12 @@ import android.os.PowerManager
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.Constants import eu.kanade.tachiyomi.Constants
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.OnlineSource
@ -24,9 +24,9 @@ import eu.kanade.tachiyomi.util.*
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
/** /**
* This class will take care of updating the chapters of the manga from the library. It can be * This class will take care of updating the chapters of the manga from the library. It can be
@ -41,17 +41,17 @@ class LibraryUpdateService : Service() {
/** /**
* Database helper. * Database helper.
*/ */
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
/** /**
* Source manager. * Source manager.
*/ */
@Inject lateinit var sourceManager: SourceManager val sourceManager: SourceManager by injectLazy()
/** /**
* Preferences. * Preferences.
*/ */
@Inject lateinit var preferences: PreferencesHelper val preferences: PreferencesHelper by injectLazy()
/** /**
* Wake lock that will be held until the service is destroyed. * Wake lock that will be held until the service is destroyed.
@ -126,7 +126,6 @@ class LibraryUpdateService : Service() {
*/ */
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
App.get(this).component.inject(this)
createAndAcquireWakeLock() createAndAcquireWakeLock()
} }

View File

@ -2,23 +2,18 @@ package eu.kanade.tachiyomi.data.mangasync
import android.content.Context import android.content.Context
import android.support.annotation.CallSuper import android.support.annotation.CallSuper
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.database.models.MangaSync import eu.kanade.tachiyomi.data.database.models.MangaSync
import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.data.network.NetworkHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import rx.Completable import rx.Completable
import rx.Observable import rx.Observable
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
abstract class MangaSyncService(private val context: Context, val id: Int) { abstract class MangaSyncService(private val context: Context, val id: Int) {
@Inject lateinit var preferences: PreferencesHelper val preferences: PreferencesHelper by injectLazy()
@Inject lateinit var networkService: NetworkHelper val networkService: NetworkHelper by injectLazy()
init {
App.get(context).component.inject(this)
}
open val client: OkHttpClient open val client: OkHttpClient
get() = networkService.client get() = networkService.client

View File

@ -4,25 +4,23 @@ import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.MangaSync import eu.kanade.tachiyomi.data.database.models.MangaSync
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
class UpdateMangaSyncService : Service() { class UpdateMangaSyncService : Service() {
@Inject lateinit var syncManager: MangaSyncManager val syncManager: MangaSyncManager by injectLazy()
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
private lateinit var subscriptions: CompositeSubscription private lateinit var subscriptions: CompositeSubscription
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
App.get(this).component.inject(this)
subscriptions = CompositeSubscription() subscriptions = CompositeSubscription()
} }

View File

@ -97,8 +97,8 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
.flatMap { Observable.from(it.select("entry")) } .flatMap { Observable.from(it.select("entry")) }
.filter { it.select("type").text() != "Novel" } .filter { it.select("type").text() != "Novel" }
.map { .map {
MangaSync.create(this).apply { MangaSync.create(id).apply {
title = it.selectText("title") title = it.selectText("title")!!
remote_id = it.selectInt("id") remote_id = it.selectInt("id")
total_chapters = it.selectInt("chapters") total_chapters = it.selectInt("chapters")
} }
@ -114,8 +114,8 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
.map { Jsoup.parse(it.body().string()) } .map { Jsoup.parse(it.body().string()) }
.flatMap { Observable.from(it.select("manga")) } .flatMap { Observable.from(it.select("manga")) }
.map { .map {
MangaSync.create(this).apply { MangaSync.create(id).apply {
title = it.selectText("series_title") title = it.selectText("series_title")!!
remote_id = it.selectInt("series_mangadb_id") remote_id = it.selectInt("series_mangadb_id")
last_chapter_read = it.selectInt("my_read_chapters") last_chapter_read = it.selectInt("my_read_chapters")
status = it.selectInt("my_status") status = it.selectInt("my_status")

View File

@ -1,9 +1,7 @@
package eu.kanade.tachiyomi.data.source.model; package eu.kanade.tachiyomi.data.source.model;
import java.util.List;
import eu.kanade.tachiyomi.data.database.models.Chapter;
import eu.kanade.tachiyomi.data.network.ProgressListener; import eu.kanade.tachiyomi.data.network.ProgressListener;
import eu.kanade.tachiyomi.ui.reader.ReaderChapter;
import rx.subjects.PublishSubject; import rx.subjects.PublishSubject;
public class Page implements ProgressListener { public class Page implements ProgressListener {
@ -11,7 +9,7 @@ public class Page implements ProgressListener {
private int pageNumber; private int pageNumber;
private String url; private String url;
private String imageUrl; private String imageUrl;
private transient Chapter chapter; private transient ReaderChapter chapter;
private transient String imagePath; private transient String imagePath;
private transient volatile int status; private transient volatile int status;
private transient volatile int progress; private transient volatile int progress;
@ -90,16 +88,12 @@ public class Page implements ProgressListener {
this.statusSubject = subject; this.statusSubject = subject;
} }
public Chapter getChapter() { public ReaderChapter getChapter() {
return chapter; return chapter;
} }
public void setChapter(Chapter chapter) { public void setChapter(ReaderChapter chapter) {
this.chapter = chapter; this.chapter = chapter;
} }
public boolean isLastPage() {
List<Page> chapterPages = chapter.getPages();
return chapterPages.size() -1 == pageNumber;
}
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.source.online package eu.kanade.tachiyomi.data.source.online
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -14,9 +13,10 @@ import eu.kanade.tachiyomi.data.source.Language
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.Source
import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.data.source.model.MangasPage
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.model.Page
import eu.kanade.tachiyomi.util.UrlUtil
import okhttp3.* import okhttp3.*
import rx.Observable import rx.Observable
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
/** /**
* A simple implementation for sources from a website. * A simple implementation for sources from a website.
@ -28,17 +28,17 @@ abstract class OnlineSource(context: Context) : Source {
/** /**
* Network service. * Network service.
*/ */
@Inject lateinit var network: NetworkHelper val network: NetworkHelper by injectLazy()
/** /**
* Chapter cache. * Chapter cache.
*/ */
@Inject lateinit var chapterCache: ChapterCache val chapterCache: ChapterCache by injectLazy()
/** /**
* Preferences helper. * Preferences helper.
*/ */
@Inject lateinit var preferences: PreferencesHelper val preferences: PreferencesHelper by injectLazy()
/** /**
* Base url of the website without the trailing slash, like: http://mysite.com * Base url of the website without the trailing slash, like: http://mysite.com
@ -61,11 +61,6 @@ abstract class OnlineSource(context: Context) : Source {
open val client: OkHttpClient open val client: OkHttpClient
get() = network.client get() = network.client
init {
// Inject dependencies.
App.get(context).component.inject(this)
}
/** /**
* Headers builder for requests. Implementations can override this method for custom headers. * Headers builder for requests. Implementations can override this method for custom headers.
*/ */
@ -443,6 +438,15 @@ abstract class OnlineSource(context: Context) : Source {
} }
} }
fun Chapter.setUrlWithoutDomain(url: String) {
this.url = UrlUtil.getPath(url)
}
fun Manga.setUrlWithoutDomain(url: String) {
this.url = UrlUtil.getPath(url)
}
// Overridable method to allow custom parsing. // Overridable method to allow custom parsing.
open fun parseChapterNumber(chapter: Chapter) { open fun parseChapterNumber(chapter: Chapter) {

View File

@ -26,8 +26,7 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) {
override fun popularMangaParse(response: Response, page: MangasPage) { override fun popularMangaParse(response: Response, page: MangasPage) {
val document = Jsoup.parse(response.body().string()) val document = Jsoup.parse(response.body().string())
for (element in document.select(popularMangaSelector())) { for (element in document.select(popularMangaSelector())) {
Manga().apply { Manga.create(id).apply {
source = this@ParsedOnlineSource.id
popularMangaFromElement(element, this) popularMangaFromElement(element, this)
page.mangas.add(this) page.mangas.add(this)
} }
@ -70,8 +69,7 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) {
override fun searchMangaParse(response: Response, page: MangasPage, query: String) { override fun searchMangaParse(response: Response, page: MangasPage, query: String) {
val document = Jsoup.parse(response.body().string()) val document = Jsoup.parse(response.body().string())
for (element in document.select(searchMangaSelector())) { for (element in document.select(searchMangaSelector())) {
Manga().apply { Manga.create(id).apply {
source = this@ParsedOnlineSource.id
searchMangaFromElement(element, this) searchMangaFromElement(element, this)
page.mangas.add(this) page.mangas.add(this)
} }

View File

@ -54,10 +54,9 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
override fun popularMangaParse(response: Response, page: MangasPage) { override fun popularMangaParse(response: Response, page: MangasPage) {
val document = Jsoup.parse(response.body().string()) val document = Jsoup.parse(response.body().string())
for (element in document.select(map.popular.manga_css)) { for (element in document.select(map.popular.manga_css)) {
Manga().apply { Manga.create(id).apply {
source = this@YamlOnlineSource.id
title = element.text() title = element.text()
setUrl(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
page.mangas.add(this) page.mangas.add(this)
} }
} }
@ -84,10 +83,9 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
override fun searchMangaParse(response: Response, page: MangasPage, query: String) { override fun searchMangaParse(response: Response, page: MangasPage, query: String) {
val document = Jsoup.parse(response.body().string()) val document = Jsoup.parse(response.body().string())
for (element in document.select(map.search.manga_css)) { for (element in document.select(map.search.manga_css)) {
Manga().apply { Manga.create(id).apply {
source = this@YamlOnlineSource.id
title = element.text() title = element.text()
setUrl(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
page.mangas.add(this) page.mangas.add(this)
} }
} }
@ -123,7 +121,7 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
val chapter = Chapter.create() val chapter = Chapter.create()
element.select(title).first().let { element.select(title).first().let {
chapter.name = it.text() chapter.name = it.text()
chapter.setUrl(it.attr("href")) chapter.setUrlWithoutDomain(it.attr("href"))
} }
val dateElement = element.select(date?.select).first() val dateElement = element.select(date?.select).first()
chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0 chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0

View File

@ -62,8 +62,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
override fun popularMangaParse(response: Response, page: MangasPage) { override fun popularMangaParse(response: Response, page: MangasPage) {
val document = Jsoup.parse(response.body().string()) val document = Jsoup.parse(response.body().string())
for (element in document.select(popularMangaSelector())) { for (element in document.select(popularMangaSelector())) {
Manga().apply { Manga.create(id).apply {
source = this@Batoto.id
popularMangaFromElement(element, this) popularMangaFromElement(element, this)
page.mangas.add(this) page.mangas.add(this)
} }
@ -78,7 +77,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("a[href^=http://bato.to]").first().let { element.select("a[href^=http://bato.to]").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text().trim() manga.title = it.text().trim()
} }
} }
@ -90,8 +89,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
override fun searchMangaParse(response: Response, page: MangasPage, query: String) { override fun searchMangaParse(response: Response, page: MangasPage, query: String) {
val document = Jsoup.parse(response.body().string()) val document = Jsoup.parse(response.body().string())
for (element in document.select(searchMangaSelector())) { for (element in document.select(searchMangaSelector())) {
Manga().apply { Manga.create(id).apply {
source = this@Batoto.id
searchMangaFromElement(element, this) searchMangaFromElement(element, this)
page.mangas.add(this) page.mangas.add(this)
} }
@ -156,7 +154,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
override fun chapterFromElement(element: Element, chapter: Chapter) { override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select("a[href^=http://bato.to/reader").first() val urlElement = element.select("a[href^=http://bato.to/reader").first()
chapter.setUrl(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = element.select("td").getOrNull(4)?.let { chapter.date_upload = element.select("td").getOrNull(4)?.let {
parseDateFromElement(it) parseDateFromElement(it)

View File

@ -35,7 +35,7 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("td a:eq(0)").first().let { element.select("td a:eq(0)").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
} }
@ -88,7 +88,7 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun chapterFromElement(element: Element, chapter: Chapter) { override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
chapter.setUrl(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
SimpleDateFormat("MM/dd/yyyy").parse(it).time SimpleDateFormat("MM/dd/yyyy").parse(it).time

View File

@ -29,7 +29,7 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("a.title").first().let { element.select("a.title").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
} }
@ -43,7 +43,7 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
override fun searchMangaFromElement(element: Element, manga: Manga) { override fun searchMangaFromElement(element: Element, manga: Manga) {
element.select("a.series_preview").first().let { element.select("a.series_preview").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
} }
@ -74,7 +74,7 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
override fun chapterFromElement(element: Element, chapter: Chapter) { override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select("a.tips").first() val urlElement = element.select("a.tips").first()
chapter.setUrl(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0 chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0
} }

View File

@ -27,7 +27,7 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("div.title > a").first().let { element.select("div.title > a").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
} }
@ -41,7 +41,7 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun searchMangaFromElement(element: Element, manga: Manga) { override fun searchMangaFromElement(element: Element, manga: Manga) {
element.select("a.manga_info").first().let { element.select("a.manga_info").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
} }
@ -71,7 +71,7 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun chapterFromElement(element: Element, chapter: Chapter) { override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
chapter.setUrl(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0 chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0
} }

View File

@ -28,7 +28,7 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("div.title > h2 > a").first().let { element.select("div.title > h2 > a").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.attr("title") manga.title = it.attr("title")
} }
} }
@ -54,7 +54,7 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
override fun searchMangaFromElement(element: Element, manga: Manga) { override fun searchMangaFromElement(element: Element, manga: Manga) {
element.select("div.title > h2 > a").first().let { element.select("div.title > h2 > a").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.attr("title") manga.title = it.attr("title")
} }
} }
@ -83,7 +83,7 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
override fun chapterFromElement(element: Element, chapter: Chapter) { override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
chapter.setUrl(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.select("span.val").text() chapter.name = urlElement.select("span.val").text()
chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0 chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0
} }

View File

@ -29,7 +29,7 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("h2 > a").first().let { element.select("h2 > a").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
} }
@ -69,7 +69,7 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun chapterFromElement(element: Element, chapter: Chapter) { override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
chapter.setUrl(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = element.select("div.date").first()?.text()?.let { chapter.date_upload = element.select("div.date").first()?.text()?.let {
SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time

View File

@ -30,7 +30,7 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("h3 > a").first().let { element.select("h3 > a").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.attr("title") manga.title = it.attr("title")
} }
} }
@ -69,7 +69,7 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun chapterFromElement(element: Element, chapter: Chapter) { override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
chapter.setUrl(urlElement.attr("href") + "?mature=1") chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1")
chapter.name = urlElement.text().replace(" новое", "") chapter.name = urlElement.text().replace(" новое", "")
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time

View File

@ -30,7 +30,7 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun popularMangaFromElement(element: Element, manga: Manga) { override fun popularMangaFromElement(element: Element, manga: Manga) {
element.select("h3 > a").first().let { element.select("h3 > a").first().let {
manga.setUrl(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.attr("title") manga.title = it.attr("title")
} }
} }
@ -69,7 +69,7 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
override fun chapterFromElement(element: Element, chapter: Chapter) { override fun chapterFromElement(element: Element, chapter: Chapter) {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
chapter.setUrl(urlElement.attr("href") + "?mature=1") chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1")
chapter.name = urlElement.text().replace(" новое", "") chapter.name = urlElement.text().replace(" новое", "")
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time

View File

@ -8,7 +8,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.AsyncTask import android.os.AsyncTask
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.Constants import eu.kanade.tachiyomi.Constants
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.data.network.GET
@ -18,8 +17,8 @@ import eu.kanade.tachiyomi.data.network.newCallWithProgress
import eu.kanade.tachiyomi.util.notificationManager import eu.kanade.tachiyomi.util.notificationManager
import eu.kanade.tachiyomi.util.saveTo import eu.kanade.tachiyomi.util.saveTo
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import javax.inject.Inject
class UpdateDownloader(private val context: Context) : class UpdateDownloader(private val context: Context) :
AsyncTask<String, Int, UpdateDownloader.DownloadResult>() { AsyncTask<String, Int, UpdateDownloader.DownloadResult>() {
@ -40,7 +39,7 @@ class UpdateDownloader(private val context: Context) :
} }
} }
@Inject lateinit var network: NetworkHelper val network: NetworkHelper by injectLazy()
/** /**
* Default download dir * Default download dir
@ -59,9 +58,6 @@ class UpdateDownloader(private val context: Context) :
private val notificationId: Int private val notificationId: Int
get() = Constants.NOTIFICATION_UPDATER_ID get() = Constants.NOTIFICATION_UPDATER_ID
init {
App.get(context).component.inject(this)
}
/** /**
* Class containing download result * Class containing download result

View File

@ -8,9 +8,9 @@ import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import javax.inject.Inject
/** /**
* Presenter of [BackupFragment]. * Presenter of [BackupFragment].
@ -20,7 +20,7 @@ class BackupPresenter : BasePresenter<BackupFragment>() {
/** /**
* Database. * Database.
*/ */
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
/** /**
* Backup manager. * Backup manager.

View File

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

View File

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

View File

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

View File

@ -383,7 +383,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
* @return the holder of the manga or null if it's not bound. * @return the holder of the manga or null if it's not bound.
*/ */
private fun getHolder(manga: Manga): CatalogueGridHolder? { private fun getHolder(manga: Manga): CatalogueGridHolder? {
return catalogue_grid.findViewHolderForItemId(manga.id) as? CatalogueGridHolder return catalogue_grid.findViewHolderForItemId(manga.id!!) as? CatalogueGridHolder
} }
/** /**

View File

@ -19,7 +19,7 @@ import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
/** /**
* Presenter of [CatalogueFragment]. * Presenter of [CatalogueFragment].
@ -29,22 +29,22 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
/** /**
* Source manager. * Source manager.
*/ */
@Inject lateinit var sourceManager: SourceManager val sourceManager: SourceManager by injectLazy()
/** /**
* Database. * Database.
*/ */
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
/** /**
* Preferences. * Preferences.
*/ */
@Inject lateinit var prefs: PreferencesHelper val prefs: PreferencesHelper by injectLazy()
/** /**
* Cover cache. * Cover cache.
*/ */
@Inject lateinit var coverCache: CoverCache val coverCache: CoverCache by injectLazy()
/** /**
* Enabled sources. * Enabled sources.

View File

@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
/** /**
* Presenter of CategoryActivity. * Presenter of CategoryActivity.
@ -17,7 +17,7 @@ class CategoryPresenter : BasePresenter<CategoryActivity>() {
/** /**
* Used to connect to database * Used to connect to database
*/ */
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
/** /**
* List containing categories * List containing categories

View File

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

View File

@ -262,7 +262,7 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
* @return the holder of the download or null if it's not bound. * @return the holder of the download or null if it's not bound.
*/ */
private fun getHolder(download: Download): DownloadHolder? { private fun getHolder(download: Download): DownloadHolder? {
return recycler.findViewHolderForItemId(download.chapter.id) as? DownloadHolder return recycler.findViewHolderForItemId(download.chapter.id!!) as? DownloadHolder
} }
/** /**

View File

@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import rx.Observable import rx.Observable
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
/** /**
* Presenter of [DownloadFragment]. * Presenter of [DownloadFragment].
@ -24,7 +24,7 @@ class DownloadPresenter : BasePresenter<DownloadFragment>() {
/** /**
* Download manager. * Download manager.
*/ */
@Inject lateinit var downloadManager: DownloadManager val downloadManager: DownloadManager by injectLazy()
/** /**
* Property to get the queue from the download manager. * Property to get the queue from the download manager.

View File

@ -49,7 +49,7 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) :
* @return an identifier for the item. * @return an identifier for the item.
*/ */
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
return mItems[position].id return mItems[position].id!!
} }
/** /**
@ -72,8 +72,8 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) :
* @return true if the manga should be included, false otherwise. * @return true if the manga should be included, false otherwise.
*/ */
override fun filterObject(manga: Manga, query: String): Boolean = with(manga) { override fun filterObject(manga: Manga, query: String): Boolean = with(manga) {
title != null && title.toLowerCase().contains(query) || title.toLowerCase().contains(query) ||
author != null && author.toLowerCase().contains(query) author != null && author!!.toLowerCase().contains(query)
} }
/** /**

View File

@ -16,10 +16,10 @@ import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import rx.subjects.BehaviorSubject import rx.subjects.BehaviorSubject
import uy.kohesive.injekt.injectLazy
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.* import java.util.*
import javax.inject.Inject
/** /**
* Presenter of [LibraryFragment]. * Presenter of [LibraryFragment].
@ -49,27 +49,27 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
/** /**
* Database. * Database.
*/ */
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
/** /**
* Preferences. * Preferences.
*/ */
@Inject lateinit var preferences: PreferencesHelper val preferences: PreferencesHelper by injectLazy()
/** /**
* Cover cache. * Cover cache.
*/ */
@Inject lateinit var coverCache: CoverCache val coverCache: CoverCache by injectLazy()
/** /**
* Source manager. * Source manager.
*/ */
@Inject lateinit var sourceManager: SourceManager val sourceManager: SourceManager by injectLazy()
/** /**
* Download manager. * Download manager.
*/ */
@Inject lateinit var downloadManager: DownloadManager val downloadManager: DownloadManager by injectLazy()
companion object { companion object {
/** /**
@ -279,7 +279,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
@Throws(IOException::class) @Throws(IOException::class)
fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean { fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean {
if (manga.thumbnail_url != null && manga.favorite) { if (manga.thumbnail_url != null && manga.favorite) {
coverCache.copyToCache(manga.thumbnail_url, inputStream) coverCache.copyToCache(manga.thumbnail_url!!, inputStream)
return true return true
} }
return false return false

View File

@ -5,7 +5,6 @@ import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v4.view.GravityCompat import android.support.v4.view.GravityCompat
import android.view.MenuItem import android.view.MenuItem
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.backup.BackupFragment import eu.kanade.tachiyomi.ui.backup.BackupFragment
@ -18,11 +17,11 @@ import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadFragment
import eu.kanade.tachiyomi.ui.setting.SettingsActivity import eu.kanade.tachiyomi.ui.setting.SettingsActivity
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.toolbar.* import kotlinx.android.synthetic.main.toolbar.*
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
class MainActivity : BaseActivity() { class MainActivity : BaseActivity() {
@Inject lateinit var preferences: PreferencesHelper val preferences: PreferencesHelper by injectLazy()
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
setAppTheme() setAppTheme()
@ -34,8 +33,6 @@ class MainActivity : BaseActivity() {
return return
} }
App.get(this).component.inject(this)
// Inflate activity_main.xml. // Inflate activity_main.xml.
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)

View File

@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent
import eu.kanade.tachiyomi.util.SharedData import eu.kanade.tachiyomi.util.SharedData
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
/** /**
* Presenter of [MangaActivity]. * Presenter of [MangaActivity].
@ -19,12 +19,12 @@ class MangaPresenter : BasePresenter<MangaActivity>() {
/** /**
* Database helper. * Database helper.
*/ */
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
/** /**
* Manga sync manager. * Manga sync manager.
*/ */
@Inject lateinit var syncManager: MangaSyncManager val syncManager: MangaSyncManager by injectLazy()
/** /**
* Manga associated with this instance. * Manga associated with this instance.

View File

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

View File

@ -3,10 +3,9 @@ package eu.kanade.tachiyomi.ui.manga.chapter
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<ChaptersHolder, Chapter>() { class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<ChaptersHolder, ChapterModel>() {
init { init {
setHasStableIds(true) setHasStableIds(true)
@ -30,10 +29,10 @@ class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<Chapters
} }
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
return mItems[position].id return mItems[position].id!!
} }
fun setItems(chapters: List<Chapter>) { fun setItems(chapters: List<ChapterModel>) {
mItems = chapters mItems = chapters
notifyDataSetChanged() notifyDataSetChanged()
} }

View File

@ -134,8 +134,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
presenter.setDownloadedFilter(item.isChecked) presenter.setDownloadedFilter(item.isChecked)
} }
R.id.action_filter_empty -> { R.id.action_filter_empty -> {
presenter.setReadFilter(false) presenter.removeFilters()
presenter.setDownloadedFilter(false)
activity.supportInvalidateOptionsMenu() activity.supportInvalidateOptionsMenu()
} }
R.id.action_sort -> presenter.revertSortOrder() R.id.action_sort -> presenter.revertSortOrder()
@ -150,7 +149,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
setDownloadedFilter() setDownloadedFilter()
} }
fun onNextChapters(chapters: List<Chapter>) { fun onNextChapters(chapters: List<ChapterModel>) {
// If the list is empty, fetch chapters from source if the conditions are met // If the list is empty, fetch chapters from source if the conditions are met
// We use presenter chapters instead because they are always unfiltered // We use presenter chapters instead because they are always unfiltered
if (presenter.chapters.isEmpty()) if (presenter.chapters.isEmpty())
@ -206,7 +205,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
// Save the new display mode // Save the new display mode
presenter.setDisplayMode(itemView.id) presenter.setDisplayMode(itemView.id)
// Refresh ui // Refresh ui
adapter.notifyDataSetChanged() adapter.notifyItemRangeChanged(0, adapter.itemCount)
true true
} }
.show() .show()
@ -271,7 +270,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
} }
private fun getHolder(chapter: Chapter): ChaptersHolder? { private fun getHolder(chapter: Chapter): ChaptersHolder? {
return recycler.findViewHolderForItemId(chapter.id) as? ChaptersHolder return recycler.findViewHolderForItemId(chapter.id!!) as? ChaptersHolder
} }
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
@ -309,7 +308,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
actionMode = null actionMode = null
} }
fun getSelectedChapters(): List<Chapter> { fun getSelectedChapters(): List<ChapterModel> {
return adapter.selectedItems.map { adapter.getItem(it) } return adapter.selectedItems.map { adapter.getItem(it) }
} }
@ -322,27 +321,27 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
setContextTitle(adapter.selectedItemCount) setContextTitle(adapter.selectedItemCount)
} }
fun markAsRead(chapters: List<Chapter>) { fun markAsRead(chapters: List<ChapterModel>) {
presenter.markChaptersRead(chapters, true) presenter.markChaptersRead(chapters, true)
if (presenter.preferences.removeAfterMarkedAsRead()) { if (presenter.preferences.removeAfterMarkedAsRead()) {
deleteChapters(chapters) deleteChapters(chapters)
} }
} }
fun markAsUnread(chapters: List<Chapter>) { fun markAsUnread(chapters: List<ChapterModel>) {
presenter.markChaptersRead(chapters, false) presenter.markChaptersRead(chapters, false)
} }
fun markPreviousAsRead(chapter: Chapter) { fun markPreviousAsRead(chapter: ChapterModel) {
presenter.markPreviousChaptersAsRead(chapter) presenter.markPreviousChaptersAsRead(chapter)
} }
fun downloadChapters(chapters: List<Chapter>) { fun downloadChapters(chapters: List<ChapterModel>) {
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
presenter.downloadChapters(chapters) presenter.downloadChapters(chapters)
} }
fun deleteChapters(chapters: List<Chapter>) { fun deleteChapters(chapters: List<ChapterModel>) {
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG) DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
presenter.deleteChapters(chapters) presenter.deleteChapters(chapters)
@ -350,7 +349,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
fun onChaptersDeleted() { fun onChaptersDeleted() {
dismissDeletingDialog() dismissDeletingDialog()
adapter.notifyDataSetChanged() adapter.notifyItemRangeChanged(0, adapter.itemCount)
} }
fun onChaptersDeletedError(error: Throwable) { fun onChaptersDeletedError(error: Throwable) {

View File

@ -1,10 +1,8 @@
package eu.kanade.tachiyomi.ui.manga.chapter package eu.kanade.tachiyomi.ui.manga.chapter
import android.content.Context
import android.view.View import android.view.View
import android.widget.PopupMenu import android.widget.PopupMenu
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
@ -26,7 +24,7 @@ class ChaptersHolder(
private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' }) private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' })
private val df = DateFormat.getDateInstance(DateFormat.SHORT) private val df = DateFormat.getDateInstance(DateFormat.SHORT)
private var item: Chapter? = null private var item: ChapterModel? = null
init { init {
// We need to post a Runnable to show the popup to make sure that the PopupMenu is // We need to post a Runnable to show the popup to make sure that the PopupMenu is
@ -35,19 +33,16 @@ class ChaptersHolder(
view.chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } } view.chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } }
} }
fun onSetValues(chapter: Chapter, manga: Manga?) = with(view) { fun onSetValues(chapter: ChapterModel, manga: Manga?) = with(view) {
item = chapter item = chapter
val name: String chapter_title.text = when (manga?.displayMode) {
when (manga?.displayMode) {
Manga.DISPLAY_NUMBER -> { Manga.DISPLAY_NUMBER -> {
val formattedNumber = decimalFormat.format(chapter.chapter_number.toDouble()) val formattedNumber = decimalFormat.format(chapter.chapter_number.toDouble())
name = context.getString(R.string.display_mode_chapter, formattedNumber) context.getString(R.string.display_mode_chapter, formattedNumber)
} }
else -> name = chapter.name else -> chapter.name
} }
chapter_title.text = name
chapter_title.setTextColor(if (chapter.read) readColor else unreadColor) chapter_title.setTextColor(if (chapter.read) readColor else unreadColor)
if (chapter.date_upload > 0) { if (chapter.date_upload > 0) {
@ -57,31 +52,26 @@ class ChaptersHolder(
chapter_date.text = "" chapter_date.text = ""
} }
if (!chapter.read && chapter.last_page_read > 0) { chapter_pages.text = if (!chapter.read && chapter.last_page_read > 0) {
chapter_pages.text = context.getString(R.string.chapter_progress, chapter.last_page_read + 1) context.getString(R.string.chapter_progress, chapter.last_page_read + 1)
} else { } else {
chapter_pages.text = "" ""
} }
notifyStatus(chapter.status) notifyStatus(chapter.status)
} }
fun notifyStatus(status: Int) = with(view) { fun notifyStatus(status: Int) = with(view.download_text) {
when (status) { when (status) {
Download.QUEUE -> download_text.setText(R.string.chapter_queued) Download.QUEUE -> setText(R.string.chapter_queued)
Download.DOWNLOADING -> download_text.setText(R.string.chapter_downloading) Download.DOWNLOADING -> setText(R.string.chapter_downloading)
Download.DOWNLOADED -> download_text.setText(R.string.chapter_downloaded) Download.DOWNLOADED -> setText(R.string.chapter_downloaded)
Download.ERROR -> download_text.setText(R.string.chapter_error) Download.ERROR -> setText(R.string.chapter_error)
else -> download_text.text = "" else -> text = ""
} }
} }
fun onProgressChange(context: Context, downloaded: Int, total: Int) { private fun showPopupMenu(view: View) = item?.let { chapter ->
view.download_text.text = context.getString(
R.string.chapter_downloading_progress, downloaded, total)
}
private fun showPopupMenu(view: View) = item?.let { item ->
// Create a PopupMenu, giving it the clicked view for an anchor // Create a PopupMenu, giving it the clicked view for an anchor
val popup = PopupMenu(view.context, view) val popup = PopupMenu(view.context, view)
@ -89,32 +79,35 @@ class ChaptersHolder(
popup.menuInflater.inflate(R.menu.chapter_single, popup.menu) popup.menuInflater.inflate(R.menu.chapter_single, popup.menu)
// Hide download and show delete if the chapter is downloaded // Hide download and show delete if the chapter is downloaded
if (item.isDownloaded) { if (chapter.isDownloaded) {
popup.menu.findItem(R.id.action_download).isVisible = false popup.menu.findItem(R.id.action_download).isVisible = false
popup.menu.findItem(R.id.action_delete).isVisible = true popup.menu.findItem(R.id.action_delete).isVisible = true
} }
// Hide mark as unread when the chapter is unread // Hide mark as unread when the chapter is unread
if (!item.read && item.last_page_read == 0) { if (!chapter.read && chapter.last_page_read == 0) {
popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
} }
// Hide mark as read when the chapter is read // Hide mark as read when the chapter is read
if (item.read) { if (chapter.read) {
popup.menu.findItem(R.id.action_mark_as_read).isVisible = false popup.menu.findItem(R.id.action_mark_as_read).isVisible = false
} }
// Set a listener so we are notified if a menu item is clicked // Set a listener so we are notified if a menu item is clicked
popup.setOnMenuItemClickListener { menuItem -> popup.setOnMenuItemClickListener { menuItem ->
val chapter = listOf(item) val chapterList = listOf(chapter)
when (menuItem.itemId) { with(adapter.fragment) {
R.id.action_download -> adapter.fragment.downloadChapters(chapter) when (menuItem.itemId) {
R.id.action_delete -> adapter.fragment.deleteChapters(chapter) R.id.action_download -> downloadChapters(chapterList)
R.id.action_mark_as_read -> adapter.fragment.markAsRead(chapter) R.id.action_delete -> deleteChapters(chapterList)
R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(chapter) R.id.action_mark_as_read -> markAsRead(chapterList)
R.id.action_mark_previous_as_read -> adapter.fragment.markPreviousAsRead(item) R.id.action_mark_as_unread -> markAsUnread(chapterList)
R.id.action_mark_previous_as_read -> markPreviousAsRead(chapter)
}
} }
true true
} }

View File

@ -20,108 +20,197 @@ import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
/**
* Presenter of [ChaptersFragment].
*/
class ChaptersPresenter : BasePresenter<ChaptersFragment>() { class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
@Inject lateinit var db: DatabaseHelper /**
@Inject lateinit var sourceManager: SourceManager * Database helper.
@Inject lateinit var preferences: PreferencesHelper */
@Inject lateinit var downloadManager: DownloadManager val db: DatabaseHelper by injectLazy()
/**
* Source manager.
*/
val sourceManager: SourceManager by injectLazy()
/**
* Preferences.
*/
val preferences: PreferencesHelper by injectLazy()
/**
* Downloads manager.
*/
val downloadManager: DownloadManager by injectLazy()
/**
* Active manga.
*/
lateinit var manga: Manga lateinit var manga: Manga
private set private set
/**
* Source of the manga.
*/
lateinit var source: Source lateinit var source: Source
private set private set
lateinit var chapters: List<Chapter> /**
* List of chapters of the manga. It's always unfiltered and unsorted.
*/
lateinit var chapters: List<ChapterModel>
private set private set
lateinit var chaptersSubject: PublishSubject<List<Chapter>> /**
* Subject of list of chapters to allow updating the view without going to DB.
*/
val chaptersSubject by lazy { PublishSubject.create<List<ChapterModel>>() }
/**
* Whether the chapter list has been requested to the source.
*/
var hasRequested = false
private set private set
var hasRequested: Boolean = false companion object {
private set /**
* Id of the restartable which sends a filtered and ordered list of chapters to the view.
*/
private const val GET_CHAPTERS = 1
private val DB_CHAPTERS = 1 /**
private val FETCH_CHAPTERS = 2 * Id of the restartable which requests an updated list of chapters to the source.
private val CHAPTER_STATUS_CHANGES = 3 */
private const val FETCH_CHAPTERS = 2
/**
* Id of the restartable which listens for download status changes.
*/
private const val CHAPTER_STATUS_CHANGES = 3
}
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
chaptersSubject = PublishSubject.create() startableLatestCache(GET_CHAPTERS,
// On each subject emission, apply filters and sort then update the view.
startableLatestCache(DB_CHAPTERS, { chaptersSubject
{ getDbChaptersObs() }, .flatMap { applyChapterFilters(it) }
.observeOn(AndroidSchedulers.mainThread()) },
{ view, chapters -> view.onNextChapters(chapters) }) { view, chapters -> view.onNextChapters(chapters) })
startableFirst(FETCH_CHAPTERS, startableFirst(FETCH_CHAPTERS,
{ getOnlineChaptersObs() }, { getRemoteChaptersObservable() },
{ view, result -> view.onFetchChaptersDone() }, { view, result -> view.onFetchChaptersDone() },
{ view, error -> view.onFetchChaptersError(error) }) { view, error -> view.onFetchChaptersError(error) })
startableLatestCache(CHAPTER_STATUS_CHANGES, startableLatestCache(CHAPTER_STATUS_CHANGES,
{ getChapterStatusObs() }, { getChapterStatusObservable() },
{ view, download -> view.onChapterStatusChange(download) }, { view, download -> view.onChapterStatusChange(download) },
{ view, error -> Timber.e(error.cause, error.message) }) { view, error -> Timber.e(error.cause, error.message) })
// Find the active manga from the shared data or return.
manga = SharedData.get(MangaEvent::class.java)?.manga ?: return manga = SharedData.get(MangaEvent::class.java)?.manga ?: return
Observable.just(manga) Observable.just(manga)
.subscribeLatestCache({ view, manga -> view.onNextManga(manga) }) .subscribeLatestCache({ view, manga -> view.onNextManga(manga) })
// Find the source for this manga.
source = sourceManager.get(manga.source)!! source = sourceManager.get(manga.source)!!
start(DB_CHAPTERS)
// Prepare the publish subject.
start(GET_CHAPTERS)
// Add the subscription that retrieves the chapters from the database, keeps subscribed to
// changes, and sends the list of chapters to the publish subject.
add(db.getChapters(manga).asRxObservable() add(db.getChapters(manga).asRxObservable()
.map { chapters ->
// Convert every chapter to a model.
chapters.map { it.toModel() }
}
.doOnNext { chapters -> .doOnNext { chapters ->
// Store the last emission
this.chapters = chapters this.chapters = chapters
SharedData.get(ChapterCountEvent::class.java)?.emit(chapters.size)
for (chapter in chapters) { // Listen for download status changes
setChapterStatus(chapter)
}
start(CHAPTER_STATUS_CHANGES) start(CHAPTER_STATUS_CHANGES)
// Emit the number of chapters to the info tab.
SharedData.get(ChapterCountEvent::class.java)?.emit(chapters.size)
} }
.subscribe { chaptersSubject.onNext(it) }) .subscribe { chaptersSubject.onNext(it) })
} }
/**
* Converts a chapter from the database to an extended model, allowing to store new fields.
*/
private fun Chapter.toModel(): ChapterModel {
// Create the model object.
val model = ChapterModel(this)
// Find an active download for this chapter.
val download = downloadManager.queue.find { it.chapter.id == id }
if (download != null) {
// If there's an active download, assign it.
model.download = download
} else {
// Otherwise ask the manager if the chapter is downloaded and assign it to the status.
model.status = if (downloadManager.isChapterDownloaded(source, manga, this))
Download.DOWNLOADED
else
Download.NOT_DOWNLOADED
}
return model
}
/**
* Requests an updated list of chapters from the source.
*/
fun fetchChaptersFromSource() { fun fetchChaptersFromSource() {
hasRequested = true hasRequested = true
start(FETCH_CHAPTERS) start(FETCH_CHAPTERS)
} }
/**
* Updates the UI after applying the filters.
*/
private fun refreshChapters() { private fun refreshChapters() {
chaptersSubject.onNext(chapters) chaptersSubject.onNext(chapters)
} }
fun getOnlineChaptersObs(): Observable<Pair<Int, Int>> { /**
return source.fetchChapterList(manga) * Returns an observable that updates the chapter list with the latest from the source.
.subscribeOn(Schedulers.io()) */
.map { syncChaptersWithSource(db, it, manga, source) } fun getRemoteChaptersObservable() = source.fetchChapterList(manga)
.observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io())
} .map { syncChaptersWithSource(db, it, manga, source) }
.observeOn(AndroidSchedulers.mainThread())
fun getDbChaptersObs(): Observable<List<Chapter>> { /**
return chaptersSubject * Returns an observable that listens to download queue status changes.
.flatMap { applyChapterFilters(it) } */
.observeOn(AndroidSchedulers.mainThread()) fun getChapterStatusObservable() = downloadManager.queue.getStatusObservable()
} .observeOn(AndroidSchedulers.mainThread())
.filter { download -> download.manga.id == manga.id }
.doOnNext { onDownloadStatusChange(it) }
fun getChapterStatusObs(): Observable<Download> { /**
return downloadManager.queue.getStatusObservable() * Applies the view filters to the list of chapters obtained from the database.
.observeOn(AndroidSchedulers.mainThread()) *
.filter { download -> download.manga.id == manga.id } * @param chapters the list of chapters from the database
.doOnNext { updateChapterStatus(it) } * @return an observable of the list of chapters filtered and sorted.
} */
private fun applyChapterFilters(chapters: List<ChapterModel>): Observable<List<ChapterModel>> {
private fun applyChapterFilters(chapters: List<Chapter>): Observable<List<Chapter>> {
var observable = Observable.from(chapters).subscribeOn(Schedulers.io()) var observable = Observable.from(chapters).subscribeOn(Schedulers.io())
if (onlyUnread()) { if (onlyUnread()) {
observable = observable.filter { chapter -> !chapter.read } observable = observable.filter { !it.read }
} }
if (onlyDownloaded()) { if (onlyDownloaded()) {
observable = observable.filter { chapter -> chapter.status == Download.DOWNLOADED } observable = observable.filter { it.isDownloaded }
} }
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) { val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
Manga.SORTING_SOURCE -> when (sortDescending()) { Manga.SORTING_SOURCE -> when (sortDescending()) {
@ -137,37 +226,40 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
return observable.toSortedList(sortFunction) return observable.toSortedList(sortFunction)
} }
private fun setChapterStatus(chapter: Chapter) { /**
for (download in downloadManager.queue) { * Called when a download for the active manga changes status.
if (chapter.id == download.chapter.id) { *
chapter.status = download.status * @param download the download whose status changed.
return */
fun onDownloadStatusChange(download: Download) {
// Assign the download to the model object.
if (download.status == Download.QUEUE) {
chapters.find { it.id == download.chapter.id }?.let {
if (it.download == null) {
it.download = download
}
} }
} }
if (downloadManager.isChapterDownloaded(source, manga, chapter)) { // Force UI update if downloaded filter active and download finished.
chapter.status = Download.DOWNLOADED
} else {
chapter.status = Download.NOT_DOWNLOADED
}
}
fun updateChapterStatus(download: Download) {
for (chapter in chapters) {
if (download.chapter.id == chapter.id) {
chapter.status = download.status
break
}
}
if (onlyDownloaded() && download.status == Download.DOWNLOADED) if (onlyDownloaded() && download.status == Download.DOWNLOADED)
refreshChapters() refreshChapters()
} }
fun getNextUnreadChapter(): Chapter? { /**
* Returns the next unread chapter or null if everything is read.
*/
fun getNextUnreadChapter(): ChapterModel? {
return chapters.sortedByDescending { it.source_order }.find { !it.read } return chapters.sortedByDescending { it.source_order }.find { !it.read }
} }
fun markChaptersRead(selectedChapters: List<Chapter>, read: Boolean) { /**
* Mark the selected chapter list as read/unread.
*
* @param selectedChapters the list of selected chapters.
* @param read whether to mark chapters as read or unread.
*/
fun markChaptersRead(selectedChapters: List<ChapterModel>, read: Boolean) {
Observable.from(selectedChapters) Observable.from(selectedChapters)
.doOnNext { chapter -> .doOnNext { chapter ->
chapter.read = read chapter.read = read
@ -181,21 +273,36 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
.subscribe() .subscribe()
} }
fun markPreviousChaptersAsRead(selected: Chapter) { /**
* Mark the previous chapters to the selected one as read.
*
* @param chapter the selected chapter.
*/
fun markPreviousChaptersAsRead(chapter: ChapterModel) {
Observable.from(chapters) Observable.from(chapters)
.filter { it.isRecognizedNumber && it.chapter_number < selected.chapter_number } .filter { it.isRecognizedNumber && it.chapter_number < chapter.chapter_number }
.doOnNext { it.read = true } .doOnNext { it.read = true }
.toList() .toList()
.flatMap { db.updateChaptersProgress(it).asRxObservable() } .flatMap { db.updateChaptersProgress(it).asRxObservable() }
.subscribe() .subscribe()
} }
fun downloadChapters(chapters: List<Chapter>) { /**
* Downloads the given list of chapters with the manager.
*
* @param chapters the list of chapters to download.
*/
fun downloadChapters(chapters: List<ChapterModel>) {
DownloadService.start(context) DownloadService.start(context)
downloadManager.downloadChapters(manga, chapters) downloadManager.downloadChapters(manga, chapters)
} }
fun deleteChapters(chapters: List<Chapter>) { /**
* Deletes the given list of chapter.
*
* @param chapters the list of chapters to delete.
*/
fun deleteChapters(chapters: List<ChapterModel>) {
val wasRunning = downloadManager.isRunning val wasRunning = downloadManager.isRunning
if (wasRunning) { if (wasRunning) {
DownloadService.stop(context) DownloadService.stop(context)
@ -216,49 +323,97 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
}) })
} }
private fun deleteChapter(chapter: Chapter) { /**
* Deletes a chapter from disk. This method is called in a background thread.
*
* @param chapter the chapter to delete.
*/
private fun deleteChapter(chapter: ChapterModel) {
downloadManager.queue.del(chapter) downloadManager.queue.del(chapter)
downloadManager.deleteChapter(source, manga, chapter) downloadManager.deleteChapter(source, manga, chapter)
chapter.status = Download.NOT_DOWNLOADED chapter.status = Download.NOT_DOWNLOADED
chapter.download = null
} }
/**
* Reverses the sorting and requests an UI update.
*/
fun revertSortOrder() { fun revertSortOrder() {
manga.setChapterOrder(if (sortDescending()) Manga.SORT_ASC else Manga.SORT_DESC) manga.setChapterOrder(if (sortDescending()) Manga.SORT_ASC else Manga.SORT_DESC)
db.updateFlags(manga).executeAsBlocking() db.updateFlags(manga).executeAsBlocking()
refreshChapters() refreshChapters()
} }
/**
* Sets the read filter and requests an UI update.
*
* @param onlyUnread whether to display only unread chapters or all chapters.
*/
fun setReadFilter(onlyUnread: Boolean) { fun setReadFilter(onlyUnread: Boolean) {
manga.readFilter = if (onlyUnread) Manga.SHOW_UNREAD else Manga.SHOW_ALL manga.readFilter = if (onlyUnread) Manga.SHOW_UNREAD else Manga.SHOW_ALL
db.updateFlags(manga).executeAsBlocking() db.updateFlags(manga).executeAsBlocking()
refreshChapters() refreshChapters()
} }
/**
* Sets the download filter and requests an UI update.
*
* @param onlyDownloaded whether to display only downloaded chapters or all chapters.
*/
fun setDownloadedFilter(onlyDownloaded: Boolean) { fun setDownloadedFilter(onlyDownloaded: Boolean) {
manga.downloadedFilter = if (onlyDownloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL manga.downloadedFilter = if (onlyDownloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL
db.updateFlags(manga).executeAsBlocking() db.updateFlags(manga).executeAsBlocking()
refreshChapters() refreshChapters()
} }
/**
* Removes all filters and requests an UI update.
*/
fun removeFilters() {
manga.readFilter = Manga.SHOW_ALL
manga.downloadedFilter = Manga.SHOW_ALL
db.updateFlags(manga).executeAsBlocking()
refreshChapters()
}
/**
* Sets the active display mode.
*
* @param mode the mode to set.
*/
fun setDisplayMode(mode: Int) { fun setDisplayMode(mode: Int) {
manga.displayMode = mode manga.displayMode = mode
db.updateFlags(manga).executeAsBlocking() db.updateFlags(manga).executeAsBlocking()
} }
fun setSorting(mode: Int) { /**
manga.sorting = mode * Sets the sorting method and requests an UI update.
*
* @param sort the sorting mode.
*/
fun setSorting(sort: Int) {
manga.sorting = sort
db.updateFlags(manga).executeAsBlocking() db.updateFlags(manga).executeAsBlocking()
refreshChapters() refreshChapters()
} }
/**
* Whether the display only downloaded filter is enabled.
*/
fun onlyDownloaded(): Boolean { fun onlyDownloaded(): Boolean {
return manga.downloadedFilter == Manga.SHOW_DOWNLOADED return manga.downloadedFilter == Manga.SHOW_DOWNLOADED
} }
/**
* Whether the display only unread filter is enabled.
*/
fun onlyUnread(): Boolean { fun onlyUnread(): Boolean {
return manga.readFilter == Manga.SHOW_UNREAD return manga.readFilter == Manga.SHOW_UNREAD
} }
/**
* Whether the sorting method is descending or ascending.
*/
fun sortDescending(): Boolean { fun sortDescending(): Boolean {
return manga.sortDescending() return manga.sortDescending()
} }

View File

@ -104,7 +104,12 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
manga_genres.text = manga.genre manga_genres.text = manga.genre
// Update status TextView. // Update status TextView.
manga_status.text = manga.getStatus(activity) manga_status.setText(when (manga.status) {
Manga.ONGOING -> R.string.ongoing
Manga.COMPLETED -> R.string.completed
Manga.LICENSED -> R.string.licensed
else -> R.string.unknown
})
// Update description TextView. // Update description TextView.
manga_summary.text = manga.description manga_summary.text = manga.description

View File

@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.util.SharedData
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
/** /**
* Presenter of MangaInfoFragment. * Presenter of MangaInfoFragment.
@ -36,17 +36,17 @@ class MangaInfoPresenter : BasePresenter<MangaInfoFragment>() {
/** /**
* Used to connect to database. * Used to connect to database.
*/ */
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
/** /**
* Used to connect to different manga sources. * Used to connect to different manga sources.
*/ */
@Inject lateinit var sourceManager: SourceManager val sourceManager: SourceManager by injectLazy()
/** /**
* Used to connect to cache. * Used to connect to cache.
*/ */
@Inject lateinit var coverCache: CoverCache val coverCache: CoverCache by injectLazy()
/** /**
* The id of the restartable. * The id of the restartable.

View File

@ -15,12 +15,12 @@ import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() { class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
@Inject lateinit var syncManager: MangaSyncManager val syncManager: MangaSyncManager by injectLazy()
val myAnimeList by lazy { syncManager.myAnimeList } val myAnimeList by lazy { syncManager.myAnimeList }
@ -124,7 +124,7 @@ class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
fun registerManga(sync: MangaSync?) { fun registerManga(sync: MangaSync?) {
if (sync != null) { if (sync != null) {
sync.manga_id = manga.id sync.manga_id = manga.id!!
add(myAnimeList.bind(sync) add(myAnimeList.bind(sync)
.flatMap { db.insertMangaSync(sync).asRxObservable() } .flatMap { db.insertMangaSync(sync).asRxObservable() }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())

View File

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

View File

@ -5,7 +5,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
import java.util.* import java.util.*
@ -18,7 +17,8 @@ import java.util.*
* @constructor creates an instance of the adapter. * @constructor creates an instance of the adapter.
*/ */
class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdapter<RecyclerView.ViewHolder, Any>() { class RecentChaptersAdapter(val fragment: RecentChaptersFragment)
: FlexibleAdapter<RecyclerView.ViewHolder, Any>() {
/** /**
* The id of the view type * The id of the view type
*/ */
@ -45,7 +45,7 @@ class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdap
val item = getItem(position) val item = getItem(position)
when (holder.itemViewType) { when (holder.itemViewType) {
VIEW_TYPE_CHAPTER -> { VIEW_TYPE_CHAPTER -> {
if (item is MangaChapter) { if (item is RecentChapter) {
(holder as RecentChaptersHolder).onSetValues(item) (holder as RecentChaptersHolder).onSetValues(item)
} }
} }
@ -89,7 +89,7 @@ class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdap
* @param position position of item * @param position position of item
*/ */
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return if (getItem(position) is MangaChapter) VIEW_TYPE_CHAPTER else VIEW_TYPE_SECTION return if (getItem(position) is RecentChapter) VIEW_TYPE_CHAPTER else VIEW_TYPE_SECTION
} }
@ -110,8 +110,8 @@ class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdap
*/ */
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
val item = getItem(position) val item = getItem(position)
if (item is MangaChapter) if (item is RecentChapter)
return item.chapter.id return item.id!!
else else
return item.hashCode().toLong() return item.hashCode().toLong()
} }

View File

@ -7,14 +7,12 @@ import android.view.*
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.getResourceDrawable import eu.kanade.tachiyomi.util.getResourceDrawable
import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.widget.DeletingChaptersDialog import eu.kanade.tachiyomi.widget.DeletingChaptersDialog
import eu.kanade.tachiyomi.widget.DividerItemDecoration import eu.kanade.tachiyomi.widget.DividerItemDecoration
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
@ -28,7 +26,9 @@ import timber.log.Timber
* UI related actions should be called from here. * UI related actions should be called from here.
*/ */
@RequiresPresenter(RecentChaptersPresenter::class) @RequiresPresenter(RecentChaptersPresenter::class)
class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener { class RecentChaptersFragment
: BaseRxFragment<RecentChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
companion object { companion object {
/** /**
* Create new RecentChaptersFragment. * Create new RecentChaptersFragment.
@ -40,6 +40,230 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
} }
} }
/**
* Action mode for multiple selection.
*/
private var actionMode: ActionMode? = null
/**
* Adapter containing the recent chapters.
*/
lateinit var adapter: RecentChaptersAdapter
private set
/**
* Called when view gets created
* @param inflater layout inflater
* @param container view group
* @param savedState status of saved state
*/
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View {
// Inflate view
return inflater.inflate(R.layout.fragment_recent_chapters, container, false)
}
/**
* Called when view is created
* @param view created view
* @param savedInstanceState status of saved sate
*/
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Init RecyclerView and adapter
recycler.layoutManager = NpaLinearLayoutManager(activity)
recycler.addItemDecoration(DividerItemDecoration(context.theme.getResourceDrawable(R.attr.divider_drawable)))
recycler.setHasFixedSize(true)
adapter = RecentChaptersAdapter(this)
recycler.adapter = adapter
// Update toolbar text
setToolbarTitle(R.string.label_recent_updates)
}
/**
* Returns selected chapters
* @return list of selected chapters
*/
fun getSelectedChapters(): List<RecentChapter> {
return adapter.selectedItems.map { adapter.getItem(it) as? RecentChapter }.filterNotNull()
}
/**
* Called when item in list is clicked
* @param position position of clicked item
*/
override fun onListItemClick(position: Int): Boolean {
// Get item from position
val item = adapter.getItem(position)
if (item is RecentChapter) {
if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) {
toggleSelection(position)
return true
} else {
openChapter(item)
return false
}
}
return false
}
/**
* Called when item in list is long clicked
* @param position position of clicked item
*/
override fun onListItemLongClick(position: Int) {
if (actionMode == null)
actionMode = activity.startSupportActionMode(this)
toggleSelection(position)
}
/**
* Called to toggle selection
* @param position position of selected item
*/
private fun toggleSelection(position: Int) {
adapter.toggleSelection(position, false)
val count = adapter.selectedItemCount
if (count == 0) {
actionMode?.finish()
} else {
setContextTitle(count)
actionMode?.invalidate()
}
}
/**
* Set the context title
* @param count count of selected items
*/
private fun setContextTitle(count: Int) {
actionMode?.title = getString(R.string.label_selected, count)
}
/**
* Open chapter in reader
* @param chapter selected chapter
*/
private fun openChapter(chapter: RecentChapter) {
val intent = ReaderActivity.newIntent(activity, chapter.manga, chapter)
startActivity(intent)
}
/**
* Download selected items
* @param chapters list of selected [RecentChapter]s
*/
fun downloadChapters(chapters: List<RecentChapter>) {
destroyActionModeIfNeeded()
presenter.downloadChapters(chapters)
}
/**
* Populate adapter with chapters
* @param chapters list of [Any]
*/
fun onNextRecentChapters(chapters: List<Any>) {
(activity as MainActivity).updateEmptyView(chapters.isEmpty(),
R.string.information_no_recent, R.drawable.ic_update_black_128dp)
destroyActionModeIfNeeded()
adapter.setItems(chapters)
}
/**
* Update download status of chapter
* @param download [Download] object containing download progress.
*/
fun onChapterStatusChange(download: Download) {
getHolder(download)?.notifyStatus(download.status)
}
/**
* Returns holder belonging to chapter
* @param download [Download] object containing download progress.
*/
private fun getHolder(download: Download): RecentChaptersHolder? {
return recycler.findViewHolderForItemId(download.chapter.id!!) as? RecentChaptersHolder
}
/**
* Mark chapter as read
* @param chapters list of chapters
*/
fun markAsRead(chapters: List<RecentChapter>) {
presenter.markChapterRead(chapters, true)
if (presenter.preferences.removeAfterMarkedAsRead()) {
deleteChapters(chapters)
}
}
/**
* Delete selected chapters
* @param chapters list of [RecentChapter] objects
*/
fun deleteChapters(chapters: List<RecentChapter>) {
destroyActionModeIfNeeded()
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
presenter.deleteChapters(chapters)
}
/**
* Destory [ActionMode] if it's shown
*/
fun destroyActionModeIfNeeded() {
actionMode?.finish()
}
/**
* Mark chapter as unread
* @param chapters list of selected [RecentChapter]
*/
fun markAsUnread(chapters: List<RecentChapter>) {
presenter.markChapterRead(chapters, false)
}
/**
* Start downloading chapter
* @param chapter selected chapter with manga
*/
fun downloadChapter(chapter: RecentChapter) {
presenter.downloadChapter(chapter)
}
/**
* Start deleting chapter
* @param chapter selected chapter with manga
*/
fun deleteChapter(chapter: RecentChapter) {
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
presenter.deleteChapters(listOf(chapter))
}
/**
* Called when chapters are deleted
*/
fun onChaptersDeleted() {
dismissDeletingDialog()
adapter.notifyDataSetChanged()
}
/**
* Called when error while deleting
* @param error error message
*/
fun onChaptersDeletedError(error: Throwable) {
dismissDeletingDialog()
Timber.e(error, error.message)
}
/**
* Called to dismiss deleting dialog
*/
fun dismissDeletingDialog() {
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false return false
} }
@ -88,229 +312,4 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Action
actionMode = null actionMode = null
} }
/**
* Action mode for multiple selection.
*/
private var actionMode: ActionMode? = null
/**
* Adapter containing the recent chapters.
*/
lateinit var adapter: RecentChaptersAdapter
private set
/**
* Called when view gets created
* @param inflater layout inflater
* @param container view group
* @param savedState status of saved state
*/
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
// Inflate view
return inflater.inflate(R.layout.fragment_recent_chapters, container, false)
}
/**
* Called when view is created
* @param view created view
* @param savedInstanceState status of saved sate
*/
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
// Init RecyclerView and adapter
recycler.layoutManager = NpaLinearLayoutManager(activity)
recycler.addItemDecoration(DividerItemDecoration(context.theme.getResourceDrawable(R.attr.divider_drawable)))
recycler.setHasFixedSize(true)
adapter = RecentChaptersAdapter(this)
recycler.adapter = adapter
// Update toolbar text
setToolbarTitle(R.string.label_recent_updates)
}
/**
* Returns selected chapters
* @return list of [MangaChapter]s
*/
fun getSelectedChapters(): List<MangaChapter> {
return adapter.selectedItems.map { adapter.getItem(it) as? MangaChapter }.filterNotNull()
}
/**
* Called when item in list is clicked
* @param position position of clicked item
*/
override fun onListItemClick(position: Int): Boolean {
// Get item from position
val item = adapter.getItem(position)
if (item is MangaChapter) {
if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) {
toggleSelection(position)
return true
} else {
openChapter(item)
return false
}
}
return false
}
/**
* Called when item in list is long clicked
* @param position position of clicked item
*/
override fun onListItemLongClick(position: Int) {
if (actionMode == null)
actionMode = activity.startSupportActionMode(this)
toggleSelection(position)
}
/**
* Called to toggle selection
* @param position position of selected item
*/
private fun toggleSelection(position: Int) {
adapter.toggleSelection(position, false)
val count = adapter.selectedItemCount
if (count == 0) {
actionMode?.finish()
} else {
setContextTitle(count)
actionMode?.invalidate()
}
}
/**
* Set the context title
* @param count count of selected items
*/
private fun setContextTitle(count: Int) {
actionMode?.title = getString(R.string.label_selected, count)
}
/**
* Open chapter in reader
* @param mangaChapter selected [MangaChapter]
*/
private fun openChapter(mangaChapter: MangaChapter) {
val intent = ReaderActivity.newIntent(activity, mangaChapter.manga, mangaChapter.chapter)
startActivity(intent)
}
/**
* Download selected items
* @param mangaChapters list of selected [MangaChapter]s
*/
fun downloadChapters(mangaChapters: List<MangaChapter>) {
destroyActionModeIfNeeded()
presenter.downloadChapters(mangaChapters)
}
/**
* Populate adapter with chapters
* @param chapters list of [Any]
*/
fun onNextMangaChapters(chapters: List<Any>) {
(activity as MainActivity).updateEmptyView(chapters.isEmpty(),
R.string.information_no_recent, R.drawable.ic_update_black_128dp)
destroyActionModeIfNeeded()
adapter.setItems(chapters)
}
/**
* Update download status of chapter
* @param download [Download] object containing download progress.
*/
fun onChapterStatusChange(download: Download) {
getHolder(download)?.onStatusChange(download.status)
}
/**
* Returns holder belonging to chapter
* @param download [Download] object containing download progress.
*/
private fun getHolder(download: Download): RecentChaptersHolder? {
return recycler.findViewHolderForItemId(download.chapter.id) as? RecentChaptersHolder
}
/**
* Mark chapter as read
* @param mangaChapters list of [MangaChapter] objects
*/
fun markAsRead(mangaChapters: List<MangaChapter>) {
presenter.markChapterRead(mangaChapters, true)
if (presenter.preferences.removeAfterMarkedAsRead()) {
deleteChapters(mangaChapters)
}
}
/**
* Delete selected chapters
* @param mangaChapters list of [MangaChapter] objects
*/
fun deleteChapters(mangaChapters: List<MangaChapter>) {
destroyActionModeIfNeeded()
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
presenter.deleteChapters(mangaChapters)
}
/**
* Destory [ActionMode] if it's shown
*/
fun destroyActionModeIfNeeded() {
actionMode?.finish()
}
/**
* Mark chapter as unread
* @param mangaChapters list of selected [MangaChapter]
*/
fun markAsUnread(mangaChapters: List<MangaChapter>) {
presenter.markChapterRead(mangaChapters, false)
}
/**
* Start downloading chapter
* @param item selected chapter with manga
*/
fun downloadChapter(item: MangaChapter) {
presenter.downloadChapter(item)
}
/**
* Start deleting chapter
* @param item selected chapter with manga
*/
fun deleteChapter(item: MangaChapter) {
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
presenter.deleteChapter(item)
}
/**
* Called when chapters are deleted
*/
fun onChaptersDeleted() {
dismissDeletingDialog()
adapter.notifyDataSetChanged()
}
/**
* Called when error while deleting
* @param error error message
*/
fun onChaptersDeletedError(error: Throwable) {
dismissDeletingDialog()
Timber.e(error, error.message)
}
/**
* Called to dismiss deleting dialog
*/
fun dismissDeletingDialog() {
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
}
} }

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.recent_updates
import android.view.View import android.view.View
import android.widget.PopupMenu import android.widget.PopupMenu
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
@ -19,8 +18,11 @@ import kotlinx.android.synthetic.main.item_recent_chapters.view.*
* @param listener a listener to react to single tap and long tap events. * @param listener a listener to react to single tap and long tap events.
* @constructor creates a new recent chapter holder. * @constructor creates a new recent chapter holder.
*/ */
class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapter, listener: OnListItemClickListener) : class RecentChaptersHolder(
FlexibleViewHolder(view, adapter, listener) { private val view: View,
private val adapter: RecentChaptersAdapter,
listener: OnListItemClickListener)
: FlexibleViewHolder(view, adapter, listener) {
/** /**
* Color of read chapter * Color of read chapter
*/ */
@ -34,100 +36,97 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte
/** /**
* Object containing chapter information * Object containing chapter information
*/ */
private var mangaChapter: MangaChapter? = null private var chapter: RecentChapter? = null
init { init {
// We need to post a Runnable to show the popup to make sure that the PopupMenu is // We need to post a Runnable to show the popup to make sure that the PopupMenu is
// correctly positioned. The reason being that the view may change position before the // correctly positioned. The reason being that the view may change position before the
// PopupMenu is shown. // PopupMenu is shown.
itemView.chapter_menu.setOnClickListener { it.post({ showPopupMenu(it) }) } view.chapter_menu.setOnClickListener { it.post({ showPopupMenu(it) }) }
} }
/** /**
* Set values of view * Set values of view
* *
* @param item item containing chapter information * @param chapter item containing chapter information
*/ */
fun onSetValues(item: MangaChapter) { fun onSetValues(chapter: RecentChapter) {
this.mangaChapter = item this.chapter = chapter
// Set chapter title // Set chapter title
itemView.chapter_title.text = item.chapter.name view.chapter_title.text = chapter.name
// Set manga title // Set manga title
itemView.manga_title.text = item.manga.title view.manga_title.text = chapter.manga.title
// Check if chapter is read and set correct color // Check if chapter is read and set correct color
if (item.chapter.read) { if (chapter.read) {
itemView.chapter_title.setTextColor(readColor) view.chapter_title.setTextColor(readColor)
itemView.manga_title.setTextColor(readColor) view.manga_title.setTextColor(readColor)
} else { } else {
itemView.chapter_title.setTextColor(unreadColor) view.chapter_title.setTextColor(unreadColor)
itemView.manga_title.setTextColor(unreadColor) view.manga_title.setTextColor(unreadColor)
} }
// Set chapter status // Set chapter status
onStatusChange(item.chapter.status) notifyStatus(chapter.status)
} }
/** /**
* Updates chapter status in view. * Updates chapter status in view.
*
* @param status download status * @param status download status
*/ */
fun onStatusChange(status: Int) { fun notifyStatus(status: Int) = with(view.download_text) {
when (status) { when (status) {
Download.QUEUE -> itemView.download_text.setText(R.string.chapter_queued) Download.QUEUE -> setText(R.string.chapter_queued)
Download.DOWNLOADING -> itemView.download_text.setText(R.string.chapter_downloading) Download.DOWNLOADING -> setText(R.string.chapter_downloading)
Download.DOWNLOADED -> itemView.download_text.setText(R.string.chapter_downloaded) Download.DOWNLOADED -> setText(R.string.chapter_downloaded)
Download.ERROR -> itemView.download_text.setText(R.string.chapter_error) Download.ERROR -> setText(R.string.chapter_error)
else -> itemView.download_text.text = "" else -> text = ""
} }
} }
/** /**
* Show pop up menu * Show pop up menu
*
* @param view view containing popup menu. * @param view view containing popup menu.
*/ */
private fun showPopupMenu(view: View) { private fun showPopupMenu(view: View) = chapter?.let { chapter ->
// Create a PopupMenu, giving it the clicked view for an anchor // Create a PopupMenu, giving it the clicked view for an anchor
val popup = PopupMenu(adapter.fragment.activity, view) val popup = PopupMenu(view.context, view)
// Inflate our menu resource into the PopupMenu's Menu // Inflate our menu resource into the PopupMenu's Menu
popup.menuInflater.inflate(R.menu.chapter_recent, popup.menu) popup.menuInflater.inflate(R.menu.chapter_recent, popup.menu)
mangaChapter?.let { // Hide download and show delete if the chapter is downloaded and
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 // Hide mark as unread when the chapter is unread
if (it.chapter.isDownloaded) { if (!chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) {
val menu = popup.menu popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
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 // Hide mark as read when the chapter is read
if (!it.chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) { if (chapter.read) {
popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false popup.menu.findItem(R.id.action_mark_as_read).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 ->
// Set a listener so we are notified if a menu item is clicked
popup.setOnMenuItemClickListener { menuItem ->
with(adapter.fragment) {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.action_download -> adapter.fragment.downloadChapter(it) R.id.action_download -> downloadChapter(chapter)
R.id.action_delete -> adapter.fragment.deleteChapter(it) R.id.action_delete -> deleteChapter(chapter)
R.id.action_mark_as_read -> adapter.fragment.markAsRead(listOf(it)) R.id.action_mark_as_read -> markAsRead(listOf(chapter))
R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(listOf(it)) R.id.action_mark_as_unread -> markAsUnread(listOf(chapter))
} }
true
} }
true
} }
// Finally show the PopupMenu // Finally show the PopupMenu

View File

@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.ui.recent_updates
import android.os.Bundle import android.os.Bundle
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaChapter import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
@ -15,34 +13,34 @@ import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.util.* import java.util.*
import javax.inject.Inject
class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() { class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
/** /**
* Used to connect to database * Used to connect to database
*/ */
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
/** /**
* Used to get settings * Used to get settings
*/ */
@Inject lateinit var preferences: PreferencesHelper val preferences: PreferencesHelper by injectLazy()
/** /**
* Used to get information from download manager * Used to get information from download manager
*/ */
@Inject lateinit var downloadManager: DownloadManager val downloadManager: DownloadManager by injectLazy()
/** /**
* Used to get source from source id * Used to get source from source id
*/ */
@Inject lateinit var sourceManager: SourceManager val sourceManager: SourceManager by injectLazy()
/** /**
* List containing chapter and manga information * List containing chapter and manga information
*/ */
private var mangaChapters: List<MangaChapter>? = null private var chapters: List<RecentChapter>? = null
/** /**
* The id of the restartable. * The id of the restartable.
@ -60,139 +58,26 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
// Used to get recent chapters // Used to get recent chapters
restartableLatestCache(GET_RECENT_CHAPTERS, restartableLatestCache(GET_RECENT_CHAPTERS,
{ getRecentChaptersObservable() }, { getRecentChaptersObservable() },
{ fragment, chapters -> { view, chapters ->
// Update adapter to show recent manga's // Update adapter to show recent manga's
fragment.onNextMangaChapters(chapters) view.onNextRecentChapters(chapters)
// Update download status
updateChapterStatus(convertToMangaChaptersList(chapters))
} }
) )
// Used to update download status // Used to update download status
startableLatestCache(CHAPTER_STATUS_CHANGES, restartableLatestCache(CHAPTER_STATUS_CHANGES,
{ getChapterStatusObs() }, { getChapterStatusObservable() },
{ recentChaptersFragment, download -> { view, download ->
// Set chapter status // Set chapter status
recentChaptersFragment.onChapterStatusChange(download) view.onChapterStatusChange(download)
}, },
{ view, error -> Timber.e(error.cause, error.message) } { view, error -> Timber.e(error.cause, error.message) }
) )
if (savedState == null) { if (savedState == null) {
// Start fetching recent chapters // Start fetching recent chapters
start(GET_RECENT_CHAPTERS) start(GET_RECENT_CHAPTERS)
} start(CHAPTER_STATUS_CHANGES)
}
/**
* 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
} }
} }
@ -200,33 +85,89 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
* Get observable containing recent chapters and date * Get observable containing recent chapters and date
* @return observable containing recent chapters and date * @return observable containing recent chapters and date
*/ */
fun getRecentChaptersObservable(): Observable<ArrayList<Any>>? { fun getRecentChaptersObservable(): Observable<ArrayList<Any>> {
// Set date for recent chapters // Set date for recent chapters
val cal = Calendar.getInstance() val cal = Calendar.getInstance().apply {
cal.time = Date() time = Date()
cal.add(Calendar.MONTH, -1) add(Calendar.MONTH, -1)
}
return db.getRecentChapters(cal.time).asRxObservable() return db.getRecentChapters(cal.time).asRxObservable()
// Convert to a list of recent chapters.
.map { mangaChapters ->
mangaChapters.map { it.toModel() }
}
.doOnNext { chapters = it }
// Group chapters by the date they were fetched on a ordered map. // Group chapters by the date they were fetched on a ordered map.
.flatMap { recentItems -> .flatMap { recentItems ->
Observable.from(recentItems) Observable.from(recentItems)
.toMultimap( .toMultimap(
{ getMapKey(it.chapter.date_fetch) }, { getMapKey(it.date_fetch) },
{ it }, { it },
{ TreeMap { d1, d2 -> d2.compareTo(d1) } }) { TreeMap { d1, d2 -> d2.compareTo(d1) } })
} }
// Add every day and all its chapters to a single list. // Add every day and all its chapters to a single list.
.map { recentItems -> .map { recentItems ->
val items = ArrayList<Any>() ArrayList<Any>().apply {
recentItems.entries.forEach { recent -> for ((key, value) in recentItems) {
items.add(recent.key) add(key)
items.addAll(recent.value) addAll(value)
}
} }
items
} }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
/**
* Returns observable containing chapter status.
* @return download object containing download progress.
*/
private fun getChapterStatusObservable(): Observable<Download> {
return downloadManager.queue.getStatusObservable()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { download -> onDownloadStatusChange(download) }
}
/**
* Converts a chapter from the database to an extended model, allowing to store new fields.
*/
private fun MangaChapter.toModel(): RecentChapter {
// Create the model object.
val model = RecentChapter(this)
// Find an active download for this chapter.
val download = downloadManager.queue.find { it.chapter.id == chapter.id }
// If there's an active download, assign it, otherwise ask the manager if the chapter is
// downloaded and assign it to the status.
if (download != null) {
model.download = download
} else {
// Get source of chapter.
val source = sourceManager.get(manga.source)!!
model.status = if (downloadManager.isChapterDownloaded(source, manga, chapter))
Download.DOWNLOADED
else
Download.NOT_DOWNLOADED
}
return model
}
/**
* Update status of chapters.
* @param download download object containing progress.
*/
private fun onDownloadStatusChange(download: Download) {
// Assign the download to the model object.
if (download.status == Download.QUEUE) {
val chapter = chapters?.find { it.id == download.chapter.id }
if (chapter != null && chapter.download == null) {
chapter.download = download
}
}
}
/** /**
* Get date as time key * Get date as time key
* @param date desired date * @param date desired date
@ -244,18 +185,17 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
/** /**
* Mark selected chapter as read * Mark selected chapter as read
* @param mangaChapters list of selected MangaChapters * @param chapters list of selected chapters
* @param read read status * @param read read status
*/ */
fun markChapterRead(mangaChapters: List<MangaChapter>, read: Boolean) { fun markChapterRead(chapters: List<RecentChapter>, read: Boolean) {
Observable.from(mangaChapters) Observable.from(chapters)
.doOnNext { mangaChapter -> .doOnNext { chapter ->
mangaChapter.chapter.read = read chapter.read = read
if (!read) { if (!read) {
mangaChapter.chapter.last_page_read = 0 chapter.last_page_read = 0
} }
} }
.map { mangaChapter -> mangaChapter.chapter }
.toList() .toList()
.flatMap { db.updateChaptersProgress(it).asRxObservable() } .flatMap { db.updateChaptersProgress(it).asRxObservable() }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@ -264,9 +204,9 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
/** /**
* Delete selected chapters * Delete selected chapters
* @param chapters list of MangaChapters * @param chapters list of chapters
*/ */
fun deleteChapters(chapters: List<MangaChapter>) { fun deleteChapters(chapters: List<RecentChapter>) {
val wasRunning = downloadManager.isRunning val wasRunning = downloadManager.isRunning
if (wasRunning) { if (wasRunning) {
DownloadService.stop(context) DownloadService.stop(context)
@ -288,11 +228,11 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
/** /**
* Download selected chapters * Download selected chapters
* @param mangaChapters [MangaChapter] that is selected * @param chapters list of recent chapters seleted.
*/ */
fun downloadChapters(mangaChapters: List<MangaChapter>) { fun downloadChapters(chapters: List<RecentChapter>) {
DownloadService.start(context) DownloadService.start(context)
Observable.from(mangaChapters) Observable.from(chapters)
.doOnNext { downloadChapter(it) } .doOnNext { downloadChapter(it) }
.subscribeOn(AndroidSchedulers.mainThread()) .subscribeOn(AndroidSchedulers.mainThread())
.subscribe() .subscribe()
@ -300,47 +240,23 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
/** /**
* Download selected chapter * Download selected chapter
* @param item chapter that is selected * @param chapter chapter that is selected
*/ */
fun downloadChapter(item: MangaChapter) { fun downloadChapter(chapter: RecentChapter) {
DownloadService.start(context) DownloadService.start(context)
downloadManager.downloadChapters(item.manga, listOf(item.chapter)) downloadManager.downloadChapters(chapter.manga, listOf(chapter))
}
/**
* Delete selected chapter
* @param item chapter that are selected
*/
fun deleteChapter(item: MangaChapter) {
val wasRunning = downloadManager.isRunning
if (wasRunning) {
DownloadService.stop(context)
}
Observable.just(item)
.doOnNext { deleteChapter(it.chapter, it.manga) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, result ->
view.onChaptersDeleted()
if (wasRunning) {
DownloadService.start(context)
}
}, { view, error ->
view.onChaptersDeletedError(error)
})
} }
/** /**
* Delete selected chapter * Delete selected chapter
* @param chapter chapter that is selected * @param chapter chapter that is selected
* @param manga manga that belongs to chapter
*/ */
private fun deleteChapter(chapter: Chapter, manga: Manga) { private fun deleteChapter(chapter: RecentChapter) {
val source = sourceManager.get(manga.source) ?: return val source = sourceManager.get(chapter.manga.source) ?: return
downloadManager.queue.del(chapter) downloadManager.queue.del(chapter)
downloadManager.deleteChapter(source, manga, chapter) downloadManager.deleteChapter(source, chapter.manga, chapter)
chapter.status = Download.NOT_DOWNLOADED chapter.status = Download.NOT_DOWNLOADED
chapter.download = null
} }
} }

View File

@ -2,11 +2,12 @@ package eu.kanade.tachiyomi.ui.recent_updates
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.text.format.DateUtils import android.text.format.DateUtils
import android.text.format.DateUtils.DAY_IN_MILLIS
import android.view.View import android.view.View
import kotlinx.android.synthetic.main.item_recent_chapter_section.view.* import kotlinx.android.synthetic.main.item_recent_chapter_section.view.*
import java.util.* import java.util.*
class SectionViewHolder(view: View) : RecyclerView.ViewHolder(view) { class SectionViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
/** /**
* Current date * Current date
@ -19,8 +20,6 @@ class SectionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
* @param date of section header * @param date of section header
*/ */
fun onSetValues(date: Date) { fun onSetValues(date: Date) {
val s = DateUtils.getRelativeTimeSpanString( view.section_text.text = DateUtils.getRelativeTimeSpanString(date.time, now, DAY_IN_MILLIS)
date.time, now, DateUtils.DAY_IN_MILLIS)
itemView.section_text.text = s
} }
} }

View File

@ -37,8 +37,8 @@ class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter)
*/ */
fun onSetValues(item: MangaChapterHistory) { fun onSetValues(item: MangaChapterHistory) {
// Retrieve objects // Retrieve objects
val manga = item.mangaChapter.manga val manga = item.manga
val chapter = item.mangaChapter.chapter val chapter = item.chapter
val history = item.history val history = item.history
// Set manga title // Set manga title
@ -71,7 +71,7 @@ class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter)
.onPositive { materialDialog, dialogAction -> .onPositive { materialDialog, dialogAction ->
// Check if user wants all chapters reset // Check if user wants all chapters reset
if (materialDialog.customView?.removeAll?.isChecked as Boolean) { if (materialDialog.customView?.removeAll?.isChecked as Boolean) {
adapter.fragment.removeAllFromHistory(manga.id) adapter.fragment.removeAllFromHistory(manga.id!!)
} else { } else {
adapter.fragment.removeFromHistory(history) adapter.fragment.removeFromHistory(history)
} }

View File

@ -9,9 +9,9 @@ import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import javax.inject.Inject
/** /**
* Presenter of RecentlyReadFragment. * Presenter of RecentlyReadFragment.
@ -30,7 +30,7 @@ class RecentlyReadPresenter : BasePresenter<RecentlyReadFragment>() {
/** /**
* Used to connect to database * Used to connect to database
*/ */
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.setting
import android.os.Bundle import android.os.Bundle
import android.support.v14.preference.PreferenceFragment import android.support.v14.preference.PreferenceFragment
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -12,22 +11,21 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import kotlinx.android.synthetic.main.toolbar.* import kotlinx.android.synthetic.main.toolbar.*
import javax.inject.Inject import uy.kohesive.injekt.injectLazy
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
@Inject lateinit var preferences: PreferencesHelper val preferences: PreferencesHelper by injectLazy()
@Inject lateinit var chapterCache: ChapterCache val chapterCache: ChapterCache by injectLazy()
@Inject lateinit var db: DatabaseHelper val db: DatabaseHelper by injectLazy()
@Inject lateinit var sourceManager: SourceManager val sourceManager: SourceManager by injectLazy()
@Inject lateinit var syncManager: MangaSyncManager val syncManager: MangaSyncManager by injectLazy()
@Inject lateinit var networkHelper: NetworkHelper val networkHelper: NetworkHelper by injectLazy()
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
setAppTheme() setAppTheme()
super.onCreate(savedState) super.onCreate(savedState)
setContentView(R.layout.activity_preferences) setContentView(R.layout.activity_preferences)
App.get(this).component.inject(this)
setupToolbar(toolbar) setupToolbar(toolbar)