Move tracking to a bottom sheet (#4364)
* Move tracking to a bottom sheet * Give methods better names and remove unnecessary annotation
This commit is contained in:
parent
c34b548a3e
commit
535abcbb8b
@ -37,6 +37,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.databinding.MangaControllerBinding
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
@ -62,7 +63,9 @@ import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomChaptersDialog
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.MangaChaptersHeaderAdapter
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChaptersAdapter
|
||||
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoHeaderAdapter
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackController
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackSearchDialog
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackSheet
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.recent.history.HistoryController
|
||||
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
|
||||
@ -160,6 +163,8 @@ class MangaController :
|
||||
private var isRefreshingInfo = false
|
||||
private var isRefreshingChapters = false
|
||||
|
||||
private var trackSheet: TrackSheet? = null
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
@ -246,6 +251,8 @@ class MangaController :
|
||||
}
|
||||
}
|
||||
|
||||
trackSheet = TrackSheet(this, manga!!)
|
||||
|
||||
updateFilterIconState()
|
||||
}
|
||||
|
||||
@ -461,7 +468,7 @@ class MangaController :
|
||||
}
|
||||
|
||||
fun onTrackingClick() {
|
||||
router.pushController(TrackController(manga).withFadeTransaction())
|
||||
trackSheet?.show()
|
||||
}
|
||||
|
||||
private fun addToLibrary(manga: Manga) {
|
||||
@ -1030,6 +1037,35 @@ class MangaController :
|
||||
|
||||
// Chapters list - end
|
||||
|
||||
// Tracker sheet - start
|
||||
fun onNextTrackers(trackers: List<TrackItem>) {
|
||||
trackSheet?.onNextTrackers(trackers)
|
||||
}
|
||||
|
||||
fun onTrackingRefreshDone() {
|
||||
}
|
||||
|
||||
fun onTrackingRefreshError(error: Throwable) {
|
||||
Timber.e(error)
|
||||
activity?.toast(error.message)
|
||||
}
|
||||
|
||||
fun onTrackingSearchResults(results: List<TrackSearch>) {
|
||||
getTrackingSearchDialog()?.onSearchResults(results)
|
||||
}
|
||||
|
||||
fun onTrackingSearchResultsError(error: Throwable) {
|
||||
Timber.e(error)
|
||||
activity?.toast(error.message)
|
||||
getTrackingSearchDialog()?.onSearchResultsError()
|
||||
}
|
||||
|
||||
private fun getTrackingSearchDialog(): TrackSearchDialog? {
|
||||
return trackSheet?.getSearchDialog()
|
||||
}
|
||||
|
||||
// Tracker sheet - end
|
||||
|
||||
companion object {
|
||||
const val FROM_SOURCE_EXTRA = "from_source"
|
||||
const val MANGA_EXTRA = "manga"
|
||||
|
@ -10,17 +10,20 @@ import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.toSChapter
|
||||
import eu.kanade.tachiyomi.source.model.toSManga
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||
import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import eu.kanade.tachiyomi.util.isLocal
|
||||
@ -29,9 +32,13 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.prepUpdateCover
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.updateCoverLastModified
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
@ -86,6 +93,15 @@ class MangaPresenter(
|
||||
private var observeDownloadsStatusSubscription: Subscription? = null
|
||||
private var observeDownloadsPageSubscription: Subscription? = null
|
||||
|
||||
private var _trackList: List<TrackItem> = emptyList()
|
||||
val trackList get() = _trackList
|
||||
|
||||
private val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
|
||||
|
||||
private var trackSubscription: Subscription? = null
|
||||
private var searchJob: Job? = null
|
||||
private var refreshJob: Job? = null
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
@ -134,6 +150,8 @@ class MangaPresenter(
|
||||
)
|
||||
|
||||
// Chapters list - end
|
||||
|
||||
fetchTrackers()
|
||||
}
|
||||
|
||||
// Manga info - start
|
||||
@ -645,4 +663,128 @@ class MangaPresenter(
|
||||
}
|
||||
|
||||
// Chapters list - end
|
||||
|
||||
// Track sheet - start
|
||||
|
||||
private fun fetchTrackers() {
|
||||
trackSubscription?.let { remove(it) }
|
||||
trackSubscription = db.getTracks(manga)
|
||||
.asRxObservable()
|
||||
.map { tracks ->
|
||||
loggedServices.map { service ->
|
||||
TrackItem(tracks.find { it.sync_id == service.id }, service)
|
||||
}
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { _trackList = it }
|
||||
.subscribeLatestCache(MangaController::onNextTrackers)
|
||||
}
|
||||
|
||||
fun trackingRefresh() {
|
||||
refreshJob?.cancel()
|
||||
refreshJob = launchIO {
|
||||
supervisorScope {
|
||||
try {
|
||||
trackList
|
||||
.filter { it.track != null }
|
||||
.map {
|
||||
async {
|
||||
val track = it.service.refresh(it.track!!)
|
||||
db.insertTrack(track).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
|
||||
withUIContext { view?.onTrackingRefreshDone() }
|
||||
} catch (e: Throwable) {
|
||||
withUIContext { view?.onTrackingRefreshError(e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun trackingSearch(query: String, service: TrackService) {
|
||||
searchJob?.cancel()
|
||||
searchJob = launchIO {
|
||||
try {
|
||||
val results = service.search(query)
|
||||
withUIContext { view?.onTrackingSearchResults(results) }
|
||||
} catch (e: Throwable) {
|
||||
withUIContext { view?.onTrackingSearchResultsError(e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerTracking(item: Track?, service: TrackService) {
|
||||
if (item != null) {
|
||||
item.manga_id = manga.id!!
|
||||
launchIO {
|
||||
try {
|
||||
service.bind(item)
|
||||
db.insertTrack(item).executeAsBlocking()
|
||||
} catch (e: Throwable) {
|
||||
withUIContext { view?.applicationContext?.toast(e.message) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unregisterTracking(service)
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterTracking(service: TrackService) {
|
||||
db.deleteTrackForManga(manga, service).executeAsBlocking()
|
||||
}
|
||||
|
||||
private fun updateRemote(track: Track, service: TrackService) {
|
||||
launchIO {
|
||||
try {
|
||||
service.update(track)
|
||||
db.insertTrack(track).executeAsBlocking()
|
||||
withUIContext { view?.onTrackingRefreshDone() }
|
||||
} catch (e: Throwable) {
|
||||
withUIContext { view?.onTrackingRefreshError(e) }
|
||||
|
||||
// Restart on error to set old values
|
||||
fetchTrackers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setTrackerStatus(item: TrackItem, index: Int) {
|
||||
val track = item.track!!
|
||||
track.status = item.service.getStatusList()[index]
|
||||
if (track.status == item.service.getCompletionStatus() && track.total_chapters != 0) {
|
||||
track.last_chapter_read = track.total_chapters
|
||||
}
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setTrackerScore(item: TrackItem, index: Int) {
|
||||
val track = item.track!!
|
||||
track.score = item.service.indexToScore(index)
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setTrackerLastChapterRead(item: TrackItem, chapterNumber: Int) {
|
||||
val track = item.track!!
|
||||
track.last_chapter_read = chapterNumber
|
||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||
track.status = item.service.getCompletionStatus()
|
||||
}
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setTrackerStartDate(item: TrackItem, date: Long) {
|
||||
val track = item.track!!
|
||||
track.started_reading_date = date
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setTrackerFinishDate(item: TrackItem, date: Long) {
|
||||
val track = item.track!!
|
||||
track.finished_reading_date = date
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
// Track sheet - end
|
||||
}
|
||||
|
@ -16,14 +16,17 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class SetTrackChaptersDialog<T> : DialogController
|
||||
where T : Controller, T : SetTrackChaptersDialog.Listener {
|
||||
where T : Controller {
|
||||
|
||||
private val item: TrackItem
|
||||
|
||||
constructor(target: T, item: TrackItem) : super(
|
||||
private lateinit var listener: Listener
|
||||
|
||||
constructor(target: T, listener: Listener, item: TrackItem) : super(
|
||||
bundleOf(KEY_ITEM_TRACK to item.track)
|
||||
) {
|
||||
targetController = target
|
||||
this.listener = listener
|
||||
this.item = item
|
||||
}
|
||||
|
||||
@ -46,7 +49,7 @@ class SetTrackChaptersDialog<T> : DialogController
|
||||
val np: NumberPicker = view.findViewById(R.id.chapters_picker)
|
||||
np.clearFocus()
|
||||
|
||||
(targetController as? Listener)?.setChaptersRead(item, np.value)
|
||||
listener.setChaptersRead(item, np.value)
|
||||
}
|
||||
.negativeButton(android.R.string.cancel)
|
||||
|
||||
|
@ -15,16 +15,19 @@ import uy.kohesive.injekt.api.get
|
||||
import java.util.Calendar
|
||||
|
||||
class SetTrackReadingDatesDialog<T> : DialogController
|
||||
where T : Controller, T : SetTrackReadingDatesDialog.Listener {
|
||||
where T : Controller {
|
||||
|
||||
private val item: TrackItem
|
||||
|
||||
private val dateToUpdate: ReadingDate
|
||||
|
||||
constructor(target: T, dateToUpdate: ReadingDate, item: TrackItem) : super(
|
||||
private lateinit var listener: Listener
|
||||
|
||||
constructor(target: T, listener: Listener, dateToUpdate: ReadingDate, item: TrackItem) : super(
|
||||
bundleOf(KEY_ITEM_TRACK to item.track)
|
||||
) {
|
||||
targetController = target
|
||||
this.listener = listener
|
||||
this.item = item
|
||||
this.dateToUpdate = dateToUpdate
|
||||
}
|
||||
@ -38,8 +41,6 @@ class SetTrackReadingDatesDialog<T> : DialogController
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
val listener = (targetController as? Listener)
|
||||
|
||||
return MaterialDialog(activity!!)
|
||||
.title(
|
||||
when (dateToUpdate) {
|
||||
|
@ -16,14 +16,17 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class SetTrackScoreDialog<T> : DialogController
|
||||
where T : Controller, T : SetTrackScoreDialog.Listener {
|
||||
where T : Controller {
|
||||
|
||||
private val item: TrackItem
|
||||
|
||||
constructor(target: T, item: TrackItem) : super(
|
||||
private lateinit var listener: Listener
|
||||
|
||||
constructor(target: T, listener: Listener, item: TrackItem) : super(
|
||||
bundleOf(KEY_ITEM_TRACK to item.track)
|
||||
) {
|
||||
targetController = target
|
||||
this.listener = listener
|
||||
this.item = item
|
||||
}
|
||||
|
||||
@ -46,7 +49,7 @@ class SetTrackScoreDialog<T> : DialogController
|
||||
val np: NumberPicker = view.findViewById(R.id.score_picker)
|
||||
np.clearFocus()
|
||||
|
||||
(targetController as? Listener)?.setScore(item, np.value)
|
||||
listener.setScore(item, np.value)
|
||||
}
|
||||
.negativeButton(android.R.string.cancel)
|
||||
|
||||
|
@ -14,14 +14,17 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class SetTrackStatusDialog<T> : DialogController
|
||||
where T : Controller, T : SetTrackStatusDialog.Listener {
|
||||
where T : Controller {
|
||||
|
||||
private val item: TrackItem
|
||||
|
||||
constructor(target: T, item: TrackItem) : super(
|
||||
private lateinit var listener: Listener
|
||||
|
||||
constructor(target: T, listener: Listener, item: TrackItem) : super(
|
||||
bundleOf(KEY_ITEM_TRACK to item.track)
|
||||
) {
|
||||
targetController = target
|
||||
this.listener = listener
|
||||
this.item = item
|
||||
}
|
||||
|
||||
@ -46,7 +49,7 @@ class SetTrackStatusDialog<T> : DialogController
|
||||
initialSelection = selectedIndex,
|
||||
waitForPositiveButton = false
|
||||
) { dialog, position, _ ->
|
||||
(targetController as? Listener)?.setStatus(item, position)
|
||||
listener.setStatus(item, position)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.kanade.tachiyomi.databinding.TrackItemBinding
|
||||
|
||||
class TrackAdapter(controller: TrackController) : RecyclerView.Adapter<TrackHolder>() {
|
||||
class TrackAdapter(listener: OnClickListener) : RecyclerView.Adapter<TrackHolder>() {
|
||||
|
||||
private lateinit var binding: TrackItemBinding
|
||||
|
||||
@ -17,7 +17,7 @@ class TrackAdapter(controller: TrackController) : RecyclerView.Adapter<TrackHold
|
||||
}
|
||||
}
|
||||
|
||||
val rowClickListener: OnClickListener = controller
|
||||
val rowClickListener: OnClickListener = listener
|
||||
|
||||
fun getItem(index: Int): TrackItem? {
|
||||
return items.getOrNull(index)
|
||||
|
@ -1,200 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.databinding.TrackControllerBinding
|
||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class TrackController :
|
||||
NucleusController<TrackControllerBinding, TrackPresenter>,
|
||||
TrackAdapter.OnClickListener,
|
||||
SetTrackStatusDialog.Listener,
|
||||
SetTrackChaptersDialog.Listener,
|
||||
SetTrackScoreDialog.Listener,
|
||||
SetTrackReadingDatesDialog.Listener {
|
||||
|
||||
constructor(manga: Manga?) : super(
|
||||
bundleOf(MANGA_EXTRA to (manga?.id ?: 0))
|
||||
) {
|
||||
this.manga = manga
|
||||
}
|
||||
|
||||
constructor(mangaId: Long) : this(
|
||||
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking()
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA))
|
||||
|
||||
var manga: Manga? = null
|
||||
private set
|
||||
|
||||
private var adapter: TrackAdapter? = null
|
||||
|
||||
init {
|
||||
// There's no menu, but this avoids a bug when coming from the catalogue, where the menu
|
||||
// disappears if the searchview is expanded
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun getTitle(): String? {
|
||||
return manga?.title
|
||||
}
|
||||
|
||||
override fun createPresenter(): TrackPresenter {
|
||||
return TrackPresenter(manga!!)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = TrackControllerBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
if (manga == null) return
|
||||
|
||||
adapter = TrackAdapter(this)
|
||||
binding.trackRecycler.layoutManager = LinearLayoutManager(view.context)
|
||||
binding.trackRecycler.adapter = adapter
|
||||
binding.swipeRefresh.isEnabled = false
|
||||
binding.swipeRefresh.refreshes()
|
||||
.onEach { presenter.refresh() }
|
||||
.launchIn(viewScope)
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
adapter = null
|
||||
super.onDestroyView(view)
|
||||
}
|
||||
|
||||
fun onNextTrackings(trackings: List<TrackItem>) {
|
||||
val atLeastOneLink = trackings.any { it.track != null }
|
||||
adapter?.items = trackings
|
||||
binding.swipeRefresh.isEnabled = atLeastOneLink
|
||||
}
|
||||
|
||||
fun onSearchResults(results: List<TrackSearch>) {
|
||||
getSearchDialog()?.onSearchResults(results)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun onSearchResultsError(error: Throwable) {
|
||||
Timber.e(error)
|
||||
activity?.toast(error.message)
|
||||
getSearchDialog()?.onSearchResultsError()
|
||||
}
|
||||
|
||||
private fun getSearchDialog(): TrackSearchDialog? {
|
||||
return router.getControllerWithTag(TAG_SEARCH_CONTROLLER) as? TrackSearchDialog
|
||||
}
|
||||
|
||||
fun onRefreshDone() {
|
||||
binding.swipeRefresh.isRefreshing = false
|
||||
}
|
||||
|
||||
fun onRefreshError(error: Throwable) {
|
||||
binding.swipeRefresh.isRefreshing = false
|
||||
activity?.toast(error.message)
|
||||
}
|
||||
|
||||
override fun onLogoClick(position: Int) {
|
||||
val track = adapter?.getItem(position)?.track ?: return
|
||||
|
||||
if (track.tracking_url.isNotBlank()) {
|
||||
activity?.startActivity(Intent(Intent.ACTION_VIEW, track.tracking_url.toUri()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSetClick(position: Int) {
|
||||
val item = adapter?.getItem(position) ?: return
|
||||
TrackSearchDialog(this, item.service).showDialog(router, TAG_SEARCH_CONTROLLER)
|
||||
}
|
||||
|
||||
override fun onTitleLongClick(position: Int) {
|
||||
adapter?.getItem(position)?.track?.title?.let {
|
||||
activity?.copyToClipboard(it, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStatusClick(position: Int) {
|
||||
val item = adapter?.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackStatusDialog(this, item).showDialog(router)
|
||||
}
|
||||
|
||||
override fun onChaptersClick(position: Int) {
|
||||
val item = adapter?.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackChaptersDialog(this, item).showDialog(router)
|
||||
}
|
||||
|
||||
override fun onScoreClick(position: Int) {
|
||||
val item = adapter?.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackScoreDialog(this, item).showDialog(router)
|
||||
}
|
||||
|
||||
override fun onStartDateClick(position: Int) {
|
||||
val item = adapter?.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackReadingDatesDialog(this, SetTrackReadingDatesDialog.ReadingDate.Start, item).showDialog(router)
|
||||
}
|
||||
|
||||
override fun onFinishDateClick(position: Int) {
|
||||
val item = adapter?.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackReadingDatesDialog(this, SetTrackReadingDatesDialog.ReadingDate.Finish, item).showDialog(router)
|
||||
}
|
||||
|
||||
override fun setStatus(item: TrackItem, selection: Int) {
|
||||
presenter.setStatus(item, selection)
|
||||
binding.swipeRefresh.isRefreshing = true
|
||||
}
|
||||
|
||||
override fun setScore(item: TrackItem, score: Int) {
|
||||
presenter.setScore(item, score)
|
||||
binding.swipeRefresh.isRefreshing = true
|
||||
}
|
||||
|
||||
override fun setChaptersRead(item: TrackItem, chaptersRead: Int) {
|
||||
presenter.setLastChapterRead(item, chaptersRead)
|
||||
binding.swipeRefresh.isRefreshing = true
|
||||
}
|
||||
|
||||
override fun setReadingDate(item: TrackItem, type: SetTrackReadingDatesDialog.ReadingDate, date: Long) {
|
||||
when (type) {
|
||||
SetTrackReadingDatesDialog.ReadingDate.Start -> presenter.setStartDate(item, date)
|
||||
SetTrackReadingDatesDialog.ReadingDate.Finish -> presenter.setFinishDate(item, date)
|
||||
}
|
||||
binding.swipeRefresh.isRefreshing = true
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MANGA_EXTRA = "manga"
|
||||
const val TAG_SEARCH_CONTROLLER = "track_search_controller"
|
||||
}
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import android.os.Bundle
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class TrackPresenter(
|
||||
val manga: Manga,
|
||||
preferences: PreferencesHelper = Injekt.get(),
|
||||
private val db: DatabaseHelper = Injekt.get(),
|
||||
private val trackManager: TrackManager = Injekt.get()
|
||||
) : BasePresenter<TrackController>() {
|
||||
|
||||
private val context = preferences.context
|
||||
|
||||
private var trackList: List<TrackItem> = emptyList()
|
||||
|
||||
private val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
|
||||
|
||||
private var trackSubscription: Subscription? = null
|
||||
private var searchJob: Job? = null
|
||||
private var refreshJob: Job? = null
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
fetchTrackings()
|
||||
}
|
||||
|
||||
private fun fetchTrackings() {
|
||||
trackSubscription?.let { remove(it) }
|
||||
trackSubscription = db.getTracks(manga)
|
||||
.asRxObservable()
|
||||
.map { tracks ->
|
||||
loggedServices.map { service ->
|
||||
TrackItem(tracks.find { it.sync_id == service.id }, service)
|
||||
}
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { trackList = it }
|
||||
.subscribeLatestCache(TrackController::onNextTrackings)
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
refreshJob?.cancel()
|
||||
refreshJob = launchIO {
|
||||
supervisorScope {
|
||||
try {
|
||||
trackList
|
||||
.filter { it.track != null }
|
||||
.map {
|
||||
async {
|
||||
val track = it.service.refresh(it.track!!)
|
||||
db.insertTrack(track).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
|
||||
withUIContext { view?.onRefreshDone() }
|
||||
} catch (e: Throwable) {
|
||||
withUIContext { view?.onRefreshError(e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun search(query: String, service: TrackService) {
|
||||
searchJob?.cancel()
|
||||
searchJob = launchIO {
|
||||
try {
|
||||
val results = service.search(query)
|
||||
withUIContext { view?.onSearchResults(results) }
|
||||
} catch (e: Throwable) {
|
||||
withUIContext { view?.onSearchResultsError(e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerTracking(item: Track?, service: TrackService) {
|
||||
if (item != null) {
|
||||
item.manga_id = manga.id!!
|
||||
launchIO {
|
||||
try {
|
||||
service.bind(item)
|
||||
db.insertTrack(item).executeAsBlocking()
|
||||
} catch (e: Throwable) {
|
||||
withUIContext { context.toast(e.message) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unregisterTracking(service)
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterTracking(service: TrackService) {
|
||||
db.deleteTrackForManga(manga, service).executeAsBlocking()
|
||||
}
|
||||
|
||||
private fun updateRemote(track: Track, service: TrackService) {
|
||||
launchIO {
|
||||
try {
|
||||
service.update(track)
|
||||
db.insertTrack(track).executeAsBlocking()
|
||||
withUIContext { view?.onRefreshDone() }
|
||||
} catch (e: Throwable) {
|
||||
withUIContext { view?.onRefreshError(e) }
|
||||
|
||||
// Restart on error to set old values
|
||||
fetchTrackings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setStatus(item: TrackItem, index: Int) {
|
||||
val track = item.track!!
|
||||
track.status = item.service.getStatusList()[index]
|
||||
if (track.status == item.service.getCompletionStatus() && track.total_chapters != 0) {
|
||||
track.last_chapter_read = track.total_chapters
|
||||
}
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setScore(item: TrackItem, index: Int) {
|
||||
val track = item.track!!
|
||||
track.score = item.service.indexToScore(index)
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setLastChapterRead(item: TrackItem, chapterNumber: Int) {
|
||||
val track = item.track!!
|
||||
track.last_chapter_read = chapterNumber
|
||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||
track.status = item.service.getCompletionStatus()
|
||||
}
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setStartDate(item: TrackItem, date: Long) {
|
||||
val track = item.track!!
|
||||
track.started_reading_date = date
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setFinishDate(item: TrackItem, date: Long) {
|
||||
val track = item.track!!
|
||||
track.finished_reading_date = date
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.databinding.TrackSearchDialogBinding
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@ -36,9 +37,9 @@ class TrackSearchDialog : DialogController {
|
||||
private val service: TrackService
|
||||
|
||||
private val trackController
|
||||
get() = targetController as TrackController
|
||||
get() = targetController as MangaController
|
||||
|
||||
constructor(target: TrackController, service: TrackService) : super(
|
||||
constructor(target: MangaController, service: TrackService) : super(
|
||||
bundleOf(KEY_SERVICE to service.id)
|
||||
) {
|
||||
targetController = target
|
||||
@ -105,7 +106,7 @@ class TrackSearchDialog : DialogController {
|
||||
val binding = binding ?: return
|
||||
binding.progress.isVisible = true
|
||||
binding.trackSearchList.isVisible = false
|
||||
trackController.presenter.search(query, service)
|
||||
trackController.presenter.trackingSearch(query, service)
|
||||
}
|
||||
|
||||
fun onSearchResults(results: List<TrackSearch>) {
|
||||
|
@ -0,0 +1,144 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.net.toUri
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.databinding.TrackControllerBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
||||
|
||||
class TrackSheet(
|
||||
val controller: MangaController,
|
||||
val manga: Manga
|
||||
) : BaseBottomSheetDialog(controller.activity!!),
|
||||
TrackAdapter.OnClickListener,
|
||||
SetTrackStatusDialog.Listener,
|
||||
SetTrackChaptersDialog.Listener,
|
||||
SetTrackScoreDialog.Listener,
|
||||
SetTrackReadingDatesDialog.Listener {
|
||||
|
||||
private lateinit var binding: TrackControllerBinding
|
||||
|
||||
private lateinit var sheetBehavior: BottomSheetBehavior<*>
|
||||
|
||||
private lateinit var adapter: TrackAdapter
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = TrackControllerBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
adapter = TrackAdapter(this)
|
||||
binding.trackRecycler.layoutManager = LinearLayoutManager(context)
|
||||
binding.trackRecycler.adapter = adapter
|
||||
|
||||
sheetBehavior = BottomSheetBehavior.from(binding.root.parent as ViewGroup)
|
||||
|
||||
adapter.items = controller.presenter.trackList
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
sheetBehavior.skipCollapsed = true
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
controller.presenter.trackingRefresh()
|
||||
sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
|
||||
fun onNextTrackers(trackers: List<TrackItem>) {
|
||||
if (this::adapter.isInitialized) {
|
||||
adapter.items = trackers
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLogoClick(position: Int) {
|
||||
val track = adapter.getItem(position)?.track ?: return
|
||||
|
||||
if (track.tracking_url.isNotBlank()) {
|
||||
controller.activity?.startActivity(Intent(Intent.ACTION_VIEW, track.tracking_url.toUri()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSetClick(position: Int) {
|
||||
val item = adapter.getItem(position) ?: return
|
||||
TrackSearchDialog(controller, item.service).showDialog(controller.router, TAG_SEARCH_CONTROLLER)
|
||||
}
|
||||
|
||||
override fun onTitleLongClick(position: Int) {
|
||||
adapter.getItem(position)?.track?.title?.let {
|
||||
controller.activity?.copyToClipboard(it, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStatusClick(position: Int) {
|
||||
val item = adapter.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackStatusDialog(controller, this, item).showDialog(controller.router)
|
||||
}
|
||||
|
||||
override fun onChaptersClick(position: Int) {
|
||||
val item = adapter.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackChaptersDialog(controller, this, item).showDialog(controller.router)
|
||||
}
|
||||
|
||||
override fun onScoreClick(position: Int) {
|
||||
val item = adapter.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackScoreDialog(controller, this, item).showDialog(controller.router)
|
||||
}
|
||||
|
||||
override fun onStartDateClick(position: Int) {
|
||||
val item = adapter.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackReadingDatesDialog(controller, this, SetTrackReadingDatesDialog.ReadingDate.Start, item).showDialog(controller.router)
|
||||
}
|
||||
|
||||
override fun onFinishDateClick(position: Int) {
|
||||
val item = adapter.getItem(position) ?: return
|
||||
if (item.track == null) return
|
||||
|
||||
SetTrackReadingDatesDialog(controller, this, SetTrackReadingDatesDialog.ReadingDate.Finish, item).showDialog(controller.router)
|
||||
}
|
||||
|
||||
override fun setStatus(item: TrackItem, selection: Int) {
|
||||
controller.presenter.setTrackerStatus(item, selection)
|
||||
}
|
||||
|
||||
override fun setChaptersRead(item: TrackItem, chaptersRead: Int) {
|
||||
controller.presenter.setTrackerLastChapterRead(item, chaptersRead)
|
||||
}
|
||||
|
||||
override fun setScore(item: TrackItem, score: Int) {
|
||||
controller.presenter.setTrackerScore(item, score)
|
||||
}
|
||||
|
||||
override fun setReadingDate(item: TrackItem, type: SetTrackReadingDatesDialog.ReadingDate, date: Long) {
|
||||
when (type) {
|
||||
SetTrackReadingDatesDialog.ReadingDate.Start -> controller.presenter.setTrackerStartDate(item, date)
|
||||
SetTrackReadingDatesDialog.ReadingDate.Finish -> controller.presenter.setTrackerFinishDate(item, date)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSearchDialog(): TrackSearchDialog? {
|
||||
return controller.router.getControllerWithTag(TAG_SEARCH_CONTROLLER) as? TrackSearchDialog
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TAG_SEARCH_CONTROLLER = "track_search_controller"
|
||||
}
|
||||
}
|
@ -5,20 +5,12 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/track_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="4dp"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/track_item" />
|
||||
|
||||
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
Loading…
Reference in New Issue
Block a user