Move tracking to a bottom sheet (#4364)

* Move tracking to a bottom sheet

* Give methods better names and remove unnecessary annotation

(cherry picked from commit 535abcbb8b2197faef77a6b0ba56aa4309067d4c)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackPresenter.kt
This commit is contained in:
Andreas 2021-01-31 20:43:43 +01:00 committed by Jobobby04
parent 7bb4191d41
commit 369e075ed9
12 changed files with 386 additions and 434 deletions

View File

@ -52,6 +52,7 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
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.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
@ -86,7 +87,9 @@ import eu.kanade.tachiyomi.ui.manga.info.MangaInfoButtonsAdapter
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoHeaderAdapter
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoItemAdapter
import eu.kanade.tachiyomi.ui.manga.merged.EditMergedSettingsDialog
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
@ -222,6 +225,8 @@ class MangaController :
private var isRefreshingInfo = false
private var isRefreshingChapters = false
private var trackSheet: TrackSheet? = null
// EXH -->
val smartSearchConfig: SourceController.SmartSearchConfig? = args.getParcelable(
SMART_SEARCH_CONFIG_EXTRA
@ -346,6 +351,8 @@ class MangaController :
}
}
trackSheet = TrackSheet(this, manga!!)
presenter.redirectFlow
.onEach { redirect ->
XLog.d("Redirecting to updated manga (manga.id: %s, manga.title: %s, update: %s)!", redirect.manga.id, redirect.manga.title, redirect.update)
@ -659,7 +666,7 @@ class MangaController :
}
fun onTrackingClick() {
router.pushController(TrackController(manga).withFadeTransaction())
trackSheet?.show()
}
private fun addToLibrary(manga: Manga) {
@ -1445,6 +1452,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"

View File

@ -13,12 +13,14 @@ 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.library.CustomMangaManager
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.SourceManager
@ -29,6 +31,7 @@ import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.source.online.all.MergedSource
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
@ -39,6 +42,7 @@ import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.updateCoverLastModified
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
import exh.debug.DebugToggles
@ -54,13 +58,17 @@ import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource
import exh.source.isEhBasedSource
import exh.source.mangaDexSourceIds
import exh.util.shouldDeleteChapters
import exh.util.trimOrNull
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
import rx.Observable
import rx.Single
@ -120,6 +128,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
// EXH -->
private val customMangaManager: CustomMangaManager by injectLazy()
@ -239,6 +256,8 @@ class MangaPresenter(
)
// Chapters list - end
fetchTrackers()
}
// Manga info - start
@ -1082,4 +1101,152 @@ 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())
// SY -->
.map { trackItems ->
val mdTrack = trackItems.firstOrNull { it.service.id == TrackManager.MDLIST }
if (manga.source in mangaDexSourceIds) {
when {
mdTrack == null -> {
trackItems
}
mdTrack.track == null -> {
trackItems - mdTrack + createMdListTrack()
}
else -> trackItems
}
} else mdTrack?.let { trackItems - it } ?: trackItems
}
// SY <--
.doOnNext { _trackList = it }
.subscribeLatestCache(MangaController::onNextTrackers)
}
// SY -->
private fun createMdListTrack(): TrackItem {
val track = trackManager.mdList.createInitialTracker(manga)
track.id = db.insertTrack(track).executeAsBlocking().insertedId()
return TrackItem(track, trackManager.mdList)
}
// SY <--
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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,208 +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.TrackManager
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
if (presenter.needsRefresh) {
presenter.needsRefresh = false
presenter.refresh()
}
}
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
// SY --> Kill search for now until cesco puts MdList into stable
if (item.service.id == TrackManager.MDLIST) return
// SY <--
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"
}
}

View File

@ -1,194 +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 exh.source.mangaDexSourceIds
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
// SY -->
var needsRefresh = false
// SY <--
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())
// SY -->
.map { trackItems ->
val mdTrack = trackItems.firstOrNull { it.service.id == TrackManager.MDLIST }
if (manga.source in mangaDexSourceIds) {
when {
mdTrack == null -> {
trackItems
}
mdTrack.track == null -> {
needsRefresh = true
trackItems - mdTrack + createMdListTrack()
}
else -> trackItems
}
} else mdTrack?.let { trackItems - it } ?: trackItems
}
// SY <--
.doOnNext { trackList = it }
.subscribeLatestCache(TrackController::onNextTrackings)
}
// SY -->
private fun createMdListTrack(): TrackItem {
val track = trackManager.mdList.createInitialTracker(manga)
track.id = db.insertTrack(track).executeAsBlocking().insertedId()
return TrackItem(track, trackManager.mdList)
}
// SY <--
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)
}
}

View File

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

View File

@ -0,0 +1,148 @@
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.data.track.TrackManager
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
// SY --> Kill search for now until cesco puts MdList into stable
if (item.service.id == TrackManager.MDLIST) return
// SY <--
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"
}
}

View File

@ -5,20 +5,12 @@
android:layout_height="match_parent"
android:orientation="vertical">
<eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout
android:id="@+id/swipe_refresh"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_recycler"
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"
tools:listitem="@layout/track_item" />
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>
android:clipToPadding="false"
tools:listitem="@layout/track_item" />
</LinearLayout>