mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-13 12:38:58 +01:00
Experimental Anilist and Kitsu support (#586)
* Tracking tab with anilist support * Rename MangaSync to Track * Rename variables and methods to track * Kitsu implementation * Variables refactoring * Travis fix?
This commit is contained in:
@@ -3,15 +3,18 @@ package eu.kanade.tachiyomi.ui.manga
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.graphics.drawable.VectorDrawableCompat
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.support.v4.app.FragmentPagerAdapter
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersFragment
|
||||
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoFragment
|
||||
import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListFragment
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackFragment
|
||||
import eu.kanade.tachiyomi.util.SharedData
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import kotlinx.android.synthetic.main.activity_manga.*
|
||||
@@ -28,7 +31,7 @@ class MangaActivity : BaseRxActivity<MangaPresenter>() {
|
||||
const val FROM_LAUNCHER_EXTRA = "from_launcher"
|
||||
const val INFO_FRAGMENT = 0
|
||||
const val CHAPTERS_FRAGMENT = 1
|
||||
const val MYANIMELIST_FRAGMENT = 2
|
||||
const val TRACK_FRAGMENT = 2
|
||||
|
||||
fun newIntent(context: Context, manga: Manga, fromCatalogue: Boolean = false): Intent {
|
||||
SharedData.put(MangaEvent(manga))
|
||||
@@ -71,6 +74,7 @@ class MangaActivity : BaseRxActivity<MangaPresenter>() {
|
||||
fromCatalogue = intent.getBooleanExtra(FROM_CATALOGUE_EXTRA, false)
|
||||
|
||||
adapter = MangaDetailAdapter(supportFragmentManager, this)
|
||||
view_pager.offscreenPageLimit = 3
|
||||
view_pager.adapter = adapter
|
||||
|
||||
tabs.setupWithViewPager(view_pager)
|
||||
@@ -85,33 +89,50 @@ class MangaActivity : BaseRxActivity<MangaPresenter>() {
|
||||
setToolbarTitle(manga.title)
|
||||
}
|
||||
|
||||
internal class MangaDetailAdapter(fm: FragmentManager, activity: MangaActivity) : FragmentPagerAdapter(fm) {
|
||||
fun setTrackingIcon(visible: Boolean) {
|
||||
val tab = tabs.getTabAt(TRACK_FRAGMENT) ?: return
|
||||
val drawable = if (visible)
|
||||
VectorDrawableCompat.create(resources, R.drawable.ic_done_white_18dp, null)
|
||||
else null
|
||||
|
||||
private var pageCount: Int = 0
|
||||
private val tabTitles = arrayOf(activity.getString(R.string.manga_detail_tab),
|
||||
activity.getString(R.string.manga_chapters_tab), "MAL")
|
||||
// I had no choice but to use reflection...
|
||||
val field = tab.javaClass.getDeclaredField("mView").apply { isAccessible = true }
|
||||
val view = field.get(tab) as LinearLayout
|
||||
val textView = view.getChildAt(1) as TextView
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null)
|
||||
textView.compoundDrawablePadding = 4
|
||||
}
|
||||
|
||||
private class MangaDetailAdapter(fm: FragmentManager, activity: MangaActivity)
|
||||
: FragmentPagerAdapter(fm) {
|
||||
|
||||
private var tabCount = 2
|
||||
|
||||
private val tabTitles = listOf(
|
||||
R.string.manga_detail_tab,
|
||||
R.string.manga_chapters_tab,
|
||||
R.string.manga_tracking_tab)
|
||||
.map { activity.getString(it) }
|
||||
|
||||
init {
|
||||
pageCount = 2
|
||||
if (!activity.fromCatalogue && activity.presenter.syncManager.myAnimeList.isLogged)
|
||||
pageCount++
|
||||
if (!activity.fromCatalogue && activity.presenter.trackManager.hasLoggedServices())
|
||||
tabCount++
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return pageCount
|
||||
return tabCount
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): Fragment? {
|
||||
override fun getItem(position: Int): Fragment {
|
||||
when (position) {
|
||||
INFO_FRAGMENT -> return MangaInfoFragment.newInstance()
|
||||
CHAPTERS_FRAGMENT -> return ChaptersFragment.newInstance()
|
||||
MYANIMELIST_FRAGMENT -> return MyAnimeListFragment.newInstance()
|
||||
else -> return null
|
||||
TRACK_FRAGMENT -> return TrackFragment.newInstance()
|
||||
else -> throw Exception("Unknown position")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence {
|
||||
// Generate title based on item position
|
||||
return tabTitles[position]
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.manga
|
||||
import android.os.Bundle
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent
|
||||
import eu.kanade.tachiyomi.util.SharedData
|
||||
@@ -22,9 +22,9 @@ class MangaPresenter : BasePresenter<MangaActivity>() {
|
||||
val db: DatabaseHelper by injectLazy()
|
||||
|
||||
/**
|
||||
* Manga sync manager.
|
||||
* Tracking manager.
|
||||
*/
|
||||
val syncManager: MangaSyncManager by injectLazy()
|
||||
val trackManager: TrackManager by injectLazy()
|
||||
|
||||
/**
|
||||
* Manga associated with this instance.
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.myanimelist
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.view.View
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
|
||||
import kotlinx.android.synthetic.main.dialog_myanimelist_search.view.*
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.subjects.PublishSubject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class MyAnimeListDialogFragment : DialogFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(): MyAnimeListDialogFragment {
|
||||
return MyAnimeListDialogFragment()
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var v: View
|
||||
|
||||
lateinit var adapter: MyAnimeListSearchAdapter
|
||||
private set
|
||||
|
||||
lateinit var querySubject: PublishSubject<String>
|
||||
private set
|
||||
|
||||
private var selectedItem: MangaSync? = null
|
||||
|
||||
private var searchSubscription: Subscription? = null
|
||||
|
||||
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
||||
val dialog = MaterialDialog.Builder(activity)
|
||||
.customView(R.layout.dialog_myanimelist_search, false)
|
||||
.positiveText(android.R.string.ok)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.onPositive { dialog1, which -> onPositiveButtonClick() }
|
||||
.build()
|
||||
|
||||
onViewCreated(dialog.view, savedState)
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||
v = view
|
||||
|
||||
// Create adapter
|
||||
adapter = MyAnimeListSearchAdapter(activity)
|
||||
view.myanimelist_search_results.adapter = adapter
|
||||
|
||||
// Set listeners
|
||||
view.myanimelist_search_results.setOnItemClickListener { parent, viewList, position, id ->
|
||||
selectedItem = adapter.getItem(position)
|
||||
}
|
||||
|
||||
// Do an initial search based on the manga's title
|
||||
if (savedState == null) {
|
||||
val title = presenter.manga.title
|
||||
view.myanimelist_search_field.append(title)
|
||||
search(title)
|
||||
}
|
||||
|
||||
querySubject = PublishSubject.create<String>()
|
||||
|
||||
view.myanimelist_search_field.addTextChangedListener(object : SimpleTextWatcher() {
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
querySubject.onNext(s.toString())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// Listen to text changes
|
||||
searchSubscription = querySubject.debounce(1, TimeUnit.SECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { search(it) }
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
searchSubscription?.unsubscribe()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
private fun onPositiveButtonClick() {
|
||||
presenter.registerManga(selectedItem)
|
||||
}
|
||||
|
||||
private fun search(query: String) {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
v.myanimelist_search_results.visibility = View.GONE
|
||||
v.progress.visibility = View.VISIBLE
|
||||
presenter.searchManga(query)
|
||||
}
|
||||
}
|
||||
|
||||
fun onSearchResults(results: List<MangaSync>) {
|
||||
selectedItem = null
|
||||
v.progress.visibility = View.GONE
|
||||
v.myanimelist_search_results.visibility = View.VISIBLE
|
||||
adapter.setItems(results)
|
||||
}
|
||||
|
||||
fun onSearchResultsError() {
|
||||
v.progress.visibility = View.GONE
|
||||
v.myanimelist_search_results.visibility = View.VISIBLE
|
||||
adapter.clear()
|
||||
}
|
||||
|
||||
val malFragment: MyAnimeListFragment
|
||||
get() = parentFragment as MyAnimeListFragment
|
||||
|
||||
val presenter: MyAnimeListPresenter
|
||||
get() = malFragment.presenter
|
||||
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.myanimelist
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.NumberPicker
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import kotlinx.android.synthetic.main.card_myanimelist_personal.*
|
||||
import kotlinx.android.synthetic.main.fragment_myanimelist.*
|
||||
import nucleus.factory.RequiresPresenter
|
||||
import java.text.DecimalFormat
|
||||
|
||||
@RequiresPresenter(MyAnimeListPresenter::class)
|
||||
class MyAnimeListFragment : BaseRxFragment<MyAnimeListPresenter>() {
|
||||
|
||||
companion object {
|
||||
fun newInstance(): MyAnimeListFragment {
|
||||
return MyAnimeListFragment()
|
||||
}
|
||||
}
|
||||
|
||||
private var dialog: MyAnimeListDialogFragment? = null
|
||||
|
||||
private val decimalFormat = DecimalFormat("#.##")
|
||||
|
||||
private val SEARCH_FRAGMENT_TAG = "mal_search"
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_myanimelist, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||
swipe_refresh.isEnabled = false
|
||||
swipe_refresh.setOnRefreshListener { presenter.refresh() }
|
||||
myanimelist_title_layout.setOnClickListener { onTitleClick() }
|
||||
myanimelist_status_layout.setOnClickListener { onStatusClick() }
|
||||
myanimelist_chapters_layout.setOnClickListener { onChaptersClick() }
|
||||
myanimelist_score_layout.setOnClickListener { onScoreClick() }
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
fun setMangaSync(mangaSync: MangaSync?) {
|
||||
swipe_refresh.isEnabled = mangaSync != null
|
||||
mangaSync?.let {
|
||||
myanimelist_title.setTextAppearance(context, R.style.TextAppearance_Regular_Body1_Secondary)
|
||||
myanimelist_title.setAllCaps(false)
|
||||
myanimelist_title.text = it.title
|
||||
myanimelist_chapters.text = if (it.total_chapters > 0)
|
||||
"${it.last_chapter_read}/${it.total_chapters}" else "${it.last_chapter_read}/-"
|
||||
myanimelist_score.text = if (it.score == 0f) "-" else decimalFormat.format(it.score)
|
||||
myanimelist_status.text = presenter.myAnimeList.getStatus(it.status)
|
||||
} ?: run {
|
||||
myanimelist_title.setTextAppearance(context, R.style.TextAppearance_Medium_Button)
|
||||
myanimelist_title.setText(R.string.action_edit)
|
||||
myanimelist_chapters.text = ""
|
||||
myanimelist_score.text = ""
|
||||
myanimelist_status.text = ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun onRefreshDone() {
|
||||
swipe_refresh.isRefreshing = false
|
||||
}
|
||||
|
||||
fun onRefreshError(error: Throwable) {
|
||||
swipe_refresh.isRefreshing = false
|
||||
context.toast(error.message)
|
||||
}
|
||||
|
||||
fun setSearchResults(results: List<MangaSync>) {
|
||||
findSearchFragmentIfNeeded()
|
||||
|
||||
dialog?.onSearchResults(results)
|
||||
}
|
||||
|
||||
fun setSearchResultsError(error: Throwable) {
|
||||
findSearchFragmentIfNeeded()
|
||||
context.toast(error.message)
|
||||
|
||||
dialog?.onSearchResultsError()
|
||||
}
|
||||
|
||||
private fun findSearchFragmentIfNeeded() {
|
||||
if (dialog == null) {
|
||||
dialog = childFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG) as MyAnimeListDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
fun onTitleClick() {
|
||||
if (dialog == null) {
|
||||
dialog = MyAnimeListDialogFragment.newInstance()
|
||||
}
|
||||
|
||||
presenter.restartSearch()
|
||||
dialog?.show(childFragmentManager, SEARCH_FRAGMENT_TAG)
|
||||
}
|
||||
|
||||
fun onStatusClick() {
|
||||
if (presenter.mangaSync == null)
|
||||
return
|
||||
|
||||
MaterialDialog.Builder(activity)
|
||||
.title(R.string.status)
|
||||
.items(presenter.getAllStatus())
|
||||
.itemsCallbackSingleChoice(presenter.getIndexFromStatus(), { dialog, view, i, charSequence ->
|
||||
presenter.setStatus(i)
|
||||
myanimelist_status.text = "..."
|
||||
true
|
||||
})
|
||||
.show()
|
||||
}
|
||||
|
||||
fun onChaptersClick() {
|
||||
if (presenter.mangaSync == null)
|
||||
return
|
||||
|
||||
val dialog = MaterialDialog.Builder(activity)
|
||||
.title(R.string.chapters)
|
||||
.customView(R.layout.dialog_myanimelist_chapters, false)
|
||||
.positiveText(android.R.string.ok)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.onPositive { d, action ->
|
||||
val view = d.customView
|
||||
if (view != null) {
|
||||
val np = view.findViewById(R.id.chapters_picker) as NumberPicker
|
||||
np.clearFocus()
|
||||
presenter.setLastChapterRead(np.value)
|
||||
myanimelist_chapters.text = "..."
|
||||
}
|
||||
}
|
||||
.show()
|
||||
|
||||
val view = dialog.customView
|
||||
if (view != null) {
|
||||
val np = view.findViewById(R.id.chapters_picker) as NumberPicker
|
||||
// Set initial value
|
||||
np.value = presenter.mangaSync!!.last_chapter_read
|
||||
// Don't allow to go from 0 to 9999
|
||||
np.wrapSelectorWheel = false
|
||||
}
|
||||
}
|
||||
|
||||
fun onScoreClick() {
|
||||
if (presenter.mangaSync == null)
|
||||
return
|
||||
|
||||
val dialog = MaterialDialog.Builder(activity)
|
||||
.title(R.string.score)
|
||||
.customView(R.layout.dialog_myanimelist_score, false)
|
||||
.positiveText(android.R.string.ok)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.onPositive { d, action ->
|
||||
val view = d.customView
|
||||
if (view != null) {
|
||||
val np = view.findViewById(R.id.score_picker) as NumberPicker
|
||||
np.clearFocus()
|
||||
presenter.setScore(np.value)
|
||||
myanimelist_score.text = "..."
|
||||
}
|
||||
}
|
||||
.show()
|
||||
|
||||
val view = dialog.customView
|
||||
if (view != null) {
|
||||
val np = view.findViewById(R.id.score_picker) as NumberPicker
|
||||
// Set initial value
|
||||
np.value = presenter.mangaSync!!.score.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.myanimelist
|
||||
|
||||
import android.os.Bundle
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaEvent
|
||||
import eu.kanade.tachiyomi.util.SharedData
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
|
||||
|
||||
val db: DatabaseHelper by injectLazy()
|
||||
val syncManager: MangaSyncManager by injectLazy()
|
||||
|
||||
val myAnimeList by lazy { syncManager.myAnimeList }
|
||||
|
||||
lateinit var manga: Manga
|
||||
private set
|
||||
|
||||
var mangaSync: MangaSync? = null
|
||||
private set
|
||||
|
||||
private var query: String? = null
|
||||
|
||||
private val GET_MANGA_SYNC = 1
|
||||
private val GET_SEARCH_RESULTS = 2
|
||||
private val REFRESH = 3
|
||||
|
||||
private val PREFIX_MY = "my:"
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
startableLatestCache(GET_MANGA_SYNC,
|
||||
{ db.getMangaSync(manga, myAnimeList).asRxObservable()
|
||||
.doOnNext { mangaSync = it }
|
||||
.observeOn(AndroidSchedulers.mainThread()) },
|
||||
{ view, mangaSync -> view.setMangaSync(mangaSync) })
|
||||
|
||||
startableLatestCache(GET_SEARCH_RESULTS,
|
||||
{ getSearchResultsObservable() },
|
||||
{ view, results -> view.setSearchResults(results) },
|
||||
{ view, error -> view.setSearchResultsError(error) })
|
||||
|
||||
startableFirst(REFRESH,
|
||||
{ getRefreshObservable() },
|
||||
{ view, result -> view.onRefreshDone() },
|
||||
{ view, error -> view.onRefreshError(error) })
|
||||
|
||||
manga = SharedData.get(MangaEvent::class.java)?.manga ?: return
|
||||
start(GET_MANGA_SYNC)
|
||||
}
|
||||
|
||||
fun getSearchResultsObservable(): Observable<List<MangaSync>> {
|
||||
return query?.let { query ->
|
||||
val observable: Observable<List<MangaSync>>
|
||||
if (query.startsWith(PREFIX_MY)) {
|
||||
val realQuery = query.substring(PREFIX_MY.length).toLowerCase().trim()
|
||||
observable = myAnimeList.getList()
|
||||
.flatMap { Observable.from(it) }
|
||||
.filter { it.title.toLowerCase().contains(realQuery) }
|
||||
.toList()
|
||||
} else {
|
||||
observable = myAnimeList.search(query)
|
||||
}
|
||||
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
|
||||
} ?: Observable.error(Exception("Null query"))
|
||||
|
||||
}
|
||||
|
||||
fun getRefreshObservable(): Observable<PutResult> {
|
||||
return mangaSync?.let { mangaSync ->
|
||||
myAnimeList.getList()
|
||||
.map { myList ->
|
||||
myList.find { it.remote_id == mangaSync.remote_id }?.let {
|
||||
mangaSync.copyPersonalFrom(it)
|
||||
mangaSync.total_chapters = it.total_chapters
|
||||
mangaSync
|
||||
} ?: throw Exception("Could not find manga")
|
||||
}
|
||||
.flatMap { db.insertMangaSync(it).asRxObservable() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
} ?: Observable.error(Exception("Not found"))
|
||||
}
|
||||
|
||||
private fun updateRemote() {
|
||||
mangaSync?.let { mangaSync ->
|
||||
add(myAnimeList.update(mangaSync)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMap { db.insertMangaSync(mangaSync).asRxObservable() }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ next -> },
|
||||
{ error ->
|
||||
Timber.e(error)
|
||||
// Restart on error to set old values
|
||||
start(GET_MANGA_SYNC)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fun searchManga(query: String) {
|
||||
if (query.isNullOrEmpty() || query == this.query)
|
||||
return
|
||||
|
||||
this.query = query
|
||||
start(GET_SEARCH_RESULTS)
|
||||
}
|
||||
|
||||
fun restartSearch() {
|
||||
query = null
|
||||
stop(GET_SEARCH_RESULTS)
|
||||
}
|
||||
|
||||
fun registerManga(sync: MangaSync?) {
|
||||
if (sync != null) {
|
||||
sync.manga_id = manga.id!!
|
||||
add(myAnimeList.bind(sync)
|
||||
.flatMap { db.insertMangaSync(sync).asRxObservable() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ },
|
||||
{ error -> context.toast(error.message) }))
|
||||
} else {
|
||||
db.deleteMangaSyncForManga(manga).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllStatus(): List<String> {
|
||||
return listOf(context.getString(R.string.reading),
|
||||
context.getString(R.string.completed),
|
||||
context.getString(R.string.on_hold),
|
||||
context.getString(R.string.dropped),
|
||||
context.getString(R.string.plan_to_read))
|
||||
}
|
||||
|
||||
fun getIndexFromStatus(): Int {
|
||||
return mangaSync?.let { mangaSync ->
|
||||
if (mangaSync.status == 6) 4 else mangaSync.status - 1
|
||||
} ?: 0
|
||||
}
|
||||
|
||||
fun setStatus(index: Int) {
|
||||
mangaSync?.status = if (index == 4) 6 else index + 1
|
||||
updateRemote()
|
||||
}
|
||||
|
||||
fun setScore(score: Int) {
|
||||
mangaSync?.score = score.toFloat()
|
||||
updateRemote()
|
||||
}
|
||||
|
||||
fun setLastChapterRead(chapterNumber: Int) {
|
||||
mangaSync?.last_chapter_read = chapterNumber
|
||||
updateRemote()
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
if (mangaSync != null) {
|
||||
start(REFRESH)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.myanimelist
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||
import eu.kanade.tachiyomi.util.inflate
|
||||
import kotlinx.android.synthetic.main.dialog_myanimelist_search_item.view.*
|
||||
import java.util.*
|
||||
|
||||
class MyAnimeListSearchAdapter(context: Context) :
|
||||
ArrayAdapter<MangaSync>(context, R.layout.dialog_myanimelist_search_item, ArrayList<MangaSync>()) {
|
||||
|
||||
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
|
||||
var v = view
|
||||
// Get the data item for this position
|
||||
val sync = getItem(position)
|
||||
// Check if an existing view is being reused, otherwise inflate the view
|
||||
val holder: SearchViewHolder // view lookup cache stored in tag
|
||||
if (v == null) {
|
||||
v = parent.inflate(R.layout.dialog_myanimelist_search_item)
|
||||
holder = SearchViewHolder(v)
|
||||
v.tag = holder
|
||||
} else {
|
||||
holder = v.tag as SearchViewHolder
|
||||
}
|
||||
holder.onSetValues(sync)
|
||||
return v
|
||||
}
|
||||
|
||||
fun setItems(syncs: List<MangaSync>) {
|
||||
setNotifyOnChange(false)
|
||||
clear()
|
||||
addAll(syncs)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
class SearchViewHolder(private val view: View) {
|
||||
|
||||
fun onSetValues(sync: MangaSync) {
|
||||
view.myanimelist_result_title.text = sync.title
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.ViewGroup
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.inflate
|
||||
|
||||
class TrackAdapter(val fragment: TrackFragment) : RecyclerView.Adapter<TrackHolder>() {
|
||||
|
||||
var items = emptyList<TrackItem>()
|
||||
set(value) {
|
||||
if (field !== value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
var onClickListener: (TrackItem) -> Unit = {}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackHolder {
|
||||
val view = parent.inflate(R.layout.item_track)
|
||||
return TrackHolder(view, fragment)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TrackHolder, position: Int) {
|
||||
holder.onSetValues(items[position])
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.NumberPicker
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaActivity
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import kotlinx.android.synthetic.main.fragment_track.*
|
||||
import nucleus.factory.RequiresPresenter
|
||||
|
||||
@RequiresPresenter(TrackPresenter::class)
|
||||
class TrackFragment : BaseRxFragment<TrackPresenter>() {
|
||||
|
||||
companion object {
|
||||
fun newInstance(): TrackFragment {
|
||||
return TrackFragment()
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var adapter: TrackAdapter
|
||||
|
||||
private var dialog: TrackSearchDialog? = null
|
||||
|
||||
private val searchFragmentTag: String
|
||||
get() = "search_fragment"
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View {
|
||||
return inflater.inflate(R.layout.fragment_track, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
adapter = TrackAdapter(this)
|
||||
recycler.layoutManager = LinearLayoutManager(context)
|
||||
recycler.adapter = adapter
|
||||
swipe_refresh.isEnabled = false
|
||||
swipe_refresh.setOnRefreshListener { presenter.refresh() }
|
||||
}
|
||||
|
||||
private fun findSearchFragmentIfNeeded() {
|
||||
if (dialog == null) {
|
||||
dialog = childFragmentManager.findFragmentByTag(searchFragmentTag) as TrackSearchDialog
|
||||
}
|
||||
}
|
||||
|
||||
fun onNextTrackings(trackings: List<TrackItem>) {
|
||||
adapter.items = trackings
|
||||
swipe_refresh.isEnabled = trackings.any { it.track != null }
|
||||
(activity as MangaActivity).setTrackingIcon(trackings.any { it.track != null })
|
||||
}
|
||||
|
||||
fun onSearchResults(results: List<Track>) {
|
||||
if (!isResumed) return
|
||||
|
||||
findSearchFragmentIfNeeded()
|
||||
dialog?.onSearchResults(results)
|
||||
}
|
||||
|
||||
fun onSearchResultsError(error: Throwable) {
|
||||
if (!isResumed) return
|
||||
|
||||
findSearchFragmentIfNeeded()
|
||||
dialog?.onSearchResultsError()
|
||||
}
|
||||
|
||||
fun onRefreshDone() {
|
||||
swipe_refresh.isRefreshing = false
|
||||
}
|
||||
|
||||
fun onRefreshError(error: Throwable) {
|
||||
swipe_refresh.isRefreshing = false
|
||||
context.toast(error.message)
|
||||
}
|
||||
|
||||
fun onTitleClick(item: TrackItem) {
|
||||
if (!isResumed) return
|
||||
|
||||
if (dialog == null) {
|
||||
dialog = TrackSearchDialog.newInstance()
|
||||
}
|
||||
|
||||
presenter.selectedService = item.service
|
||||
dialog?.show(childFragmentManager, searchFragmentTag)
|
||||
}
|
||||
|
||||
fun onStatusClick(item: TrackItem) {
|
||||
if (!isResumed || item.track == null) return
|
||||
|
||||
val statusList = item.service.getStatusList().map { item.service.getStatus(it) }
|
||||
val selectedIndex = item.service.getStatusList().indexOf(item.track.status)
|
||||
|
||||
MaterialDialog.Builder(context)
|
||||
.title(R.string.status)
|
||||
.items(statusList)
|
||||
.itemsCallbackSingleChoice(selectedIndex, { dialog, view, i, charSequence ->
|
||||
presenter.setStatus(item, i)
|
||||
swipe_refresh.isRefreshing = true
|
||||
true
|
||||
})
|
||||
.show()
|
||||
}
|
||||
|
||||
fun onChaptersClick(item: TrackItem) {
|
||||
if (!isResumed || item.track == null) return
|
||||
|
||||
val dialog = MaterialDialog.Builder(context)
|
||||
.title(R.string.chapters)
|
||||
.customView(R.layout.dialog_track_chapters, false)
|
||||
.positiveText(android.R.string.ok)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.onPositive { d, action ->
|
||||
val view = d.customView
|
||||
if (view != null) {
|
||||
val np = view.findViewById(R.id.chapters_picker) as NumberPicker
|
||||
np.clearFocus()
|
||||
presenter.setLastChapterRead(item, np.value)
|
||||
swipe_refresh.isRefreshing = true
|
||||
}
|
||||
}
|
||||
.show()
|
||||
|
||||
val view = dialog.customView
|
||||
if (view != null) {
|
||||
val np = view.findViewById(R.id.chapters_picker) as NumberPicker
|
||||
// Set initial value
|
||||
np.value = item.track.last_chapter_read
|
||||
// Don't allow to go from 0 to 9999
|
||||
np.wrapSelectorWheel = false
|
||||
}
|
||||
}
|
||||
|
||||
fun onScoreClick(item: TrackItem) {
|
||||
if (!isResumed || item.track == null) return
|
||||
|
||||
val dialog = MaterialDialog.Builder(activity)
|
||||
.title(R.string.score)
|
||||
.customView(R.layout.dialog_track_score, false)
|
||||
.positiveText(android.R.string.ok)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.onPositive { d, action ->
|
||||
val view = d.customView
|
||||
if (view != null) {
|
||||
val np = view.findViewById(R.id.score_picker) as NumberPicker
|
||||
np.clearFocus()
|
||||
presenter.setScore(item, np.value)
|
||||
swipe_refresh.isRefreshing = true
|
||||
}
|
||||
}
|
||||
.show()
|
||||
|
||||
val view = dialog.customView
|
||||
if (view != null) {
|
||||
val np = view.findViewById(R.id.score_picker) as NumberPicker
|
||||
np.maxValue = item.service.maxScore()
|
||||
// Set initial value
|
||||
np.value = item.track.score.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlinx.android.synthetic.main.item_track.view.*
|
||||
|
||||
class TrackHolder(private val view: View, private val fragment: TrackFragment)
|
||||
: RecyclerView.ViewHolder(view) {
|
||||
|
||||
private lateinit var item: TrackItem
|
||||
|
||||
init {
|
||||
view.title_container.setOnClickListener { fragment.onTitleClick(item) }
|
||||
view.status_container.setOnClickListener { fragment.onStatusClick(item) }
|
||||
view.chapters_container.setOnClickListener { fragment.onChaptersClick(item) }
|
||||
view.score_container.setOnClickListener { fragment.onScoreClick(item) }
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
fun onSetValues(item: TrackItem) = with(view) {
|
||||
this@TrackHolder.item = item
|
||||
val track = item.track
|
||||
track_logo.setImageResource(item.service.getLogo())
|
||||
logo.setBackgroundColor(item.service.getLogoColor())
|
||||
if (track != null) {
|
||||
track_title.setTextAppearance(context, R.style.TextAppearance_Regular_Body1_Secondary)
|
||||
track_title.setAllCaps(false)
|
||||
track_title.text = track.title
|
||||
track_chapters.text = "${track.last_chapter_read}/" +
|
||||
if (track.total_chapters > 0) track.total_chapters else "-"
|
||||
track_status.text = item.service.getStatus(track.status)
|
||||
track_score.text = if (track.score == 0f) "-" else item.service.formatScore(track)
|
||||
} else {
|
||||
track_title.setTextAppearance(context, R.style.TextAppearance_Medium_Button)
|
||||
track_title.setText(R.string.action_edit)
|
||||
track_chapters.text = ""
|
||||
track_score.text = ""
|
||||
track_status.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
|
||||
class TrackItem(val track: Track?, val service: TrackService) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
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.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaEvent
|
||||
import eu.kanade.tachiyomi.util.SharedData
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class TrackPresenter : BasePresenter<TrackFragment>() {
|
||||
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
|
||||
private val trackManager: TrackManager by injectLazy()
|
||||
|
||||
lateinit var manga: Manga
|
||||
private set
|
||||
|
||||
private var trackList: List<TrackItem> = emptyList()
|
||||
|
||||
private val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
|
||||
|
||||
var selectedService: TrackService? = null
|
||||
|
||||
private var trackSubscription: Subscription? = null
|
||||
|
||||
private var searchSubscription: Subscription? = null
|
||||
|
||||
private var refreshSubscription: Subscription? = null
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
manga = SharedData.get(MangaEvent::class.java)?.manga ?: return
|
||||
fetchTrackings()
|
||||
}
|
||||
|
||||
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(TrackFragment::onNextTrackings)
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
refreshSubscription?.let { remove(it) }
|
||||
refreshSubscription = Observable.from(trackList)
|
||||
.filter { it.track != null }
|
||||
.concatMap { item ->
|
||||
item.service.refresh(item.track!!)
|
||||
.flatMap { db.insertTrack(it).asRxObservable() }
|
||||
.map { item }
|
||||
.onErrorReturn { item }
|
||||
}
|
||||
.toList()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst({ view, result -> view.onRefreshDone() },
|
||||
TrackFragment::onRefreshError)
|
||||
}
|
||||
|
||||
fun search(query: String) {
|
||||
val service = selectedService ?: return
|
||||
|
||||
searchSubscription?.let { remove(it) }
|
||||
searchSubscription = service.search(query)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeLatestCache(TrackFragment::onSearchResults,
|
||||
TrackFragment::onSearchResultsError)
|
||||
}
|
||||
|
||||
fun registerTracking(item: Track?) {
|
||||
val service = selectedService ?: return
|
||||
|
||||
if (item != null) {
|
||||
item.manga_id = manga.id!!
|
||||
add(service.bind(item)
|
||||
.flatMap { db.insertTrack(item).asRxObservable() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ },
|
||||
{ error -> context.toast(error.message) }))
|
||||
} else {
|
||||
db.deleteTrackForManga(manga, service).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRemote(track: Track, service: TrackService) {
|
||||
service.update(track)
|
||||
.flatMap { db.insertTrack(track).asRxObservable() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst({ view, result -> view.onRefreshDone() },
|
||||
{ view, error ->
|
||||
view.onRefreshError(error)
|
||||
|
||||
// Restart on error to set old values
|
||||
fetchTrackings()
|
||||
})
|
||||
}
|
||||
|
||||
fun setStatus(item: TrackItem, index: Int) {
|
||||
val track = item.track!!
|
||||
track.status = item.service.getStatusList()[index]
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setScore(item: TrackItem, score: Int) {
|
||||
val track = item.track!!
|
||||
track.score = score.toFloat()
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
fun setLastChapterRead(item: TrackItem, chapterNumber: Int) {
|
||||
val track = item.track!!
|
||||
track.last_chapter_read = chapterNumber
|
||||
updateRemote(track, item.service)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.util.inflate
|
||||
import kotlinx.android.synthetic.main.item_track_search.view.*
|
||||
import java.util.*
|
||||
|
||||
class TrackSearchAdapter(context: Context)
|
||||
: ArrayAdapter<Track>(context, R.layout.item_track_search, ArrayList<Track>()) {
|
||||
|
||||
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
|
||||
var v = view
|
||||
// Get the data item for this position
|
||||
val track = getItem(position)
|
||||
// Check if an existing view is being reused, otherwise inflate the view
|
||||
val holder: TrackSearchHolder // view lookup cache stored in tag
|
||||
if (v == null) {
|
||||
v = parent.inflate(R.layout.item_track_search)
|
||||
holder = TrackSearchHolder(v)
|
||||
v.tag = holder
|
||||
} else {
|
||||
holder = v.tag as TrackSearchHolder
|
||||
}
|
||||
holder.onSetValues(track)
|
||||
return v
|
||||
}
|
||||
|
||||
fun setItems(syncs: List<Track>) {
|
||||
setNotifyOnChange(false)
|
||||
clear()
|
||||
addAll(syncs)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
class TrackSearchHolder(private val view: View) {
|
||||
|
||||
fun onSetValues(track: Track) {
|
||||
view.track_search_title.text = track.title
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.view.View
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.jakewharton.rxrelay.PublishRelay
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
|
||||
import kotlinx.android.synthetic.main.dialog_track_search.view.*
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class TrackSearchDialog : DialogFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(): TrackSearchDialog {
|
||||
return TrackSearchDialog()
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var v: View
|
||||
|
||||
lateinit var adapter: TrackSearchAdapter
|
||||
private set
|
||||
|
||||
private val queryRelay by lazy { PublishRelay.create<String>() }
|
||||
|
||||
private var searchDebounceSubscription: Subscription? = null
|
||||
|
||||
private var selectedItem: Track? = null
|
||||
|
||||
val presenter: TrackPresenter
|
||||
get() = (parentFragment as TrackFragment).presenter
|
||||
|
||||
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
||||
val dialog = MaterialDialog.Builder(context)
|
||||
.customView(R.layout.dialog_track_search, false)
|
||||
.positiveText(android.R.string.ok)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.onPositive { dialog1, which -> onPositiveButtonClick() }
|
||||
.build()
|
||||
|
||||
onViewCreated(dialog.view, savedState)
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||
v = view
|
||||
|
||||
// Create adapter
|
||||
adapter = TrackSearchAdapter(context)
|
||||
view.track_search_list.adapter = adapter
|
||||
|
||||
// Set listeners
|
||||
selectedItem = null
|
||||
view.track_search_list.setOnItemClickListener { parent, viewList, position, id ->
|
||||
selectedItem = adapter.getItem(position)
|
||||
}
|
||||
|
||||
// Do an initial search based on the manga's title
|
||||
if (savedState == null) {
|
||||
val title = presenter.manga.title
|
||||
view.track_search.append(title)
|
||||
search(title)
|
||||
}
|
||||
|
||||
view.track_search.addTextChangedListener(object : SimpleTextWatcher() {
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
queryRelay.call(s.toString())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// Listen to text changes
|
||||
searchDebounceSubscription = queryRelay.debounce(1, TimeUnit.SECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.filter { it.isNotBlank() }
|
||||
.subscribe { search(it) }
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
searchDebounceSubscription?.unsubscribe()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
private fun search(query: String) {
|
||||
v.progress.visibility = View.VISIBLE
|
||||
v.track_search_list.visibility = View.GONE
|
||||
|
||||
presenter.search(query)
|
||||
}
|
||||
|
||||
fun onSearchResults(results: List<Track>) {
|
||||
selectedItem = null
|
||||
v.progress.visibility = View.GONE
|
||||
v.track_search_list.visibility = View.VISIBLE
|
||||
adapter.setItems(results)
|
||||
}
|
||||
|
||||
fun onSearchResultsError() {
|
||||
v.progress.visibility = View.VISIBLE
|
||||
v.track_search_list.visibility = View.GONE
|
||||
adapter.setItems(emptyList())
|
||||
}
|
||||
|
||||
private fun onPositiveButtonClick() {
|
||||
presenter.registerTracking(selectedItem)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -163,19 +163,19 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
val chapterToUpdate = presenter.getMangaSyncChapterToUpdate()
|
||||
val chapterToUpdate = presenter.getTrackChapterToUpdate()
|
||||
|
||||
if (chapterToUpdate > 0) {
|
||||
if (preferences.askUpdateMangaSync()) {
|
||||
if (preferences.askUpdateTrack()) {
|
||||
MaterialDialog.Builder(this)
|
||||
.content(getString(R.string.confirm_update_manga_sync, chapterToUpdate))
|
||||
.positiveText(android.R.string.yes)
|
||||
.negativeText(android.R.string.no)
|
||||
.onPositive { dialog, which -> presenter.updateMangaSyncLastChapterRead() }
|
||||
.onPositive { dialog, which -> presenter.updateTrackLastChapterRead() }
|
||||
.onAny { dialog1, which1 -> super.onBackPressed() }
|
||||
.show()
|
||||
} else {
|
||||
presenter.updateMangaSyncLastChapterRead()
|
||||
presenter.updateTrackLastChapterRead()
|
||||
super.onBackPressed()
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -10,14 +10,14 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.History
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
||||
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||
import eu.kanade.tachiyomi.data.source.model.Page
|
||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackUpdateService
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.reader.notification.ImageNotifier
|
||||
import eu.kanade.tachiyomi.util.DiskUtil
|
||||
@@ -54,9 +54,9 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||
val downloadManager: DownloadManager by injectLazy()
|
||||
|
||||
/**
|
||||
* Sync manager.
|
||||
* Tracking manager.
|
||||
*/
|
||||
val syncManager: MangaSyncManager by injectLazy()
|
||||
val trackManager: TrackManager by injectLazy()
|
||||
|
||||
/**
|
||||
* Source manager.
|
||||
@@ -124,7 +124,7 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||
/**
|
||||
* List of manga services linked to the active manga, or null if auto syncing is not enabled.
|
||||
*/
|
||||
private var mangaSyncList: List<MangaSync>? = null
|
||||
private var trackList: List<Track>? = null
|
||||
|
||||
/**
|
||||
* Chapter loader whose job is to obtain the chapter list and initialize every page.
|
||||
@@ -165,9 +165,9 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||
.subscribeLatestCache({ view, manga -> view.onMangaOpen(manga) })
|
||||
|
||||
// Retrieve the sync list if auto syncing is enabled.
|
||||
if (prefs.autoUpdateMangaSync()) {
|
||||
add(db.getMangasSync(manga).asRxSingle()
|
||||
.subscribe({ mangaSyncList = it }))
|
||||
if (prefs.autoUpdateTrack()) {
|
||||
add(db.getTracks(manga).asRxSingle()
|
||||
.subscribe({ trackList = it }))
|
||||
}
|
||||
|
||||
restartableLatestCache(LOAD_ACTIVE_CHAPTER,
|
||||
@@ -431,9 +431,9 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||
/**
|
||||
* Returns the chapter to be marked as last read in sync services or 0 if no update required.
|
||||
*/
|
||||
fun getMangaSyncChapterToUpdate(): Int {
|
||||
val mangaSyncList = mangaSyncList
|
||||
if (chapter.pages == null || mangaSyncList == null || mangaSyncList.isEmpty())
|
||||
fun getTrackChapterToUpdate(): Int {
|
||||
val trackList = trackList
|
||||
if (chapter.pages == null || trackList == null || trackList.isEmpty())
|
||||
return 0
|
||||
|
||||
val prevChapter = prevChapter
|
||||
@@ -446,24 +446,24 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||
else
|
||||
0
|
||||
|
||||
mangaSyncList.forEach { sync ->
|
||||
trackList.forEach { sync ->
|
||||
if (lastChapterRead > sync.last_chapter_read) {
|
||||
sync.last_chapter_read = lastChapterRead
|
||||
sync.update = true
|
||||
}
|
||||
}
|
||||
|
||||
return if (mangaSyncList.any { it.update }) lastChapterRead else 0
|
||||
return if (trackList.any { it.update }) lastChapterRead else 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the service that updates the last chapter read in sync services
|
||||
*/
|
||||
fun updateMangaSyncLastChapterRead() {
|
||||
mangaSyncList?.forEach { sync ->
|
||||
val service = syncManager.getService(sync.sync_id)
|
||||
fun updateTrackLastChapterRead() {
|
||||
trackList?.forEach { sync ->
|
||||
val service = trackManager.getService(sync.sync_id)
|
||||
if (service != null && service.isLogged && sync.update) {
|
||||
UpdateMangaSyncService.start(context, sync)
|
||||
TrackUpdateService.start(context, sync)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ import android.view.Gravity.CENTER
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ProgressBar
|
||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class AnilistLoginActivity : AppCompatActivity() {
|
||||
|
||||
private val syncManager: MangaSyncManager by injectLazy()
|
||||
private val trackManager: TrackManager by injectLazy()
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
@@ -24,7 +24,7 @@ class AnilistLoginActivity : AppCompatActivity() {
|
||||
|
||||
val code = intent.data?.getQueryParameter("code")
|
||||
if (code != null) {
|
||||
syncManager.aniList.login(code)
|
||||
trackManager.aniList.login(code)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
@@ -33,7 +33,7 @@ class AnilistLoginActivity : AppCompatActivity() {
|
||||
returnToSettings()
|
||||
})
|
||||
} else {
|
||||
syncManager.aniList.logout()
|
||||
trackManager.aniList.logout()
|
||||
returnToSettings()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class SettingsActivity : BaseActivity(),
|
||||
"general_screen" -> SettingsGeneralFragment.newInstance(key)
|
||||
"downloads_screen" -> SettingsDownloadsFragment.newInstance(key)
|
||||
"sources_screen" -> SettingsSourcesFragment.newInstance(key)
|
||||
"sync_screen" -> SettingsSyncFragment.newInstance(key)
|
||||
"tracking_screen" -> SettingsTrackingFragment.newInstance(key)
|
||||
"advanced_screen" -> SettingsAdvancedFragment.newInstance(key)
|
||||
"about_screen" -> SettingsAboutFragment.newInstance(key)
|
||||
else -> SettingsFragment.newInstance(key)
|
||||
|
||||
@@ -28,7 +28,7 @@ open class SettingsFragment : XpPreferenceFragment() {
|
||||
addPreferencesFromResource(R.xml.pref_reader)
|
||||
addPreferencesFromResource(R.xml.pref_downloads)
|
||||
addPreferencesFromResource(R.xml.pref_sources)
|
||||
addPreferencesFromResource(R.xml.pref_sync)
|
||||
addPreferencesFromResource(R.xml.pref_tracking)
|
||||
addPreferencesFromResource(R.xml.pref_advanced)
|
||||
addPreferencesFromResource(R.xml.pref_about)
|
||||
|
||||
|
||||
@@ -1,89 +1,94 @@
|
||||
package eu.kanade.tachiyomi.ui.setting
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v7.preference.PreferenceCategory
|
||||
import android.support.v7.preference.XpPreferenceFragment
|
||||
import android.view.View
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncService
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.widget.preference.LoginPreference
|
||||
import eu.kanade.tachiyomi.widget.preference.MangaSyncLoginDialog
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class SettingsSyncFragment : SettingsFragment() {
|
||||
|
||||
companion object {
|
||||
const val SYNC_CHANGE_REQUEST = 121
|
||||
|
||||
fun newInstance(rootKey: String): SettingsSyncFragment {
|
||||
val args = Bundle()
|
||||
args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
|
||||
return SettingsSyncFragment().apply { arguments = args }
|
||||
}
|
||||
}
|
||||
|
||||
private val syncManager: MangaSyncManager by injectLazy()
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
val syncCategory: PreferenceCategory by bindPref(R.string.pref_category_manga_sync_accounts_key)
|
||||
|
||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||
super.onViewCreated(view, savedState)
|
||||
|
||||
registerService(syncManager.myAnimeList)
|
||||
|
||||
// registerService(syncManager.aniList) {
|
||||
// val intent = CustomTabsIntent.Builder()
|
||||
// .setToolbarColor(activity.theme.getResourceColor(R.attr.colorPrimary))
|
||||
// .build()
|
||||
// intent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||
// intent.launchUrl(activity, AnilistApi.authUrl())
|
||||
// }
|
||||
}
|
||||
|
||||
private fun <T : MangaSyncService> registerService(
|
||||
service: T,
|
||||
onPreferenceClick: (T) -> Unit = defaultOnPreferenceClick) {
|
||||
|
||||
LoginPreference(preferenceManager.context).apply {
|
||||
key = preferences.keys.syncUsername(service.id)
|
||||
title = service.name
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
onPreferenceClick(service)
|
||||
true
|
||||
}
|
||||
|
||||
syncCategory.addPreference(this)
|
||||
}
|
||||
}
|
||||
|
||||
private val defaultOnPreferenceClick: (MangaSyncService) -> Unit
|
||||
get() = {
|
||||
val fragment = MangaSyncLoginDialog.newInstance(it)
|
||||
fragment.setTargetFragment(this, SYNC_CHANGE_REQUEST)
|
||||
fragment.show(fragmentManager, null)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// Manually refresh anilist holder
|
||||
// updatePreference(syncManager.aniList.id)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == SYNC_CHANGE_REQUEST) {
|
||||
updatePreference(resultCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePreference(id: Int) {
|
||||
val pref = findPreference(preferences.keys.syncUsername(id)) as? LoginPreference
|
||||
pref?.notifyChanged()
|
||||
}
|
||||
|
||||
}
|
||||
package eu.kanade.tachiyomi.ui.setting
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.customtabs.CustomTabsIntent
|
||||
import android.support.v7.preference.PreferenceCategory
|
||||
import android.support.v7.preference.XpPreferenceFragment
|
||||
import android.view.View
|
||||
import eu.kanade.tachiyomi.R
|
||||
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.data.track.anilist.AnilistApi
|
||||
import eu.kanade.tachiyomi.util.getResourceColor
|
||||
import eu.kanade.tachiyomi.widget.preference.LoginPreference
|
||||
import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class SettingsTrackingFragment : SettingsFragment() {
|
||||
|
||||
companion object {
|
||||
const val SYNC_CHANGE_REQUEST = 121
|
||||
|
||||
fun newInstance(rootKey: String): SettingsTrackingFragment {
|
||||
val args = Bundle()
|
||||
args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
|
||||
return SettingsTrackingFragment().apply { arguments = args }
|
||||
}
|
||||
}
|
||||
|
||||
private val trackManager: TrackManager by injectLazy()
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
val syncCategory: PreferenceCategory by bindPref(R.string.pref_category_tracking_accounts_key)
|
||||
|
||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||
super.onViewCreated(view, savedState)
|
||||
|
||||
registerService(trackManager.myAnimeList)
|
||||
|
||||
registerService(trackManager.aniList) {
|
||||
val intent = CustomTabsIntent.Builder()
|
||||
.setToolbarColor(activity.theme.getResourceColor(R.attr.colorPrimary))
|
||||
.build()
|
||||
intent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||
intent.launchUrl(activity, AnilistApi.authUrl())
|
||||
}
|
||||
|
||||
registerService(trackManager.kitsu)
|
||||
}
|
||||
|
||||
private fun <T : TrackService> registerService(
|
||||
service: T,
|
||||
onPreferenceClick: (T) -> Unit = defaultOnPreferenceClick) {
|
||||
|
||||
LoginPreference(preferenceManager.context).apply {
|
||||
key = preferences.keys.trackUsername(service.id)
|
||||
title = service.name
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
onPreferenceClick(service)
|
||||
true
|
||||
}
|
||||
|
||||
syncCategory.addPreference(this)
|
||||
}
|
||||
}
|
||||
|
||||
private val defaultOnPreferenceClick: (TrackService) -> Unit
|
||||
get() = {
|
||||
val fragment = TrackLoginDialog.newInstance(it)
|
||||
fragment.setTargetFragment(this, SYNC_CHANGE_REQUEST)
|
||||
fragment.show(fragmentManager, null)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// Manually refresh anilist holder
|
||||
updatePreference(trackManager.aniList.id)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == SYNC_CHANGE_REQUEST) {
|
||||
updatePreference(resultCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePreference(id: Int) {
|
||||
val pref = findPreference(preferences.keys.trackUsername(id)) as? LoginPreference
|
||||
pref?.notifyChanged()
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user