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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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