mirror of
https://git.mihon.tech/mihonapp/mihon
synced 2024-11-22 13:15:47 +03:00
Manga in Kotlin. Expect some errors yet
This commit is contained in:
parent
a87c65872c
commit
f49577bc77
36 changed files with 1928 additions and 2196 deletions
|
@ -100,7 +100,7 @@ apt {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
final SUPPORT_LIBRARY_VERSION = '23.2.0'
|
final SUPPORT_LIBRARY_VERSION = '23.2.1'
|
||||||
final DAGGER_VERSION = '2.0.2'
|
final DAGGER_VERSION = '2.0.2'
|
||||||
final OKHTTP_VERSION = '3.2.0'
|
final OKHTTP_VERSION = '3.2.0'
|
||||||
final RETROFIT_VERSION = '2.0.0-beta4'
|
final RETROFIT_VERSION = '2.0.0-beta4'
|
||||||
|
|
|
@ -66,14 +66,14 @@ open class BaseActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun snack(text: String?, duration: Int = Snackbar.LENGTH_LONG) {
|
fun snack(text: String?, duration: Int = Snackbar.LENGTH_LONG) {
|
||||||
val snack = Snackbar.make(findViewById(android.R.id.content), text ?: getString(R.string.unknown_error), duration)
|
val snack = Snackbar.make(findViewById(android.R.id.content)!!, text ?: getString(R.string.unknown_error), duration)
|
||||||
val textView = snack.view.findViewById(android.support.design.R.id.snackbar_text) as TextView
|
val textView = snack.view.findViewById(android.support.design.R.id.snackbar_text) as TextView
|
||||||
textView.setTextColor(Color.WHITE)
|
textView.setTextColor(Color.WHITE)
|
||||||
snack.show()
|
snack.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun snack(text: String?, actionRes: Int, actionFunc: () -> Unit,
|
fun snack(text: String?, actionRes: Int, actionFunc: () -> Unit,
|
||||||
duration: Int = Snackbar.LENGTH_LONG, view: View = findViewById(android.R.id.content)) {
|
duration: Int = Snackbar.LENGTH_LONG, view: View = findViewById(android.R.id.content)!!) {
|
||||||
|
|
||||||
val snack = Snackbar.make(view, text ?: getString(R.string.unknown_error), duration)
|
val snack = Snackbar.make(view, text ?: getString(R.string.unknown_error), duration)
|
||||||
.setAction(actionRes, { actionFunc() })
|
.setAction(actionRes, { actionFunc() })
|
||||||
|
|
|
@ -20,7 +20,7 @@ import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaActivity
|
import eu.kanade.tachiyomi.ui.manga.MangaActivity
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import eu.kanade.tachiyomi.widget.EndlessGridScrollListener
|
import eu.kanade.tachiyomi.widget.EndlessGridScrollListener
|
||||||
import eu.kanade.tachiyomi.widget.EndlessListScrollListener
|
import eu.kanade.tachiyomi.widget.EndlessListScrollListener
|
||||||
import kotlinx.android.synthetic.main.fragment_catalogue.*
|
import kotlinx.android.synthetic.main.fragment_catalogue.*
|
||||||
|
@ -178,7 +178,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
||||||
// Set previous selection if it's not a valid source and notify the user
|
// Set previous selection if it's not a valid source and notify the user
|
||||||
if (!presenter.isValidSource(source)) {
|
if (!presenter.isValidSource(source)) {
|
||||||
spinner.setSelection(presenter.findFirstValidSource())
|
spinner.setSelection(presenter.findFirstValidSource())
|
||||||
ToastUtil.showShort(activity, R.string.source_requires_login)
|
context.toast(R.string.source_requires_login)
|
||||||
} else {
|
} else {
|
||||||
selectedIndex = position
|
selectedIndex = position
|
||||||
presenter.setEnabledSource(selectedIndex)
|
presenter.setEnabledSource(selectedIndex)
|
||||||
|
@ -430,7 +430,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
||||||
val selectedManga = adapter.getItem(position)
|
val selectedManga = adapter.getItem(position)
|
||||||
|
|
||||||
val intent = MangaActivity.newIntent(activity, selectedManga)
|
val intent = MangaActivity.newIntent(activity, selectedManga)
|
||||||
intent.putExtra(MangaActivity.MANGA_ONLINE, true)
|
intent.putExtra(MangaActivity.FROM_CATALOGUE, true)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.support.design.widget.TabLayout
|
||||||
import android.support.v7.view.ActionMode
|
import android.support.v7.view.ActionMode
|
||||||
import android.support.v7.widget.SearchView
|
import android.support.v7.widget.SearchView
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import butterknife.ButterKnife
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
@ -20,7 +19,6 @@ import eu.kanade.tachiyomi.event.LibraryMangasEvent
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryActivity
|
import eu.kanade.tachiyomi.ui.category.CategoryActivity
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil
|
|
||||||
import eu.kanade.tachiyomi.util.inflate
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import kotlinx.android.synthetic.main.fragment_library.*
|
import kotlinx.android.synthetic.main.fragment_library.*
|
||||||
|
@ -125,7 +123,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||||
setToolbarTitle(getString(R.string.label_library))
|
setToolbarTitle(getString(R.string.label_library))
|
||||||
ButterKnife.bind(this, view)
|
|
||||||
|
|
||||||
appBar = (activity as MainActivity).appBar
|
appBar = (activity as MainActivity).appBar
|
||||||
tabs = appBar.inflate(R.layout.library_tab_layout) as TabLayout
|
tabs = appBar.inflate(R.layout.library_tab_layout) as TabLayout
|
||||||
|
@ -369,7 +366,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
startActivityForResult(Intent.createChooser(intent,
|
startActivityForResult(Intent.createChooser(intent,
|
||||||
getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN)
|
getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN)
|
||||||
} else {
|
} else {
|
||||||
ToastUtil.showShort(context, R.string.notification_first_add_to_library)
|
context.toast(R.string.notification_first_add_to_library)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -419,8 +416,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
.positiveText(R.string.button_ok)
|
.positiveText(android.R.string.ok)
|
||||||
.negativeText(R.string.button_cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.design.widget.TabLayout;
|
|
||||||
import android.support.v4.app.ActivityCompat;
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.support.v4.app.FragmentPagerAdapter;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v4.view.ViewPager;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import eu.kanade.tachiyomi.App;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
|
||||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
|
||||||
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 nucleus.factory.RequiresPresenter;
|
|
||||||
|
|
||||||
@RequiresPresenter(MangaPresenter.class)
|
|
||||||
public class MangaActivity extends BaseRxActivity<MangaPresenter> {
|
|
||||||
|
|
||||||
@Bind(R.id.toolbar) Toolbar toolbar;
|
|
||||||
@Bind(R.id.tabs) TabLayout tabs;
|
|
||||||
@Bind(R.id.view_pager) ViewPager viewPager;
|
|
||||||
|
|
||||||
@Inject PreferencesHelper preferences;
|
|
||||||
@Inject MangaSyncManager mangaSyncManager;
|
|
||||||
|
|
||||||
private MangaDetailAdapter adapter;
|
|
||||||
private boolean isOnline;
|
|
||||||
|
|
||||||
public final static String MANGA_ONLINE = "manga_online";
|
|
||||||
|
|
||||||
public static Intent newIntent(Context context, Manga manga) {
|
|
||||||
Intent intent = new Intent(context, MangaActivity.class);
|
|
||||||
if (manga != null) {
|
|
||||||
EventBus.getDefault().postSticky(manga);
|
|
||||||
}
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedState) {
|
|
||||||
super.onCreate(savedState);
|
|
||||||
App.get(this).getComponent().inject(this);
|
|
||||||
setContentView(R.layout.activity_manga);
|
|
||||||
ButterKnife.bind(this);
|
|
||||||
|
|
||||||
setupToolbar(toolbar);
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
|
|
||||||
isOnline = intent.getBooleanExtra(MANGA_ONLINE, false);
|
|
||||||
|
|
||||||
setupViewPager();
|
|
||||||
|
|
||||||
requestPermissionsOnMarshmallow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupViewPager() {
|
|
||||||
adapter = new MangaDetailAdapter(getSupportFragmentManager(), this);
|
|
||||||
|
|
||||||
viewPager.setAdapter(adapter);
|
|
||||||
|
|
||||||
// Workaround to prevent: Tab belongs to a different TabLayout.
|
|
||||||
// Internal bug in Support library v23.2.0.
|
|
||||||
// See https://code.google.com/p/android/issues/detail?id=201827
|
|
||||||
for (int j = 0; j < 17; j++)
|
|
||||||
tabs.newTab();
|
|
||||||
|
|
||||||
tabs.setupWithViewPager(viewPager);
|
|
||||||
|
|
||||||
if (!isOnline)
|
|
||||||
viewPager.setCurrentItem(MangaDetailAdapter.CHAPTERS_FRAGMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setManga(Manga manga) {
|
|
||||||
setToolbarTitle(manga.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCatalogueManga() {
|
|
||||||
return isOnline;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestPermissionsOnMarshmallow() {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
if (ContextCompat.checkSelfPermission(this,
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
|
||||||
|
|
||||||
ActivityCompat.requestPermissions(this,
|
|
||||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE},
|
|
||||||
1);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MangaDetailAdapter extends FragmentPagerAdapter {
|
|
||||||
|
|
||||||
private int pageCount;
|
|
||||||
private String tabTitles[];
|
|
||||||
|
|
||||||
final static int INFO_FRAGMENT = 0;
|
|
||||||
final static int CHAPTERS_FRAGMENT = 1;
|
|
||||||
final static int MYANIMELIST_FRAGMENT = 2;
|
|
||||||
|
|
||||||
public MangaDetailAdapter(FragmentManager fm, Context context) {
|
|
||||||
super(fm);
|
|
||||||
tabTitles = new String[]{
|
|
||||||
context.getString(R.string.manga_detail_tab),
|
|
||||||
context.getString(R.string.manga_chapters_tab),
|
|
||||||
"MAL"
|
|
||||||
};
|
|
||||||
|
|
||||||
pageCount = 2;
|
|
||||||
if (!isOnline && mangaSyncManager.getMyAnimeList().isLogged())
|
|
||||||
pageCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return pageCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Fragment getItem(int position) {
|
|
||||||
switch (position) {
|
|
||||||
case INFO_FRAGMENT:
|
|
||||||
return MangaInfoFragment.newInstance();
|
|
||||||
case CHAPTERS_FRAGMENT:
|
|
||||||
return ChaptersFragment.newInstance();
|
|
||||||
case MYANIMELIST_FRAGMENT:
|
|
||||||
return MyAnimeListFragment.newInstance();
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence getPageTitle(int position) {
|
|
||||||
// Generate title based on item position
|
|
||||||
return tabTitles[position];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
118
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt
Normal file
118
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.manga
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.ActivityCompat
|
||||||
|
import android.support.v4.app.Fragment
|
||||||
|
import android.support.v4.app.FragmentManager
|
||||||
|
import android.support.v4.app.FragmentPagerAdapter
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
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 kotlinx.android.synthetic.main.activity_manga.*
|
||||||
|
import kotlinx.android.synthetic.main.tab_layout.*
|
||||||
|
import kotlinx.android.synthetic.main.toolbar.*
|
||||||
|
import nucleus.factory.RequiresPresenter
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
|
||||||
|
@RequiresPresenter(MangaPresenter::class)
|
||||||
|
class MangaActivity : BaseRxActivity<MangaPresenter>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val FROM_CATALOGUE = "from_catalogue"
|
||||||
|
val INFO_FRAGMENT = 0
|
||||||
|
val CHAPTERS_FRAGMENT = 1
|
||||||
|
val MYANIMELIST_FRAGMENT = 2
|
||||||
|
|
||||||
|
fun newIntent(context: Context, manga: Manga?): Intent {
|
||||||
|
val intent = Intent(context, MangaActivity::class.java)
|
||||||
|
if (manga != null) {
|
||||||
|
EventBus.getDefault().postSticky(manga)
|
||||||
|
}
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var adapter: MangaDetailAdapter
|
||||||
|
|
||||||
|
var isCatalogueManga: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
setContentView(R.layout.activity_manga)
|
||||||
|
|
||||||
|
setupToolbar(toolbar)
|
||||||
|
|
||||||
|
isCatalogueManga = intent.getBooleanExtra(FROM_CATALOGUE, false)
|
||||||
|
|
||||||
|
adapter = MangaDetailAdapter(supportFragmentManager, this)
|
||||||
|
view_pager.adapter = adapter
|
||||||
|
|
||||||
|
tabs.setupWithViewPager(view_pager)
|
||||||
|
|
||||||
|
if (!isCatalogueManga)
|
||||||
|
view_pager.currentItem = CHAPTERS_FRAGMENT
|
||||||
|
|
||||||
|
requestPermissionsOnMarshmallow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSetManga(manga: Manga) {
|
||||||
|
setToolbarTitle(manga.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestPermissionsOnMarshmallow() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
if (ContextCompat.checkSelfPermission(this,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|
||||||
|
ActivityCompat.requestPermissions(this,
|
||||||
|
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE),
|
||||||
|
1)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class MangaDetailAdapter(fm: FragmentManager, activity: MangaActivity) : FragmentPagerAdapter(fm) {
|
||||||
|
|
||||||
|
private var pageCount: Int = 0
|
||||||
|
private val tabTitles = arrayOf(activity.getString(R.string.manga_detail_tab),
|
||||||
|
activity.getString(R.string.manga_chapters_tab), "MAL")
|
||||||
|
|
||||||
|
init {
|
||||||
|
pageCount = 2
|
||||||
|
if (!activity.isCatalogueManga && activity.presenter.syncManager.myAnimeList.isLogged)
|
||||||
|
pageCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return pageCount
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPageTitle(position: Int): CharSequence {
|
||||||
|
// Generate title based on item position
|
||||||
|
return tabTitles[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,56 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
|
||||||
import eu.kanade.tachiyomi.event.MangaEvent;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
|
||||||
import icepick.State;
|
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
public class MangaPresenter extends BasePresenter<MangaActivity> {
|
|
||||||
|
|
||||||
@Inject DatabaseHelper db;
|
|
||||||
|
|
||||||
@State Manga manga;
|
|
||||||
|
|
||||||
private static final int GET_MANGA = 1;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedState) {
|
|
||||||
super.onCreate(savedState);
|
|
||||||
|
|
||||||
restartableLatestCache(GET_MANGA, this::getMangaObservable, MangaActivity::setManga);
|
|
||||||
|
|
||||||
if (savedState == null)
|
|
||||||
registerForEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
// Avoid new instances receiving wrong manga
|
|
||||||
EventBus.getDefault().removeStickyEvent(MangaEvent.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<Manga> getMangaObservable() {
|
|
||||||
return Observable.just(manga)
|
|
||||||
.doOnNext(manga -> EventBus.getDefault().postSticky(new MangaEvent(manga)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
|
||||||
public void onEvent(Manga manga) {
|
|
||||||
EventBus.getDefault().removeStickyEvent(manga);
|
|
||||||
unregisterForEvents();
|
|
||||||
this.manga = manga;
|
|
||||||
start(GET_MANGA);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
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.event.MangaEvent
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.greenrobot.eventbus.Subscribe
|
||||||
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
import rx.Observable
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of [MangaActivity].
|
||||||
|
*/
|
||||||
|
class MangaPresenter : BasePresenter<MangaActivity>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database helper.
|
||||||
|
*/
|
||||||
|
@Inject lateinit var db: DatabaseHelper
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manga sync manager.
|
||||||
|
*/
|
||||||
|
@Inject lateinit var syncManager: MangaSyncManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manga associated with this instance.
|
||||||
|
*/
|
||||||
|
lateinit var manga: Manga
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key to save and restore [manga] from a bundle.
|
||||||
|
*/
|
||||||
|
private val MANGA_KEY = "manga_key"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of the restartable that notifies the view of a manga.
|
||||||
|
*/
|
||||||
|
private val GET_MANGA = 1
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
if (savedState != null) {
|
||||||
|
manga = savedState.getSerializable(MANGA_KEY) as Manga
|
||||||
|
}
|
||||||
|
|
||||||
|
restartableLatestCache(GET_MANGA,
|
||||||
|
{ Observable.just(manga)
|
||||||
|
.doOnNext { EventBus.getDefault().postSticky(MangaEvent(it)) } },
|
||||||
|
{ view, manga -> view.onSetManga(manga) })
|
||||||
|
|
||||||
|
if (savedState == null) {
|
||||||
|
registerForEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
// Avoid new instances receiving wrong manga
|
||||||
|
EventBus.getDefault().removeStickyEvent(MangaEvent::class.java)
|
||||||
|
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSave(state: Bundle) {
|
||||||
|
state.putSerializable(MANGA_KEY, manga)
|
||||||
|
super.onSave(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
|
fun onEvent(manga: Manga) {
|
||||||
|
EventBus.getDefault().removeStickyEvent(manga)
|
||||||
|
unregisterForEvents()
|
||||||
|
this.manga = manga
|
||||||
|
start(GET_MANGA)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,57 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.chapter;
|
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
|
||||||
|
|
||||||
public class ChaptersAdapter extends FlexibleAdapter<ChaptersHolder, Chapter> {
|
|
||||||
|
|
||||||
private ChaptersFragment fragment;
|
|
||||||
|
|
||||||
public ChaptersAdapter(ChaptersFragment fragment) {
|
|
||||||
this.fragment = fragment;
|
|
||||||
mItems = new ArrayList<>();
|
|
||||||
setHasStableIds(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDataSet(String param) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChaptersHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
View v = LayoutInflater.from(fragment.getActivity()).inflate(R.layout.item_chapter, parent, false);
|
|
||||||
return new ChaptersHolder(v, this, fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(ChaptersHolder holder, int position) {
|
|
||||||
final Chapter chapter = getItem(position);
|
|
||||||
final Manga manga = fragment.getPresenter().getManga();
|
|
||||||
holder.onSetValues(chapter, manga);
|
|
||||||
|
|
||||||
//When user scrolls this bind the correct selection status
|
|
||||||
holder.itemView.setActivated(isSelected(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
return mItems.get(position).id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItems(List<Chapter> chapters) {
|
|
||||||
mItems = chapters;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChaptersFragment getFragment() {
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
|
|
||||||
|
class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<ChaptersHolder, Chapter>() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
setHasStableIds(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateDataSet(param: String) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChaptersHolder {
|
||||||
|
val v = parent.inflate(R.layout.item_chapter)
|
||||||
|
return ChaptersHolder(v, this, fragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ChaptersHolder, position: Int) {
|
||||||
|
val chapter = getItem(position)
|
||||||
|
val manga = fragment.presenter.manga
|
||||||
|
holder.onSetValues(chapter, manga)
|
||||||
|
|
||||||
|
//When user scrolls this bind the correct selection status
|
||||||
|
holder.itemView.isActivated = isSelected(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return mItems[position].id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setItems(chapters: List<Chapter>) {
|
||||||
|
mItems = chapters
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,439 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.chapter;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
|
||||||
import android.support.v7.view.ActionMode;
|
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService;
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaActivity;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil;
|
|
||||||
import nucleus.factory.RequiresPresenter;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
|
||||||
import rx.schedulers.Schedulers;
|
|
||||||
|
|
||||||
@RequiresPresenter(ChaptersPresenter.class)
|
|
||||||
public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implements
|
|
||||||
ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
|
|
||||||
|
|
||||||
@Bind(R.id.chapter_list) RecyclerView recyclerView;
|
|
||||||
@Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefresh;
|
|
||||||
@Bind(R.id.toolbar_bottom) ViewGroup toolbarBottom;
|
|
||||||
|
|
||||||
@Bind(R.id.action_sort) ImageView sortBtn;
|
|
||||||
@Bind(R.id.action_next_unread) ImageView nextUnreadBtn;
|
|
||||||
@Bind(R.id.action_show_unread) CheckBox readCb;
|
|
||||||
@Bind(R.id.action_show_downloaded) CheckBox downloadedCb;
|
|
||||||
|
|
||||||
private ChaptersAdapter adapter;
|
|
||||||
private LinearLayoutManager linearLayout;
|
|
||||||
private ActionMode actionMode;
|
|
||||||
|
|
||||||
private Subscription downloadProgressSubscription;
|
|
||||||
|
|
||||||
public static ChaptersFragment newInstance() {
|
|
||||||
return new ChaptersFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle bundle) {
|
|
||||||
super.onCreate(bundle);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
View view = inflater.inflate(R.layout.fragment_manga_chapters, container, false);
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
// Init RecyclerView and adapter
|
|
||||||
linearLayout = new LinearLayoutManager(getActivity());
|
|
||||||
recyclerView.setLayoutManager(linearLayout);
|
|
||||||
recyclerView.addItemDecoration(new DividerItemDecoration(
|
|
||||||
ContextCompat.getDrawable(getContext(), R.drawable.line_divider)));
|
|
||||||
recyclerView.setHasFixedSize(true);
|
|
||||||
adapter = new ChaptersAdapter(this);
|
|
||||||
recyclerView.setAdapter(adapter);
|
|
||||||
|
|
||||||
swipeRefresh.setOnRefreshListener(this::fetchChapters);
|
|
||||||
|
|
||||||
nextUnreadBtn.setOnClickListener(v -> {
|
|
||||||
Chapter chapter = getPresenter().getNextUnreadChapter();
|
|
||||||
if (chapter != null) {
|
|
||||||
openChapter(chapter);
|
|
||||||
} else {
|
|
||||||
ToastUtil.showShort(getContext(), R.string.no_next_chapter);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
// Stop recycler's scrolling when onPause is called. If the activity is finishing
|
|
||||||
// the presenter will be destroyed, and it could cause NPE
|
|
||||||
// https://github.com/inorichi/tachiyomi/issues/159
|
|
||||||
recyclerView.stopScroll();
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.chapters, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.action_display_mode:
|
|
||||||
showDisplayModeDialog();
|
|
||||||
return true;
|
|
||||||
case R.id.manga_download:
|
|
||||||
showDownloadDialog();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNextManga(Manga manga) {
|
|
||||||
// Remove listeners before setting the values
|
|
||||||
readCb.setOnCheckedChangeListener(null);
|
|
||||||
downloadedCb.setOnCheckedChangeListener(null);
|
|
||||||
sortBtn.setOnClickListener(null);
|
|
||||||
|
|
||||||
// Set initial values
|
|
||||||
setReadFilter();
|
|
||||||
setDownloadedFilter();
|
|
||||||
setSortIcon();
|
|
||||||
|
|
||||||
// Init listeners
|
|
||||||
readCb.setOnCheckedChangeListener((arg, isChecked) ->
|
|
||||||
getPresenter().setReadFilter(isChecked));
|
|
||||||
downloadedCb.setOnCheckedChangeListener((v, isChecked) ->
|
|
||||||
getPresenter().setDownloadedFilter(isChecked));
|
|
||||||
sortBtn.setOnClickListener(v -> {
|
|
||||||
getPresenter().revertSortOrder();
|
|
||||||
setSortIcon();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNextChapters(List<Chapter> chapters) {
|
|
||||||
// If the list is empty, fetch chapters from source if the conditions are met
|
|
||||||
// We use presenter chapters instead because they are always unfiltered
|
|
||||||
if (getPresenter().getChapters().isEmpty())
|
|
||||||
initialFetchChapters();
|
|
||||||
|
|
||||||
destroyActionModeIfNeeded();
|
|
||||||
adapter.setItems(chapters);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialFetchChapters() {
|
|
||||||
// Only fetch if this view is from the catalog and it hasn't requested previously
|
|
||||||
if (isCatalogueManga() && !getPresenter().hasRequested()) {
|
|
||||||
fetchChapters();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fetchChapters() {
|
|
||||||
if (getPresenter().getManga() != null) {
|
|
||||||
swipeRefresh.setRefreshing(true);
|
|
||||||
getPresenter().fetchChaptersFromSource();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onFetchChaptersDone() {
|
|
||||||
swipeRefresh.setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onFetchChaptersError(Throwable error) {
|
|
||||||
swipeRefresh.setRefreshing(false);
|
|
||||||
ToastUtil.showShort(getContext(), error.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCatalogueManga() {
|
|
||||||
return ((MangaActivity) getActivity()).isCatalogueManga();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void openChapter(Chapter chapter) {
|
|
||||||
getPresenter().onOpenChapter(chapter);
|
|
||||||
Intent intent = ReaderActivity.newIntent(getActivity());
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showDisplayModeDialog() {
|
|
||||||
final Manga manga = getPresenter().getManga();
|
|
||||||
if (manga == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Get available modes, ids and the selected mode
|
|
||||||
String[] modes = {getString(R.string.show_title), getString(R.string.show_chapter_number)};
|
|
||||||
int[] ids = {Manga.DISPLAY_NAME, Manga.DISPLAY_NUMBER};
|
|
||||||
int selectedIndex = manga.getDisplayMode() == Manga.DISPLAY_NAME ? 0 : 1;
|
|
||||||
|
|
||||||
new MaterialDialog.Builder(getActivity())
|
|
||||||
.title(R.string.action_display_mode)
|
|
||||||
.items(modes)
|
|
||||||
.itemsIds(ids)
|
|
||||||
.itemsCallbackSingleChoice(selectedIndex, (dialog, itemView, which, text) -> {
|
|
||||||
// Save the new display mode
|
|
||||||
getPresenter().setDisplayMode(itemView.getId());
|
|
||||||
// Refresh ui
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showDownloadDialog() {
|
|
||||||
|
|
||||||
// Get available modes
|
|
||||||
String[] modes = {getString(R.string.download_all), getString(R.string.download_unread)};
|
|
||||||
|
|
||||||
new MaterialDialog.Builder(getActivity())
|
|
||||||
.title(R.string.manga_download)
|
|
||||||
.items(modes)
|
|
||||||
.itemsCallback((dialog, view, i, charSequence) -> {
|
|
||||||
List<Chapter> chapters = new ArrayList<>();
|
|
||||||
|
|
||||||
for(Chapter chapter : getPresenter().getChapters()) {
|
|
||||||
if(!chapter.isDownloaded()) {
|
|
||||||
if(i == 0 || (i == 1 && !chapter.read)) {
|
|
||||||
chapters.add(chapter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(chapters.size() > 0) {
|
|
||||||
onDownload(Observable.from(chapters));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.negativeText(R.string.button_cancel)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void observeChapterDownloadProgress() {
|
|
||||||
downloadProgressSubscription = getPresenter().getDownloadProgressObs()
|
|
||||||
.subscribe(this::onDownloadProgressChange,
|
|
||||||
error -> { /* TODO getting a NPE sometimes on 'manga' from presenter */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unsubscribeChapterDownloadProgress() {
|
|
||||||
if (downloadProgressSubscription != null)
|
|
||||||
downloadProgressSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDownloadProgressChange(Download download) {
|
|
||||||
ChaptersHolder holder = getHolder(download.chapter);
|
|
||||||
if (holder != null)
|
|
||||||
holder.onProgressChange(getContext(), download.downloadedImages, download.pages.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onChapterStatusChange(Download download) {
|
|
||||||
ChaptersHolder holder = getHolder(download.chapter);
|
|
||||||
if (holder != null)
|
|
||||||
holder.onStatusChange(download.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private ChaptersHolder getHolder(Chapter chapter) {
|
|
||||||
return (ChaptersHolder) recyclerView.findViewHolderForItemId(chapter.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
|
||||||
mode.getMenuInflater().inflate(R.menu.chapter_selection, menu);
|
|
||||||
adapter.setMode(ChaptersAdapter.MODE_MULTI);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.action_select_all:
|
|
||||||
return onSelectAll();
|
|
||||||
case R.id.action_mark_as_read:
|
|
||||||
return onMarkAsRead(getSelectedChapters());
|
|
||||||
case R.id.action_mark_as_unread:
|
|
||||||
return onMarkAsUnread(getSelectedChapters());
|
|
||||||
case R.id.action_download:
|
|
||||||
return onDownload(getSelectedChapters());
|
|
||||||
case R.id.action_delete:
|
|
||||||
return onDelete(getSelectedChapters());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyActionMode(ActionMode mode) {
|
|
||||||
adapter.setMode(ChaptersAdapter.MODE_SINGLE);
|
|
||||||
adapter.clearSelection();
|
|
||||||
actionMode = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<Chapter> getSelectedChapters() {
|
|
||||||
// Create a blocking copy of the selected chapters.
|
|
||||||
// When the action mode is closed the list is cleared. If we use background
|
|
||||||
// threads with this observable, some emissions could be lost.
|
|
||||||
List<Chapter> chapters = Observable.from(adapter.getSelectedItems())
|
|
||||||
.map(adapter::getItem).toList().toBlocking().single();
|
|
||||||
|
|
||||||
return Observable.from(chapters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroyActionModeIfNeeded() {
|
|
||||||
if (actionMode != null) {
|
|
||||||
actionMode.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean onSelectAll() {
|
|
||||||
adapter.selectAll();
|
|
||||||
setContextTitle(adapter.getSelectedItemCount());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean onMarkAsRead(Observable<Chapter> chapters) {
|
|
||||||
getPresenter().markChaptersRead(chapters, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean onMarkAsUnread(Observable<Chapter> chapters) {
|
|
||||||
getPresenter().markChaptersRead(chapters, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onMarkPreviousAsRead(Chapter chapter) {
|
|
||||||
getPresenter().markPreviousChaptersAsRead(chapter);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean onDownload(Observable<Chapter> chapters) {
|
|
||||||
DownloadService.start(getActivity());
|
|
||||||
|
|
||||||
Observable<Chapter> observable = chapters
|
|
||||||
.doOnCompleted(adapter::notifyDataSetChanged);
|
|
||||||
|
|
||||||
getPresenter().downloadChapters(observable);
|
|
||||||
destroyActionModeIfNeeded();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean onDelete(Observable<Chapter> chapters) {
|
|
||||||
int size = adapter.getSelectedItemCount();
|
|
||||||
|
|
||||||
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
|
|
||||||
.title(R.string.deleting)
|
|
||||||
.progress(false, size, true)
|
|
||||||
.cancelable(false)
|
|
||||||
.show();
|
|
||||||
|
|
||||||
Observable<Chapter> observable = chapters
|
|
||||||
.concatMap(chapter -> {
|
|
||||||
getPresenter().deleteChapter(chapter);
|
|
||||||
return Observable.just(chapter);
|
|
||||||
})
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnNext(chapter -> {
|
|
||||||
dialog.incrementProgress(1);
|
|
||||||
chapter.status = Download.NOT_DOWNLOADED;
|
|
||||||
})
|
|
||||||
.doOnCompleted(adapter::notifyDataSetChanged)
|
|
||||||
.finallyDo(dialog::dismiss);
|
|
||||||
|
|
||||||
getPresenter().deleteChapters(observable);
|
|
||||||
destroyActionModeIfNeeded();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onListItemClick(int position) {
|
|
||||||
if (actionMode != null && adapter.getMode() == ChaptersAdapter.MODE_MULTI) {
|
|
||||||
toggleSelection(position);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
openChapter(adapter.getItem(position));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onListItemLongClick(int position) {
|
|
||||||
if (actionMode == null)
|
|
||||||
actionMode = getBaseActivity().startSupportActionMode(this);
|
|
||||||
|
|
||||||
toggleSelection(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toggleSelection(int position) {
|
|
||||||
adapter.toggleSelection(position, false);
|
|
||||||
|
|
||||||
int count = adapter.getSelectedItemCount();
|
|
||||||
if (count == 0) {
|
|
||||||
actionMode.finish();
|
|
||||||
} else {
|
|
||||||
setContextTitle(count);
|
|
||||||
actionMode.invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setContextTitle(int count) {
|
|
||||||
actionMode.setTitle(getString(R.string.label_selected, count));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSortIcon() {
|
|
||||||
if (sortBtn != null) {
|
|
||||||
boolean aToZ = getPresenter().getSortOrder();
|
|
||||||
sortBtn.setImageResource(!aToZ ? R.drawable.ic_expand_less_white_36dp : R.drawable.ic_expand_more_white_36dp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReadFilter() {
|
|
||||||
if (readCb != null) {
|
|
||||||
readCb.setChecked(getPresenter().onlyUnread());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDownloadedFilter() {
|
|
||||||
if (downloadedCb != null) {
|
|
||||||
downloadedCb.setChecked(getPresenter().onlyDownloaded());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,362 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.support.v7.view.ActionMode
|
||||||
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
|
import android.view.*
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
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.ui.base.adapter.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration
|
||||||
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.MangaActivity
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import eu.kanade.tachiyomi.util.toast
|
||||||
|
import kotlinx.android.synthetic.main.fragment_manga_chapters.*
|
||||||
|
import nucleus.factory.RequiresPresenter
|
||||||
|
import rx.Observable
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@RequiresPresenter(ChaptersPresenter::class)
|
||||||
|
class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Creates a new instance of this fragment.
|
||||||
|
*
|
||||||
|
* @return a new instance of [ChaptersFragment].
|
||||||
|
*/
|
||||||
|
fun newInstance(): ChaptersFragment {
|
||||||
|
return ChaptersFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter containing a list of chapters.
|
||||||
|
*/
|
||||||
|
private lateinit var adapter: ChaptersAdapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action mode for multiple selection.
|
||||||
|
*/
|
||||||
|
private var actionMode: ActionMode? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_manga_chapters, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
// Init RecyclerView and adapter
|
||||||
|
adapter = ChaptersAdapter(this)
|
||||||
|
|
||||||
|
recycler.adapter = adapter
|
||||||
|
recycler.layoutManager = LinearLayoutManager(activity)
|
||||||
|
recycler.addItemDecoration(DividerItemDecoration(
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.line_divider)))
|
||||||
|
recycler.setHasFixedSize(true)
|
||||||
|
|
||||||
|
swipe_refresh.setOnRefreshListener { fetchChapters() }
|
||||||
|
|
||||||
|
next_unread_btn.setOnClickListener { v ->
|
||||||
|
val chapter = presenter.getNextUnreadChapter()
|
||||||
|
if (chapter != null) {
|
||||||
|
openChapter(chapter)
|
||||||
|
} else {
|
||||||
|
context.toast(R.string.no_next_chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
// Stop recycler's scrolling when onPause is called. If the activity is finishing
|
||||||
|
// the presenter will be destroyed, and it could cause NPE
|
||||||
|
// https://github.com/inorichi/tachiyomi/issues/159
|
||||||
|
recycler.stopScroll()
|
||||||
|
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.chapters, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.action_display_mode -> showDisplayModeDialog()
|
||||||
|
R.id.manga_download -> showDownloadDialog()
|
||||||
|
else -> return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNextManga(manga: Manga) {
|
||||||
|
// Remove listeners before setting the values
|
||||||
|
show_unread.setOnCheckedChangeListener(null)
|
||||||
|
show_downloaded.setOnCheckedChangeListener(null)
|
||||||
|
sort_btn.setOnClickListener(null)
|
||||||
|
|
||||||
|
// Set initial values
|
||||||
|
setReadFilter()
|
||||||
|
setDownloadedFilter()
|
||||||
|
setSortIcon()
|
||||||
|
|
||||||
|
// Init listeners
|
||||||
|
show_unread.setOnCheckedChangeListener { arg, isChecked -> presenter.setReadFilter(isChecked) }
|
||||||
|
show_downloaded.setOnCheckedChangeListener { v, isChecked -> presenter.setDownloadedFilter(isChecked) }
|
||||||
|
sort_btn.setOnClickListener {
|
||||||
|
presenter.revertSortOrder()
|
||||||
|
setSortIcon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNextChapters(chapters: List<Chapter>) {
|
||||||
|
// If the list is empty, fetch chapters from source if the conditions are met
|
||||||
|
// We use presenter chapters instead because they are always unfiltered
|
||||||
|
if (presenter.chapters.isEmpty())
|
||||||
|
initialFetchChapters()
|
||||||
|
|
||||||
|
destroyActionModeIfNeeded()
|
||||||
|
adapter.setItems(chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initialFetchChapters() {
|
||||||
|
// Only fetch if this view is from the catalog and it hasn't requested previously
|
||||||
|
if (isCatalogueManga && !presenter.hasRequested) {
|
||||||
|
fetchChapters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchChapters() {
|
||||||
|
swipe_refresh.isRefreshing = true
|
||||||
|
presenter.fetchChaptersFromSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onFetchChaptersDone() {
|
||||||
|
swipe_refresh.isRefreshing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onFetchChaptersError(error: Throwable) {
|
||||||
|
swipe_refresh.isRefreshing = false
|
||||||
|
context.toast(error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isCatalogueManga: Boolean
|
||||||
|
get() = (activity as MangaActivity).isCatalogueManga
|
||||||
|
|
||||||
|
protected fun openChapter(chapter: Chapter) {
|
||||||
|
presenter.onOpenChapter(chapter)
|
||||||
|
val intent = ReaderActivity.newIntent(activity)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDisplayModeDialog() {
|
||||||
|
|
||||||
|
// Get available modes, ids and the selected mode
|
||||||
|
val modes = listOf(getString(R.string.show_title), getString(R.string.show_chapter_number))
|
||||||
|
val ids = intArrayOf(Manga.DISPLAY_NAME, Manga.DISPLAY_NUMBER)
|
||||||
|
val selectedIndex = if (presenter.manga.displayMode == Manga.DISPLAY_NAME) 0 else 1
|
||||||
|
|
||||||
|
MaterialDialog.Builder(activity)
|
||||||
|
.title(R.string.action_display_mode)
|
||||||
|
.items(modes)
|
||||||
|
.itemsIds(ids)
|
||||||
|
.itemsCallbackSingleChoice(selectedIndex) { dialog, itemView, which, text ->
|
||||||
|
// Save the new display mode
|
||||||
|
presenter.setDisplayMode(itemView.id)
|
||||||
|
// Refresh ui
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDownloadDialog() {
|
||||||
|
// Get available modes
|
||||||
|
val modes = listOf(getString(R.string.download_all), getString(R.string.download_unread))
|
||||||
|
|
||||||
|
MaterialDialog.Builder(activity)
|
||||||
|
.title(R.string.manga_download)
|
||||||
|
.negativeText(android.R.string.cancel)
|
||||||
|
.items(modes)
|
||||||
|
.itemsCallback { dialog, view, i, charSequence ->
|
||||||
|
val chapters = ArrayList<Chapter>()
|
||||||
|
|
||||||
|
for (chapter in presenter.chapters) {
|
||||||
|
if (!chapter.isDownloaded) {
|
||||||
|
if (i == 0 || (i == 1 && !chapter.read)) {
|
||||||
|
chapters.add(chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chapters.size > 0) {
|
||||||
|
onDownload(Observable.from(chapters))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onChapterStatusChange(download: Download) {
|
||||||
|
getHolder(download.chapter)?.notifyStatus(download.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHolder(chapter: Chapter): ChaptersHolder? {
|
||||||
|
return recycler.findViewHolderForItemId(chapter.id) as? ChaptersHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||||
|
mode.menuInflater.inflate(R.menu.chapter_selection, menu)
|
||||||
|
adapter.mode = FlexibleAdapter.MODE_MULTI
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.action_select_all -> onSelectAll()
|
||||||
|
R.id.action_mark_as_read -> onMarkAsRead(getSelectedChapters())
|
||||||
|
R.id.action_mark_as_unread -> onMarkAsUnread(getSelectedChapters())
|
||||||
|
R.id.action_download -> onDownload(getSelectedChapters())
|
||||||
|
R.id.action_delete -> onDelete(getSelectedChapters())
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode) {
|
||||||
|
adapter.mode = FlexibleAdapter.MODE_SINGLE
|
||||||
|
adapter.clearSelection()
|
||||||
|
actionMode = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelectedChapters(): Observable<Chapter> {
|
||||||
|
val chapters = adapter.selectedItems.map { adapter.getItem(it) }
|
||||||
|
return Observable.from(chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroyActionModeIfNeeded() {
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun onSelectAll() {
|
||||||
|
adapter.selectAll()
|
||||||
|
setContextTitle(adapter.selectedItemCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMarkAsRead(chapters: Observable<Chapter>) {
|
||||||
|
presenter.markChaptersRead(chapters, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMarkAsUnread(chapters: Observable<Chapter>) {
|
||||||
|
presenter.markChaptersRead(chapters, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMarkPreviousAsRead(chapter: Chapter) {
|
||||||
|
presenter.markPreviousChaptersAsRead(chapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDownload(chapters: Observable<Chapter>) {
|
||||||
|
DownloadService.start(activity)
|
||||||
|
|
||||||
|
val observable = chapters.doOnCompleted { adapter.notifyDataSetChanged() }
|
||||||
|
|
||||||
|
presenter.downloadChapters(observable)
|
||||||
|
destroyActionModeIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDelete(chapters: Observable<Chapter>) {
|
||||||
|
val size = adapter.selectedItemCount
|
||||||
|
|
||||||
|
val dialog = MaterialDialog.Builder(activity)
|
||||||
|
.title(R.string.deleting)
|
||||||
|
.progress(false, size, true)
|
||||||
|
.cancelable(false)
|
||||||
|
.show()
|
||||||
|
|
||||||
|
val observable = chapters
|
||||||
|
.concatMap { chapter ->
|
||||||
|
presenter.deleteChapter(chapter)
|
||||||
|
Observable.just(chapter)
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnNext { chapter ->
|
||||||
|
dialog.incrementProgress(1)
|
||||||
|
chapter.status = Download.NOT_DOWNLOADED
|
||||||
|
}
|
||||||
|
.doOnCompleted { adapter.notifyDataSetChanged() }
|
||||||
|
.doAfterTerminate { dialog.dismiss() }
|
||||||
|
|
||||||
|
presenter.deleteChapters(observable)
|
||||||
|
destroyActionModeIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onListItemClick(position: Int): Boolean {
|
||||||
|
if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) {
|
||||||
|
toggleSelection(position)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
openChapter(adapter.getItem(position))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onListItemLongClick(position: Int) {
|
||||||
|
if (actionMode == null)
|
||||||
|
actionMode = baseActivity.startSupportActionMode(this)
|
||||||
|
|
||||||
|
toggleSelection(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleSelection(position: Int) {
|
||||||
|
adapter.toggleSelection(position, false)
|
||||||
|
|
||||||
|
val count = adapter.selectedItemCount
|
||||||
|
if (count == 0) {
|
||||||
|
actionMode?.finish()
|
||||||
|
} else {
|
||||||
|
setContextTitle(count)
|
||||||
|
actionMode?.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setContextTitle(count: Int) {
|
||||||
|
actionMode?.title = getString(R.string.label_selected, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSortIcon() {
|
||||||
|
sort_btn?.let {
|
||||||
|
val aToZ = presenter.sortOrder()
|
||||||
|
it.setImageResource(if (!aToZ) R.drawable.ic_expand_less_white_36dp else R.drawable.ic_expand_more_white_36dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setReadFilter() {
|
||||||
|
show_unread?.let {
|
||||||
|
it.isChecked = presenter.onlyUnread()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDownloadedFilter() {
|
||||||
|
show_downloaded?.let {
|
||||||
|
it.isChecked = presenter.onlyDownloaded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,150 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.chapter;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.PopupMenu;
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.text.DecimalFormatSymbols;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
public class ChaptersHolder extends FlexibleViewHolder {
|
|
||||||
|
|
||||||
private final ChaptersAdapter adapter;
|
|
||||||
private final int readColor;
|
|
||||||
private final int unreadColor;
|
|
||||||
private final DecimalFormat decimalFormat;
|
|
||||||
private final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
|
|
||||||
@Bind(R.id.chapter_title) TextView title;
|
|
||||||
@Bind(R.id.download_text) TextView downloadText;
|
|
||||||
@Bind(R.id.chapter_menu) RelativeLayout chapterMenu;
|
|
||||||
@Bind(R.id.chapter_pages) TextView pages;
|
|
||||||
@Bind(R.id.chapter_date) TextView date;
|
|
||||||
private Context context;
|
|
||||||
private Chapter item;
|
|
||||||
|
|
||||||
public ChaptersHolder(View view, ChaptersAdapter adapter, OnListItemClickListener listener) {
|
|
||||||
super(view, adapter, listener);
|
|
||||||
this.adapter = adapter;
|
|
||||||
context = view.getContext();
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
readColor = ContextCompat.getColor(view.getContext(), R.color.hint_text);
|
|
||||||
unreadColor = ContextCompat.getColor(view.getContext(), R.color.primary_text);
|
|
||||||
|
|
||||||
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
|
|
||||||
symbols.setDecimalSeparator('.');
|
|
||||||
decimalFormat = new DecimalFormat("#.###", symbols);
|
|
||||||
|
|
||||||
chapterMenu.setOnClickListener(v -> v.post(() -> showPopupMenu(v)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSetValues(Chapter chapter, Manga manga) {
|
|
||||||
this.item = chapter;
|
|
||||||
String name;
|
|
||||||
switch (manga.getDisplayMode()) {
|
|
||||||
case Manga.DISPLAY_NAME:
|
|
||||||
default:
|
|
||||||
name = chapter.name;
|
|
||||||
break;
|
|
||||||
case Manga.DISPLAY_NUMBER:
|
|
||||||
String formattedNumber = decimalFormat.format(chapter.chapter_number);
|
|
||||||
name = context.getString(R.string.display_mode_chapter, formattedNumber);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
title.setText(name);
|
|
||||||
title.setTextColor(chapter.read ? readColor : unreadColor);
|
|
||||||
date.setTextColor(chapter.read ? readColor : unreadColor);
|
|
||||||
|
|
||||||
if (!chapter.read && chapter.last_page_read > 0) {
|
|
||||||
pages.setText(context.getString(R.string.chapter_progress, chapter.last_page_read + 1));
|
|
||||||
} else {
|
|
||||||
pages.setText("");
|
|
||||||
}
|
|
||||||
|
|
||||||
onStatusChange(chapter.status);
|
|
||||||
date.setText(df.format(new Date(chapter.date_upload)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onStatusChange(int status) {
|
|
||||||
switch (status) {
|
|
||||||
case Download.QUEUE:
|
|
||||||
downloadText.setText(R.string.chapter_queued); break;
|
|
||||||
case Download.DOWNLOADING:
|
|
||||||
downloadText.setText(R.string.chapter_downloading); break;
|
|
||||||
case Download.DOWNLOADED:
|
|
||||||
downloadText.setText(R.string.chapter_downloaded); break;
|
|
||||||
case Download.ERROR:
|
|
||||||
downloadText.setText(R.string.chapter_error); break;
|
|
||||||
default:
|
|
||||||
downloadText.setText(""); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onProgressChange(Context context, int downloaded, int total) {
|
|
||||||
downloadText.setText(context.getString(
|
|
||||||
R.string.chapter_downloading_progress, downloaded, total));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showPopupMenu(View view) {
|
|
||||||
// Create a PopupMenu, giving it the clicked view for an anchor
|
|
||||||
PopupMenu popup = new PopupMenu(adapter.getFragment().getActivity(), view);
|
|
||||||
|
|
||||||
// Inflate our menu resource into the PopupMenu's Menu
|
|
||||||
popup.getMenuInflater().inflate(R.menu.chapter_single, popup.getMenu());
|
|
||||||
|
|
||||||
// Hide download and show delete if the chapter is downloaded and
|
|
||||||
if(item.isDownloaded()) {
|
|
||||||
Menu menu = popup.getMenu();
|
|
||||||
menu.findItem(R.id.action_download).setVisible(false);
|
|
||||||
menu.findItem(R.id.action_delete).setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide mark as unread when the chapter is unread
|
|
||||||
if(!item.read && item.last_page_read == 0) {
|
|
||||||
popup.getMenu().findItem(R.id.action_mark_as_unread).setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide mark as read when the chapter is read
|
|
||||||
if(item.read) {
|
|
||||||
popup.getMenu().findItem(R.id.action_mark_as_read).setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a listener so we are notified if a menu item is clicked
|
|
||||||
popup.setOnMenuItemClickListener(menuItem -> {
|
|
||||||
Observable<Chapter> chapter = Observable.just(item);
|
|
||||||
|
|
||||||
switch (menuItem.getItemId()) {
|
|
||||||
case R.id.action_download:
|
|
||||||
return adapter.getFragment().onDownload(chapter);
|
|
||||||
case R.id.action_delete:
|
|
||||||
return adapter.getFragment().onDelete(chapter);
|
|
||||||
case R.id.action_mark_as_read:
|
|
||||||
return adapter.getFragment().onMarkAsRead(chapter);
|
|
||||||
case R.id.action_mark_as_unread:
|
|
||||||
return adapter.getFragment().onMarkAsUnread(chapter);
|
|
||||||
case R.id.action_mark_previous_as_read:
|
|
||||||
return adapter.getFragment().onMarkPreviousAsRead(item);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Finally show the PopupMenu
|
|
||||||
popup.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.PopupMenu
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
|
import kotlinx.android.synthetic.main.item_chapter.view.*
|
||||||
|
import rx.Observable
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.text.DecimalFormatSymbols
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ChaptersHolder(private val view: View, private val adapter: ChaptersAdapter, listener: FlexibleViewHolder.OnListItemClickListener) :
|
||||||
|
FlexibleViewHolder(view, adapter, listener) {
|
||||||
|
|
||||||
|
private val readColor = ContextCompat.getColor(view.context, R.color.hint_text)
|
||||||
|
private val unreadColor = ContextCompat.getColor(view.context, R.color.primary_text)
|
||||||
|
private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' })
|
||||||
|
private val df = DateFormat.getDateInstance(DateFormat.SHORT)
|
||||||
|
|
||||||
|
private var item: Chapter? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.chapter_menu.setOnClickListener { v -> v.post { showPopupMenu(v) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSetValues(chapter: Chapter, manga: Manga?) = with(view) {
|
||||||
|
item = chapter
|
||||||
|
|
||||||
|
val name: String
|
||||||
|
when (manga?.displayMode) {
|
||||||
|
Manga.DISPLAY_NUMBER -> {
|
||||||
|
val formattedNumber = decimalFormat.format(chapter.chapter_number.toDouble())
|
||||||
|
name = context.getString(R.string.display_mode_chapter, formattedNumber)
|
||||||
|
}
|
||||||
|
else -> name = chapter.name
|
||||||
|
}
|
||||||
|
|
||||||
|
chapter_title.text = name
|
||||||
|
chapter_title.setTextColor(if (chapter.read) readColor else unreadColor)
|
||||||
|
|
||||||
|
chapter_date.text = df.format(Date(chapter.date_upload))
|
||||||
|
chapter_date.setTextColor(if (chapter.read) readColor else unreadColor)
|
||||||
|
|
||||||
|
if (!chapter.read && chapter.last_page_read > 0) {
|
||||||
|
chapter_pages.text = context.getString(R.string.chapter_progress, chapter.last_page_read + 1)
|
||||||
|
} else {
|
||||||
|
chapter_pages.text = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyStatus(chapter.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notifyStatus(status: Int) = with(view) {
|
||||||
|
when (status) {
|
||||||
|
Download.QUEUE -> download_text.setText(R.string.chapter_queued)
|
||||||
|
Download.DOWNLOADING -> download_text.setText(R.string.chapter_downloading)
|
||||||
|
Download.DOWNLOADED -> download_text.setText(R.string.chapter_downloaded)
|
||||||
|
Download.ERROR -> download_text.setText(R.string.chapter_error)
|
||||||
|
else -> download_text.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onProgressChange(context: Context, downloaded: Int, total: Int) {
|
||||||
|
view.download_text.text = context.getString(
|
||||||
|
R.string.chapter_downloading_progress, downloaded, total)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPopupMenu(view: View) = item?.let { item ->
|
||||||
|
// Create a PopupMenu, giving it the clicked view for an anchor
|
||||||
|
val popup = PopupMenu(view.context, view)
|
||||||
|
|
||||||
|
// Inflate our menu resource into the PopupMenu's Menu
|
||||||
|
popup.menuInflater.inflate(R.menu.chapter_single, popup.menu)
|
||||||
|
|
||||||
|
// Hide download and show delete if the chapter is downloaded
|
||||||
|
if (item.isDownloaded) {
|
||||||
|
popup.menu.findItem(R.id.action_download).isVisible = false
|
||||||
|
popup.menu.findItem(R.id.action_delete).isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide mark as unread when the chapter is unread
|
||||||
|
if (!item.read && item.last_page_read == 0) {
|
||||||
|
popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide mark as read when the chapter is read
|
||||||
|
if (item.read) {
|
||||||
|
popup.menu.findItem(R.id.action_mark_as_read).isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a listener so we are notified if a menu item is clicked
|
||||||
|
popup.setOnMenuItemClickListener { menuItem ->
|
||||||
|
val chapter = Observable.just(item)
|
||||||
|
|
||||||
|
when (menuItem.itemId) {
|
||||||
|
R.id.action_download -> adapter.fragment.onDownload(chapter)
|
||||||
|
R.id.action_delete -> adapter.fragment.onDelete(chapter)
|
||||||
|
R.id.action_mark_as_read -> adapter.fragment.onMarkAsRead(chapter)
|
||||||
|
R.id.action_mark_as_unread -> adapter.fragment.onMarkAsUnread(chapter)
|
||||||
|
R.id.action_mark_previous_as_read -> adapter.fragment.onMarkPreviousAsRead(item)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally show the PopupMenu
|
||||||
|
popup.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,286 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.chapter;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager;
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download;
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager;
|
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source;
|
|
||||||
import eu.kanade.tachiyomi.event.ChapterCountEvent;
|
|
||||||
import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
|
|
||||||
import eu.kanade.tachiyomi.event.MangaEvent;
|
|
||||||
import eu.kanade.tachiyomi.event.ReaderEvent;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
|
||||||
import icepick.State;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
|
||||||
import rx.schedulers.Schedulers;
|
|
||||||
import rx.subjects.PublishSubject;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
|
|
||||||
|
|
||||||
@Inject DatabaseHelper db;
|
|
||||||
@Inject SourceManager sourceManager;
|
|
||||||
@Inject PreferencesHelper preferences;
|
|
||||||
@Inject DownloadManager downloadManager;
|
|
||||||
|
|
||||||
private Manga manga;
|
|
||||||
private Source source;
|
|
||||||
private List<Chapter> chapters;
|
|
||||||
@State boolean hasRequested;
|
|
||||||
|
|
||||||
private PublishSubject<List<Chapter>> chaptersSubject;
|
|
||||||
|
|
||||||
private static final int GET_MANGA = 1;
|
|
||||||
private static final int DB_CHAPTERS = 2;
|
|
||||||
private static final int FETCH_CHAPTERS = 3;
|
|
||||||
private static final int CHAPTER_STATUS_CHANGES = 4;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedState) {
|
|
||||||
super.onCreate(savedState);
|
|
||||||
|
|
||||||
chaptersSubject = PublishSubject.create();
|
|
||||||
|
|
||||||
startableLatestCache(GET_MANGA,
|
|
||||||
() -> Observable.just(manga),
|
|
||||||
ChaptersFragment::onNextManga);
|
|
||||||
|
|
||||||
startableLatestCache(DB_CHAPTERS,
|
|
||||||
this::getDbChaptersObs,
|
|
||||||
ChaptersFragment::onNextChapters);
|
|
||||||
|
|
||||||
startableFirst(FETCH_CHAPTERS,
|
|
||||||
this::getOnlineChaptersObs,
|
|
||||||
(view, result) -> view.onFetchChaptersDone(),
|
|
||||||
(view, error) -> view.onFetchChaptersError(error));
|
|
||||||
|
|
||||||
startableLatestCache(CHAPTER_STATUS_CHANGES,
|
|
||||||
this::getChapterStatusObs,
|
|
||||||
(view, download) -> view.onChapterStatusChange(download),
|
|
||||||
(view, error) -> Timber.e(error.getCause(), error.getMessage()));
|
|
||||||
|
|
||||||
registerForEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
unregisterForEvents();
|
|
||||||
EventBus.getDefault().removeStickyEvent(ChapterCountEvent.class);
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
|
||||||
public void onEvent(MangaEvent event) {
|
|
||||||
this.manga = event.manga;
|
|
||||||
start(GET_MANGA);
|
|
||||||
|
|
||||||
if (isUnsubscribed(DB_CHAPTERS)) {
|
|
||||||
source = sourceManager.get(manga.source);
|
|
||||||
start(DB_CHAPTERS);
|
|
||||||
|
|
||||||
add(db.getChapters(manga).asRxObservable()
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.doOnNext(chapters -> {
|
|
||||||
this.chapters = chapters;
|
|
||||||
EventBus.getDefault().postSticky(new ChapterCountEvent(chapters.size()));
|
|
||||||
for (Chapter chapter : chapters) {
|
|
||||||
setChapterStatus(chapter);
|
|
||||||
}
|
|
||||||
start(CHAPTER_STATUS_CHANGES);
|
|
||||||
})
|
|
||||||
.subscribe(chaptersSubject::onNext));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fetchChaptersFromSource() {
|
|
||||||
hasRequested = true;
|
|
||||||
start(FETCH_CHAPTERS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshChapters() {
|
|
||||||
chaptersSubject.onNext(chapters);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<Pair<Integer, Integer>> getOnlineChaptersObs() {
|
|
||||||
return source.pullChaptersFromNetwork(manga.url)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters, source))
|
|
||||||
.observeOn(AndroidSchedulers.mainThread());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<List<Chapter>> getDbChaptersObs() {
|
|
||||||
return chaptersSubject.flatMap(this::applyChapterFilters)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<List<Chapter>> applyChapterFilters(List<Chapter> chapters) {
|
|
||||||
Observable<Chapter> observable = Observable.from(chapters)
|
|
||||||
.subscribeOn(Schedulers.io());
|
|
||||||
if (onlyUnread()) {
|
|
||||||
observable = observable.filter(chapter -> !chapter.read);
|
|
||||||
}
|
|
||||||
if (onlyDownloaded()) {
|
|
||||||
observable = observable.filter(chapter -> chapter.status == Download.DOWNLOADED);
|
|
||||||
}
|
|
||||||
return observable.toSortedList((chapter, chapter2) -> getSortOrder() ?
|
|
||||||
Float.compare(chapter2.chapter_number, chapter.chapter_number) :
|
|
||||||
Float.compare(chapter.chapter_number, chapter2.chapter_number));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setChapterStatus(Chapter chapter) {
|
|
||||||
for (Download download : downloadManager.getQueue()) {
|
|
||||||
if (chapter.id.equals(download.chapter.id)) {
|
|
||||||
chapter.status = download.getStatus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadManager.isChapterDownloaded(source, manga, chapter)) {
|
|
||||||
chapter.status = Download.DOWNLOADED;
|
|
||||||
} else {
|
|
||||||
chapter.status = Download.NOT_DOWNLOADED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<Download> getChapterStatusObs() {
|
|
||||||
return downloadManager.getQueue().getStatusObservable()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.filter(download -> download.manga.id.equals(manga.id))
|
|
||||||
.doOnNext(this::updateChapterStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateChapterStatus(Download download) {
|
|
||||||
for (Chapter chapter : chapters) {
|
|
||||||
if (download.chapter.id.equals(chapter.id)) {
|
|
||||||
chapter.status = download.getStatus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (onlyDownloaded() && download.getStatus() == Download.DOWNLOADED)
|
|
||||||
refreshChapters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable<Download> getDownloadProgressObs() {
|
|
||||||
return downloadManager.getQueue().getProgressObservable()
|
|
||||||
.filter(download -> download.manga.id.equals(manga.id))
|
|
||||||
.observeOn(AndroidSchedulers.mainThread());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onOpenChapter(Chapter chapter) {
|
|
||||||
EventBus.getDefault().postSticky(new ReaderEvent(source, manga, chapter));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chapter getNextUnreadChapter() {
|
|
||||||
return db.getNextUnreadChapter(manga).executeAsBlocking();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markChaptersRead(Observable<Chapter> selectedChapters, boolean read) {
|
|
||||||
add(selectedChapters
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.map(chapter -> {
|
|
||||||
chapter.read = read;
|
|
||||||
if (!read) chapter.last_page_read = 0;
|
|
||||||
return chapter;
|
|
||||||
})
|
|
||||||
.toList()
|
|
||||||
.flatMap(chapters -> db.insertChapters(chapters).asRxObservable())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markPreviousChaptersAsRead(Chapter selected) {
|
|
||||||
Observable.from(chapters)
|
|
||||||
.filter(c -> c.chapter_number > -1 && c.chapter_number < selected.chapter_number)
|
|
||||||
.doOnNext(c -> c.read = true)
|
|
||||||
.toList()
|
|
||||||
.flatMap(chapters -> db.insertChapters(chapters).asRxObservable())
|
|
||||||
.subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void downloadChapters(Observable<Chapter> selectedChapters) {
|
|
||||||
add(selectedChapters
|
|
||||||
.toList()
|
|
||||||
.subscribe(chapters -> {
|
|
||||||
EventBus.getDefault().postSticky(new DownloadChaptersEvent(manga, chapters));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteChapters(Observable<Chapter> selectedChapters) {
|
|
||||||
add(selectedChapters
|
|
||||||
.subscribe(chapter -> {
|
|
||||||
downloadManager.getQueue().remove(chapter);
|
|
||||||
}, error -> {
|
|
||||||
Timber.e(error.getMessage());
|
|
||||||
}, () -> {
|
|
||||||
if (onlyDownloaded())
|
|
||||||
refreshChapters();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteChapter(Chapter chapter) {
|
|
||||||
downloadManager.deleteChapter(source, manga, chapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void revertSortOrder() {
|
|
||||||
manga.setChapterOrder(getSortOrder() ? Manga.SORT_ZA : Manga.SORT_AZ);
|
|
||||||
db.insertManga(manga).executeAsBlocking();
|
|
||||||
refreshChapters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReadFilter(boolean onlyUnread) {
|
|
||||||
manga.setReadFilter(onlyUnread ? Manga.SHOW_UNREAD : Manga.SHOW_ALL);
|
|
||||||
db.insertManga(manga).executeAsBlocking();
|
|
||||||
refreshChapters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDownloadedFilter(boolean onlyDownloaded) {
|
|
||||||
manga.setDownloadedFilter(onlyDownloaded ? Manga.SHOW_DOWNLOADED : Manga.SHOW_ALL);
|
|
||||||
db.insertManga(manga).executeAsBlocking();
|
|
||||||
refreshChapters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDisplayMode(int mode) {
|
|
||||||
manga.setDisplayMode(mode);
|
|
||||||
db.insertManga(manga).executeAsBlocking();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onlyDownloaded() {
|
|
||||||
return manga.getDownloadedFilter() == Manga.SHOW_DOWNLOADED;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onlyUnread() {
|
|
||||||
return manga.getReadFilter() == Manga.SHOW_UNREAD;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getSortOrder() {
|
|
||||||
return manga.sortChaptersAZ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Manga getManga() {
|
|
||||||
return manga;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Chapter> getChapters() {
|
|
||||||
return chapters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasRequested() {
|
|
||||||
return hasRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Pair
|
||||||
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
|
import eu.kanade.tachiyomi.data.source.base.Source
|
||||||
|
import eu.kanade.tachiyomi.event.ChapterCountEvent
|
||||||
|
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
|
||||||
|
import eu.kanade.tachiyomi.event.MangaEvent
|
||||||
|
import eu.kanade.tachiyomi.event.ReaderEvent
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.greenrobot.eventbus.Subscribe
|
||||||
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
import rx.Observable
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import rx.subjects.PublishSubject
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
|
||||||
|
|
||||||
|
@Inject lateinit var db: DatabaseHelper
|
||||||
|
@Inject lateinit var sourceManager: SourceManager
|
||||||
|
@Inject lateinit var preferences: PreferencesHelper
|
||||||
|
@Inject lateinit var downloadManager: DownloadManager
|
||||||
|
|
||||||
|
lateinit var manga: Manga
|
||||||
|
private set
|
||||||
|
|
||||||
|
lateinit var source: Source
|
||||||
|
private set
|
||||||
|
|
||||||
|
lateinit var chapters: List<Chapter>
|
||||||
|
private set
|
||||||
|
|
||||||
|
lateinit var chaptersSubject: PublishSubject<List<Chapter>>
|
||||||
|
private set
|
||||||
|
|
||||||
|
var hasRequested: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val GET_MANGA = 1
|
||||||
|
private val DB_CHAPTERS = 2
|
||||||
|
private val FETCH_CHAPTERS = 3
|
||||||
|
private val CHAPTER_STATUS_CHANGES = 4
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
chaptersSubject = PublishSubject.create()
|
||||||
|
|
||||||
|
startableLatestCache(GET_MANGA,
|
||||||
|
{ Observable.just(manga) },
|
||||||
|
{ view, manga -> view.onNextManga(manga) })
|
||||||
|
|
||||||
|
startableLatestCache(DB_CHAPTERS,
|
||||||
|
{ getDbChaptersObs() },
|
||||||
|
{ view, chapters -> view.onNextChapters(chapters) })
|
||||||
|
|
||||||
|
startableFirst(FETCH_CHAPTERS,
|
||||||
|
{ getOnlineChaptersObs() },
|
||||||
|
{ view, result -> view.onFetchChaptersDone() },
|
||||||
|
{ view, error -> view.onFetchChaptersError(error) })
|
||||||
|
|
||||||
|
startableLatestCache(CHAPTER_STATUS_CHANGES,
|
||||||
|
{ getChapterStatusObs() },
|
||||||
|
{ view, download -> view.onChapterStatusChange(download) },
|
||||||
|
{ view, error -> Timber.e(error.cause, error.message) })
|
||||||
|
|
||||||
|
registerForEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
unregisterForEvents()
|
||||||
|
EventBus.getDefault().removeStickyEvent(ChapterCountEvent::class.java)
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
|
fun onEvent(event: MangaEvent) {
|
||||||
|
this.manga = event.manga
|
||||||
|
start(GET_MANGA)
|
||||||
|
|
||||||
|
if (isUnsubscribed(DB_CHAPTERS)) {
|
||||||
|
source = sourceManager.get(manga.source)!!
|
||||||
|
start(DB_CHAPTERS)
|
||||||
|
|
||||||
|
add(db.getChapters(manga).asRxObservable()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.doOnNext { chapters ->
|
||||||
|
this.chapters = chapters
|
||||||
|
EventBus.getDefault().postSticky(ChapterCountEvent(chapters.size))
|
||||||
|
for (chapter in chapters) {
|
||||||
|
setChapterStatus(chapter)
|
||||||
|
}
|
||||||
|
start(CHAPTER_STATUS_CHANGES)
|
||||||
|
}
|
||||||
|
.subscribe{ chaptersSubject.onNext(it) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchChaptersFromSource() {
|
||||||
|
hasRequested = true
|
||||||
|
start(FETCH_CHAPTERS)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshChapters() {
|
||||||
|
chaptersSubject.onNext(chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOnlineChaptersObs(): Observable<Pair<Int, Int>> {
|
||||||
|
return source.pullChaptersFromNetwork(manga.url)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.flatMap { chapters -> db.insertOrRemoveChapters(manga, chapters, source) }
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDbChaptersObs(): Observable<List<Chapter>> {
|
||||||
|
return chaptersSubject
|
||||||
|
.flatMap { applyChapterFilters(it) }
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getChapterStatusObs(): Observable<Download> {
|
||||||
|
return downloadManager.queue.statusObservable
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.filter { download -> download.manga.id == manga.id }
|
||||||
|
.doOnNext { updateChapterStatus(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyChapterFilters(chapters: List<Chapter>): Observable<List<Chapter>> {
|
||||||
|
var observable = Observable.from(chapters).subscribeOn(Schedulers.io())
|
||||||
|
if (onlyUnread()) {
|
||||||
|
observable = observable.filter { chapter -> !chapter.read }
|
||||||
|
}
|
||||||
|
if (onlyDownloaded()) {
|
||||||
|
observable = observable.filter { chapter -> chapter.status == Download.DOWNLOADED }
|
||||||
|
}
|
||||||
|
return observable.toSortedList { chapter, chapter2 ->
|
||||||
|
if (sortOrder())
|
||||||
|
chapter2.chapter_number.compareTo(chapter.chapter_number)
|
||||||
|
else
|
||||||
|
chapter.chapter_number.compareTo(chapter2.chapter_number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setChapterStatus(chapter: Chapter) {
|
||||||
|
for (download in downloadManager.queue) {
|
||||||
|
if (chapter.id == download.chapter.id) {
|
||||||
|
chapter.status = download.status
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadManager.isChapterDownloaded(source, manga, chapter)) {
|
||||||
|
chapter.status = Download.DOWNLOADED
|
||||||
|
} else {
|
||||||
|
chapter.status = Download.NOT_DOWNLOADED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateChapterStatus(download: Download) {
|
||||||
|
for (chapter in chapters) {
|
||||||
|
if (download.chapter.id == chapter.id) {
|
||||||
|
chapter.status = download.status
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (onlyDownloaded() && download.status == Download.DOWNLOADED)
|
||||||
|
refreshChapters()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onOpenChapter(chapter: Chapter) {
|
||||||
|
EventBus.getDefault().postSticky(ReaderEvent(source, manga, chapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextUnreadChapter(): Chapter? {
|
||||||
|
return db.getNextUnreadChapter(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun markChaptersRead(selectedChapters: Observable<Chapter>, read: Boolean) {
|
||||||
|
add(selectedChapters.subscribeOn(Schedulers.io())
|
||||||
|
.doOnNext { chapter ->
|
||||||
|
chapter.read = read
|
||||||
|
if (!read) chapter.last_page_read = 0
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun markPreviousChaptersAsRead(selected: Chapter) {
|
||||||
|
Observable.from(chapters)
|
||||||
|
.filter { c -> c.chapter_number > -1 && c.chapter_number < selected.chapter_number }
|
||||||
|
.doOnNext { c -> c.read = true }
|
||||||
|
.toList()
|
||||||
|
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadChapters(selectedChapters: Observable<Chapter>) {
|
||||||
|
add(selectedChapters.toList()
|
||||||
|
.subscribe { chapters -> EventBus.getDefault().postSticky(DownloadChaptersEvent(manga, chapters)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteChapters(selectedChapters: Observable<Chapter>) {
|
||||||
|
add(selectedChapters.subscribe(
|
||||||
|
{ chapter -> downloadManager.queue.remove(chapter) },
|
||||||
|
{ error -> Timber.e(error.message) },
|
||||||
|
{
|
||||||
|
if (onlyDownloaded())
|
||||||
|
refreshChapters()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteChapter(chapter: Chapter) {
|
||||||
|
downloadManager.deleteChapter(source, manga, chapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun revertSortOrder() {
|
||||||
|
manga.setChapterOrder(if (sortOrder()) Manga.SORT_ZA else Manga.SORT_AZ)
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
refreshChapters()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setReadFilter(onlyUnread: Boolean) {
|
||||||
|
manga.readFilter = if (onlyUnread) Manga.SHOW_UNREAD else Manga.SHOW_ALL
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
refreshChapters()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDownloadedFilter(onlyDownloaded: Boolean) {
|
||||||
|
manga.downloadedFilter = if (onlyDownloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
refreshChapters()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDisplayMode(mode: Int) {
|
||||||
|
manga.displayMode = mode
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onlyDownloaded(): Boolean {
|
||||||
|
return manga.downloadedFilter == Manga.SHOW_DOWNLOADED
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onlyUnread(): Boolean {
|
||||||
|
return manga.readFilter == Manga.SHOW_UNREAD
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sortOrder(): Boolean {
|
||||||
|
return manga.sortChaptersAZ()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,244 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.info;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.design.widget.FloatingActionButton;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.model.LazyHeaders;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
|
||||||
import nucleus.factory.RequiresPresenter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment that shows manga information.
|
|
||||||
* Uses R.layout.fragment_manga_info.
|
|
||||||
* UI related actions should be called from here.
|
|
||||||
*/
|
|
||||||
@RequiresPresenter(MangaInfoPresenter.class)
|
|
||||||
public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
|
|
||||||
/**
|
|
||||||
* SwipeRefreshLayout showing refresh status
|
|
||||||
*/
|
|
||||||
@Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefresh;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TextView containing artist information.
|
|
||||||
*/
|
|
||||||
@Bind(R.id.manga_artist) TextView artist;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TextView containing author information.
|
|
||||||
*/
|
|
||||||
@Bind(R.id.manga_author) TextView author;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TextView containing chapter count.
|
|
||||||
*/
|
|
||||||
@Bind(R.id.manga_chapters) TextView chapterCount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TextView containing genres.
|
|
||||||
*/
|
|
||||||
@Bind(R.id.manga_genres) TextView genres;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TextView containing status (ongoing, finished).
|
|
||||||
*/
|
|
||||||
@Bind(R.id.manga_status) TextView status;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TextView containing source.
|
|
||||||
*/
|
|
||||||
@Bind(R.id.manga_source) TextView source;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TextView containing manga summary.
|
|
||||||
*/
|
|
||||||
@Bind(R.id.manga_summary) TextView description;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ImageView of cover.
|
|
||||||
*/
|
|
||||||
@Bind(R.id.manga_cover) ImageView cover;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ImageView containing manga cover shown as blurred backdrop.
|
|
||||||
*/
|
|
||||||
@Bind(R.id.backdrop) ImageView backdrop;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FAB anchored to bottom of top view used to (add / remove) manga (to / from) library.
|
|
||||||
*/
|
|
||||||
@Bind(R.id.fab_favorite) FloatingActionButton fabFavorite;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create new instance of MangaInfoFragment.
|
|
||||||
*
|
|
||||||
* @return MangaInfoFragment.
|
|
||||||
*/
|
|
||||||
public static MangaInfoFragment newInstance() {
|
|
||||||
return new MangaInfoFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
// Inflate the layout for this fragment.
|
|
||||||
View view = inflater.inflate(R.layout.fragment_manga_info, container, false);
|
|
||||||
|
|
||||||
// Bind layout objects.
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
// Set onclickListener to toggle favorite when FAB clicked.
|
|
||||||
fabFavorite.setOnClickListener(v -> getPresenter().toggleFavorite());
|
|
||||||
|
|
||||||
// Set SwipeRefresh to refresh manga data.
|
|
||||||
swipeRefresh.setOnRefreshListener(this::fetchMangaFromSource);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if manga is initialized.
|
|
||||||
* If true update view with manga information,
|
|
||||||
* if false fetch manga information
|
|
||||||
*
|
|
||||||
* @param manga manga object containing information about manga.
|
|
||||||
* @param source the source of the manga.
|
|
||||||
*/
|
|
||||||
public void onNextManga(Manga manga, Source source) {
|
|
||||||
if (manga.initialized) {
|
|
||||||
// Update view.
|
|
||||||
setMangaInfo(manga, source);
|
|
||||||
} else {
|
|
||||||
// Initialize manga.
|
|
||||||
fetchMangaFromSource();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the view with manga information.
|
|
||||||
*
|
|
||||||
* @param manga manga object containing information about manga.
|
|
||||||
* @param mangaSource the source of the manga.
|
|
||||||
*/
|
|
||||||
private void setMangaInfo(Manga manga, Source mangaSource) {
|
|
||||||
// Update artist TextView.
|
|
||||||
artist.setText(manga.artist);
|
|
||||||
|
|
||||||
// Update author TextView.
|
|
||||||
author.setText(manga.author);
|
|
||||||
|
|
||||||
// If manga source is known update source TextView.
|
|
||||||
if (mangaSource != null) {
|
|
||||||
source.setText(mangaSource.getVisibleName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update genres TextView.
|
|
||||||
genres.setText(manga.genre);
|
|
||||||
|
|
||||||
// Update status TextView.
|
|
||||||
status.setText(manga.getStatus(getActivity()));
|
|
||||||
|
|
||||||
// Update description TextView.
|
|
||||||
description.setText(manga.description);
|
|
||||||
|
|
||||||
// Set the favorite drawable to the correct one.
|
|
||||||
setFavoriteDrawable(manga.favorite);
|
|
||||||
|
|
||||||
// Initialize CoverCache and Glide headers to retrieve cover information.
|
|
||||||
CoverCache coverCache = getPresenter().coverCache;
|
|
||||||
LazyHeaders headers = getPresenter().source.getGlideHeaders();
|
|
||||||
|
|
||||||
// Check if thumbnail_url is given.
|
|
||||||
if (manga.thumbnail_url != null) {
|
|
||||||
// Check if cover is already drawn.
|
|
||||||
if (cover.getDrawable() == null) {
|
|
||||||
// If manga is in library then (download / save) (from / to) local cache if available,
|
|
||||||
// else download from network.
|
|
||||||
if (manga.favorite) {
|
|
||||||
coverCache.saveOrLoadFromCache(cover, manga.thumbnail_url, headers);
|
|
||||||
} else {
|
|
||||||
coverCache.loadFromNetwork(cover, manga.thumbnail_url, headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check if backdrop is already drawn.
|
|
||||||
if (backdrop.getDrawable() == null) {
|
|
||||||
// If manga is in library then (download / save) (from / to) local cache if available,
|
|
||||||
// else download from network.
|
|
||||||
if (manga.favorite) {
|
|
||||||
coverCache.saveOrLoadFromCache(backdrop, manga.thumbnail_url, headers);
|
|
||||||
} else {
|
|
||||||
coverCache.loadFromNetwork(backdrop, manga.thumbnail_url, headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update chapter count TextView.
|
|
||||||
*
|
|
||||||
* @param count number of chapters.
|
|
||||||
*/
|
|
||||||
public void setChapterCount(int count) {
|
|
||||||
chapterCount.setText(String.valueOf(count));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update FAB with correct drawable.
|
|
||||||
*
|
|
||||||
* @param isFavorite determines if manga is favorite or not.
|
|
||||||
*/
|
|
||||||
private void setFavoriteDrawable(boolean isFavorite) {
|
|
||||||
// Set the Favorite drawable to the correct one.
|
|
||||||
// Border drawable if false, filled drawable if true.
|
|
||||||
fabFavorite.setImageDrawable(ContextCompat.getDrawable(getContext(), isFavorite ?
|
|
||||||
R.drawable.ic_bookmark_white_24dp :
|
|
||||||
R.drawable.ic_bookmark_border_white_24dp));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start fetching manga information from source.
|
|
||||||
*/
|
|
||||||
private void fetchMangaFromSource() {
|
|
||||||
setRefreshing(true);
|
|
||||||
// Call presenter and start fetching manga information
|
|
||||||
getPresenter().fetchMangaFromSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update swipeRefresh to stop showing refresh in progress spinner.
|
|
||||||
*/
|
|
||||||
public void onFetchMangaDone() {
|
|
||||||
setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update swipeRefresh to start showing refresh in progress spinner.
|
|
||||||
*/
|
|
||||||
public void onFetchMangaError() {
|
|
||||||
setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set swipeRefresh status.
|
|
||||||
*
|
|
||||||
* @param value status of manga fetch.
|
|
||||||
*/
|
|
||||||
private void setRefreshing(boolean value) {
|
|
||||||
swipeRefresh.setRefreshing(value);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.manga.info
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.source.base.Source
|
||||||
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||||
|
import eu.kanade.tachiyomi.util.setDrawableCompat
|
||||||
|
import kotlinx.android.synthetic.main.fragment_manga_info.*
|
||||||
|
import nucleus.factory.RequiresPresenter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment that shows manga information.
|
||||||
|
* Uses R.layout.fragment_manga_info.
|
||||||
|
* UI related actions should be called from here.
|
||||||
|
*/
|
||||||
|
@RequiresPresenter(MangaInfoPresenter::class)
|
||||||
|
class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Create new instance of MangaInfoFragment.
|
||||||
|
*
|
||||||
|
* @return MangaInfoFragment.
|
||||||
|
*/
|
||||||
|
fun newInstance(): MangaInfoFragment {
|
||||||
|
return MangaInfoFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_manga_info, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View?, savedState: Bundle?) {
|
||||||
|
// Set onclickListener to toggle favorite when FAB clicked.
|
||||||
|
fab_favorite.setOnClickListener { presenter.toggleFavorite() }
|
||||||
|
|
||||||
|
// Set SwipeRefresh to refresh manga data.
|
||||||
|
swipe_refresh.setOnRefreshListener { fetchMangaFromSource() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if manga is initialized.
|
||||||
|
* If true update view with manga information,
|
||||||
|
* if false fetch manga information
|
||||||
|
*
|
||||||
|
* @param manga manga object containing information about manga.
|
||||||
|
* @param source the source of the manga.
|
||||||
|
*/
|
||||||
|
fun onNextManga(manga: Manga, source: Source) {
|
||||||
|
if (manga.initialized) {
|
||||||
|
// Update view.
|
||||||
|
setMangaInfo(manga, source)
|
||||||
|
} else {
|
||||||
|
// Initialize manga.
|
||||||
|
fetchMangaFromSource()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the view with manga information.
|
||||||
|
*
|
||||||
|
* @param manga manga object containing information about manga.
|
||||||
|
* @param source the source of the manga.
|
||||||
|
*/
|
||||||
|
private fun setMangaInfo(manga: Manga, source: Source?) {
|
||||||
|
// Update artist TextView.
|
||||||
|
manga_artist.text = manga.artist
|
||||||
|
|
||||||
|
// Update author TextView.
|
||||||
|
manga_author.text = manga.author
|
||||||
|
|
||||||
|
// If manga source is known update source TextView.
|
||||||
|
if (source != null) {
|
||||||
|
manga_source.text = source.visibleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update genres TextView.
|
||||||
|
manga_genres.text = manga.genre
|
||||||
|
|
||||||
|
// Update status TextView.
|
||||||
|
manga_status.text = manga.getStatus(activity)
|
||||||
|
|
||||||
|
// Update description TextView.
|
||||||
|
manga_summary.text = manga.description
|
||||||
|
|
||||||
|
// Set the favorite drawable to the correct one.
|
||||||
|
setFavoriteDrawable(manga.favorite)
|
||||||
|
|
||||||
|
// Initialize CoverCache and Glide headers to retrieve cover information.
|
||||||
|
val coverCache = presenter.coverCache
|
||||||
|
val headers = presenter.source.glideHeaders
|
||||||
|
|
||||||
|
// Check if thumbnail_url is given.
|
||||||
|
if (manga.thumbnail_url != null) {
|
||||||
|
// Check if cover is already drawn.
|
||||||
|
if (manga_cover.drawable == null) {
|
||||||
|
// If manga is in library then (download / save) (from / to) local cache if available,
|
||||||
|
// else download from network.
|
||||||
|
if (manga.favorite) {
|
||||||
|
coverCache.saveOrLoadFromCache(manga_cover, manga.thumbnail_url, headers)
|
||||||
|
} else {
|
||||||
|
coverCache.loadFromNetwork(manga_cover, manga.thumbnail_url, headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if backdrop is already drawn.
|
||||||
|
if (backdrop.drawable == null) {
|
||||||
|
// If manga is in library then (download / save) (from / to) local cache if available,
|
||||||
|
// else download from network.
|
||||||
|
if (manga.favorite) {
|
||||||
|
coverCache.saveOrLoadFromCache(backdrop, manga.thumbnail_url, headers)
|
||||||
|
} else {
|
||||||
|
coverCache.loadFromNetwork(backdrop, manga.thumbnail_url, headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update chapter count TextView.
|
||||||
|
*
|
||||||
|
* @param count number of chapters.
|
||||||
|
*/
|
||||||
|
fun setChapterCount(count: Int) {
|
||||||
|
manga_chapters.text = count.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update FAB with correct drawable.
|
||||||
|
*
|
||||||
|
* @param isFavorite determines if manga is favorite or not.
|
||||||
|
*/
|
||||||
|
private fun setFavoriteDrawable(isFavorite: Boolean) {
|
||||||
|
// Set the Favorite drawable to the correct one.
|
||||||
|
// Border drawable if false, filled drawable if true.
|
||||||
|
fab_favorite.setDrawableCompat(if (isFavorite)
|
||||||
|
R.drawable.ic_bookmark_white_24dp
|
||||||
|
else
|
||||||
|
R.drawable.ic_bookmark_border_white_24dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start fetching manga information from source.
|
||||||
|
*/
|
||||||
|
private fun fetchMangaFromSource() {
|
||||||
|
setRefreshing(true)
|
||||||
|
// Call presenter and start fetching manga information
|
||||||
|
presenter.fetchMangaFromSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update swipe refresh to stop showing refresh in progress spinner.
|
||||||
|
*/
|
||||||
|
fun onFetchMangaDone() {
|
||||||
|
setRefreshing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update swipe refresh to start showing refresh in progress spinner.
|
||||||
|
*/
|
||||||
|
fun onFetchMangaError() {
|
||||||
|
setRefreshing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set swipe refresh status.
|
||||||
|
*
|
||||||
|
* @param value whether it should be refreshing or not.
|
||||||
|
*/
|
||||||
|
private fun setRefreshing(value: Boolean) {
|
||||||
|
swipe_refresh.isRefreshing = value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,177 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.info;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache;
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager;
|
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source;
|
|
||||||
import eu.kanade.tachiyomi.event.ChapterCountEvent;
|
|
||||||
import eu.kanade.tachiyomi.event.MangaEvent;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
|
||||||
import rx.schedulers.Schedulers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Presenter of MangaInfoFragment.
|
|
||||||
* Contains information and data for fragment.
|
|
||||||
* Observable updates should be called from here.
|
|
||||||
*/
|
|
||||||
public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The id of the restartable.
|
|
||||||
*/
|
|
||||||
private static final int GET_MANGA = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The id of the restartable.
|
|
||||||
*/
|
|
||||||
private static final int GET_CHAPTER_COUNT = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The id of the restartable.
|
|
||||||
*/
|
|
||||||
private static final int FETCH_MANGA_INFO = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source information.
|
|
||||||
*/
|
|
||||||
protected Source source;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to connect to database.
|
|
||||||
*/
|
|
||||||
@Inject DatabaseHelper db;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to connect to different manga sources.
|
|
||||||
*/
|
|
||||||
@Inject SourceManager sourceManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to connect to cache.
|
|
||||||
*/
|
|
||||||
@Inject CoverCache coverCache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selected manga information.
|
|
||||||
*/
|
|
||||||
private Manga manga;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count of chapters.
|
|
||||||
*/
|
|
||||||
private int count = -1;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedState) {
|
|
||||||
super.onCreate(savedState);
|
|
||||||
|
|
||||||
// Notify the view a manga is available or has changed.
|
|
||||||
startableLatestCache(GET_MANGA,
|
|
||||||
() -> Observable.just(manga),
|
|
||||||
(view, manga) -> view.onNextManga(manga, source));
|
|
||||||
|
|
||||||
// Update chapter count.
|
|
||||||
startableLatestCache(GET_CHAPTER_COUNT,
|
|
||||||
() -> Observable.just(count),
|
|
||||||
MangaInfoFragment::setChapterCount);
|
|
||||||
|
|
||||||
// Fetch manga info from source.
|
|
||||||
startableFirst(FETCH_MANGA_INFO,
|
|
||||||
this::fetchMangaObs,
|
|
||||||
(view, manga) -> view.onFetchMangaDone(),
|
|
||||||
(view, error) -> view.onFetchMangaError());
|
|
||||||
|
|
||||||
// Listen for events.
|
|
||||||
registerForEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
unregisterForEvents();
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
|
||||||
public void onEvent(MangaEvent event) {
|
|
||||||
this.manga = event.manga;
|
|
||||||
source = sourceManager.get(manga.source);
|
|
||||||
refreshManga();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
|
||||||
public void onEvent(ChapterCountEvent event) {
|
|
||||||
if (count != event.getCount()) {
|
|
||||||
count = event.getCount();
|
|
||||||
// Update chapter count
|
|
||||||
start(GET_CHAPTER_COUNT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch manga information from source.
|
|
||||||
*/
|
|
||||||
public void fetchMangaFromSource() {
|
|
||||||
if (isUnsubscribed(FETCH_MANGA_INFO)) {
|
|
||||||
start(FETCH_MANGA_INFO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch manga information from source.
|
|
||||||
*
|
|
||||||
* @return manga information.
|
|
||||||
*/
|
|
||||||
private Observable<Manga> fetchMangaObs() {
|
|
||||||
return source.pullMangaFromNetwork(manga.url)
|
|
||||||
.flatMap(networkManga -> {
|
|
||||||
manga.copyFrom(networkManga);
|
|
||||||
db.insertManga(manga).executeAsBlocking();
|
|
||||||
return Observable.just(manga);
|
|
||||||
})
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnNext(manga -> refreshManga());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update favorite status of manga, (removes / adds) manga (to / from) library.
|
|
||||||
*/
|
|
||||||
public void toggleFavorite() {
|
|
||||||
manga.favorite = !manga.favorite;
|
|
||||||
onMangaFavoriteChange(manga.favorite);
|
|
||||||
db.insertManga(manga).executeAsBlocking();
|
|
||||||
refreshManga();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (Removes / Saves) cover depending on favorite status.
|
|
||||||
*
|
|
||||||
* @param isFavorite determines if manga is favorite or not.
|
|
||||||
*/
|
|
||||||
private void onMangaFavoriteChange(boolean isFavorite) {
|
|
||||||
if (isFavorite) {
|
|
||||||
coverCache.save(manga.thumbnail_url, source.getGlideHeaders());
|
|
||||||
} else {
|
|
||||||
coverCache.deleteCoverFromCache(manga.thumbnail_url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh MangaInfo view.
|
|
||||||
*/
|
|
||||||
private void refreshManga() {
|
|
||||||
start(GET_MANGA);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.manga.info
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
|
import eu.kanade.tachiyomi.data.source.base.Source
|
||||||
|
import eu.kanade.tachiyomi.event.ChapterCountEvent
|
||||||
|
import eu.kanade.tachiyomi.event.MangaEvent
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import org.greenrobot.eventbus.Subscribe
|
||||||
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
import rx.Observable
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of MangaInfoFragment.
|
||||||
|
* Contains information and data for fragment.
|
||||||
|
* Observable updates should be called from here.
|
||||||
|
*/
|
||||||
|
class MangaInfoPresenter : BasePresenter<MangaInfoFragment>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active manga.
|
||||||
|
*/
|
||||||
|
lateinit var manga: Manga
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of the manga.
|
||||||
|
*/
|
||||||
|
lateinit var source: Source
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to connect to database.
|
||||||
|
*/
|
||||||
|
@Inject lateinit var db: DatabaseHelper
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to connect to different manga sources.
|
||||||
|
*/
|
||||||
|
@Inject lateinit var sourceManager: SourceManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to connect to cache.
|
||||||
|
*/
|
||||||
|
@Inject lateinit var coverCache: CoverCache
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of chapters.
|
||||||
|
*/
|
||||||
|
private var count = -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the restartable.
|
||||||
|
*/
|
||||||
|
private val GET_MANGA = 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the restartable.
|
||||||
|
*/
|
||||||
|
private val GET_CHAPTER_COUNT = 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the restartable.
|
||||||
|
*/
|
||||||
|
private val FETCH_MANGA_INFO = 3
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
// Notify the view a manga is available or has changed.
|
||||||
|
startableLatestCache(GET_MANGA,
|
||||||
|
{ Observable.just(manga) },
|
||||||
|
{ view, manga -> view.onNextManga(manga, source) })
|
||||||
|
|
||||||
|
// Update chapter count.
|
||||||
|
startableLatestCache(GET_CHAPTER_COUNT,
|
||||||
|
{ Observable.just(count) },
|
||||||
|
{ view, count -> view.setChapterCount(count) })
|
||||||
|
|
||||||
|
// Fetch manga info from source.
|
||||||
|
startableFirst(FETCH_MANGA_INFO,
|
||||||
|
{ fetchMangaObs() },
|
||||||
|
{ view, manga -> view.onFetchMangaDone() },
|
||||||
|
{ view, error -> view.onFetchMangaError() })
|
||||||
|
|
||||||
|
// Listen for events.
|
||||||
|
registerForEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
unregisterForEvents()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
|
fun onEvent(event: MangaEvent) {
|
||||||
|
manga = event.manga
|
||||||
|
source = sourceManager.get(manga.source)!!
|
||||||
|
refreshManga()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
|
fun onEvent(event: ChapterCountEvent) {
|
||||||
|
if (count != event.count) {
|
||||||
|
count = event.count
|
||||||
|
// Update chapter count
|
||||||
|
start(GET_CHAPTER_COUNT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch manga information from source.
|
||||||
|
*/
|
||||||
|
fun fetchMangaFromSource() {
|
||||||
|
if (isUnsubscribed(FETCH_MANGA_INFO)) {
|
||||||
|
start(FETCH_MANGA_INFO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch manga information from source.
|
||||||
|
*
|
||||||
|
* @return manga information.
|
||||||
|
*/
|
||||||
|
private fun fetchMangaObs(): Observable<Manga> {
|
||||||
|
return source.pullMangaFromNetwork(manga.url)
|
||||||
|
.flatMap { networkManga ->
|
||||||
|
manga.copyFrom(networkManga)
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
Observable.just<Manga>(manga)
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnNext { refreshManga() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update favorite status of manga, (removes / adds) manga (to / from) library.
|
||||||
|
*/
|
||||||
|
fun toggleFavorite() {
|
||||||
|
manga.favorite = !manga.favorite
|
||||||
|
onMangaFavoriteChange(manga.favorite)
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
refreshManga()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Removes / Saves) cover depending on favorite status.
|
||||||
|
*
|
||||||
|
* @param isFavorite determines if manga is favorite or not.
|
||||||
|
*/
|
||||||
|
private fun onMangaFavoriteChange(isFavorite: Boolean) {
|
||||||
|
if (isFavorite) {
|
||||||
|
coverCache.save(manga.thumbnail_url, source.glideHeaders)
|
||||||
|
} else {
|
||||||
|
coverCache.deleteCoverFromCache(manga.thumbnail_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh MangaInfo view.
|
||||||
|
*/
|
||||||
|
private fun refreshManga() {
|
||||||
|
start(GET_MANGA)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,151 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.myanimelist;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
|
||||||
import rx.subjects.PublishSubject;
|
|
||||||
|
|
||||||
public class MyAnimeListDialogFragment extends DialogFragment {
|
|
||||||
|
|
||||||
@Bind(R.id.myanimelist_search_field) EditText searchText;
|
|
||||||
@Bind(R.id.myanimelist_search_results) ListView searchResults;
|
|
||||||
@Bind(R.id.progress) ProgressBar progressBar;
|
|
||||||
|
|
||||||
private MyAnimeListSearchAdapter adapter;
|
|
||||||
private MangaSync selectedItem;
|
|
||||||
|
|
||||||
private Subscription searchSubscription;
|
|
||||||
|
|
||||||
public static MyAnimeListDialogFragment newInstance() {
|
|
||||||
return new MyAnimeListDialogFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedState) {
|
|
||||||
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
|
|
||||||
.customView(R.layout.dialog_myanimelist_search, false)
|
|
||||||
.positiveText(R.string.button_ok)
|
|
||||||
.negativeText(R.string.button_cancel)
|
|
||||||
.onPositive((dialog1, which) -> onPositiveButtonClick())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
ButterKnife.bind(this, dialog.getView());
|
|
||||||
|
|
||||||
// Create adapter
|
|
||||||
adapter = new MyAnimeListSearchAdapter(getActivity());
|
|
||||||
searchResults.setAdapter(adapter);
|
|
||||||
|
|
||||||
// Set listeners
|
|
||||||
searchResults.setOnItemClickListener((parent, viewList, position, id) ->
|
|
||||||
selectedItem = adapter.getItem(position));
|
|
||||||
|
|
||||||
// Do an initial search based on the manga's title
|
|
||||||
if (savedState == null) {
|
|
||||||
String title = getPresenter().manga.title;
|
|
||||||
searchText.append(title);
|
|
||||||
search(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
PublishSubject<String> querySubject = PublishSubject.create();
|
|
||||||
searchText.addTextChangedListener(new SimpleTextChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
querySubject.onNext(s.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen to text changes
|
|
||||||
searchSubscription = querySubject.debounce(1, TimeUnit.SECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(this::search);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
if (searchSubscription != null) {
|
|
||||||
searchSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPositiveButtonClick() {
|
|
||||||
if (adapter != null && selectedItem != null) {
|
|
||||||
getPresenter().registerManga(selectedItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void search(String query) {
|
|
||||||
if (!TextUtils.isEmpty(query)) {
|
|
||||||
searchResults.setVisibility(View.GONE);
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
getPresenter().searchManga(query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSearchResults(List<MangaSync> results) {
|
|
||||||
selectedItem = null;
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
searchResults.setVisibility(View.VISIBLE);
|
|
||||||
adapter.setItems(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSearchResultsError() {
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
searchResults.setVisibility(View.VISIBLE);
|
|
||||||
adapter.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MyAnimeListFragment getMALFragment() {
|
|
||||||
return (MyAnimeListFragment) getParentFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MyAnimeListPresenter getPresenter() {
|
|
||||||
return getMALFragment().getPresenter();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SimpleTextChangeListener implements TextWatcher {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
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() {
|
||||||
|
selectedItem?.let {
|
||||||
|
presenter.registerManga(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,181 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.myanimelist;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.NumberPicker;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
|
||||||
import nucleus.factory.RequiresPresenter;
|
|
||||||
|
|
||||||
@RequiresPresenter(MyAnimeListPresenter.class)
|
|
||||||
public class MyAnimeListFragment extends BaseRxFragment<MyAnimeListPresenter> {
|
|
||||||
|
|
||||||
@Bind(R.id.myanimelist_title) TextView title;
|
|
||||||
@Bind(R.id.myanimelist_chapters) TextView chapters;
|
|
||||||
@Bind(R.id.myanimelist_score) TextView score;
|
|
||||||
@Bind(R.id.myanimelist_status) TextView status;
|
|
||||||
@Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefresh;
|
|
||||||
|
|
||||||
private MyAnimeListDialogFragment dialog;
|
|
||||||
|
|
||||||
private DecimalFormat decimalFormat = new DecimalFormat("#.##");
|
|
||||||
|
|
||||||
private final static String SEARCH_FRAGMENT_TAG = "mal_search";
|
|
||||||
|
|
||||||
public static MyAnimeListFragment newInstance() {
|
|
||||||
return new MyAnimeListFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.fragment_myanimelist, container, false);
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
swipeRefresh.setEnabled(false);
|
|
||||||
swipeRefresh.setOnRefreshListener(() -> getPresenter().refresh());
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMangaSync(MangaSync mangaSync) {
|
|
||||||
swipeRefresh.setEnabled(mangaSync != null);
|
|
||||||
if (mangaSync != null) {
|
|
||||||
title.setText(mangaSync.title);
|
|
||||||
chapters.setText(mangaSync.last_chapter_read + "/" +
|
|
||||||
(mangaSync.total_chapters > 0 ? mangaSync.total_chapters : "-"));
|
|
||||||
score.setText(mangaSync.score == 0 ? "-" : decimalFormat.format(mangaSync.score));
|
|
||||||
status.setText(getPresenter().myAnimeList.getStatus(mangaSync.status));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onRefreshDone() {
|
|
||||||
swipeRefresh.setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onRefreshError() {
|
|
||||||
swipeRefresh.setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSearchResults(List<MangaSync> results) {
|
|
||||||
findSearchFragmentIfNeeded();
|
|
||||||
|
|
||||||
if (dialog != null) {
|
|
||||||
dialog.onSearchResults(results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSearchResultsError() {
|
|
||||||
findSearchFragmentIfNeeded();
|
|
||||||
|
|
||||||
if (dialog != null) {
|
|
||||||
dialog.onSearchResultsError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void findSearchFragmentIfNeeded() {
|
|
||||||
if (dialog == null) {
|
|
||||||
dialog = (MyAnimeListDialogFragment) getChildFragmentManager()
|
|
||||||
.findFragmentByTag(SEARCH_FRAGMENT_TAG);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.myanimelist_title_layout)
|
|
||||||
void onTitleClick() {
|
|
||||||
if (dialog == null)
|
|
||||||
dialog = MyAnimeListDialogFragment.newInstance();
|
|
||||||
|
|
||||||
getPresenter().restartSearch();
|
|
||||||
dialog.show(getChildFragmentManager(), SEARCH_FRAGMENT_TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.myanimelist_status_layout)
|
|
||||||
void onStatusClick() {
|
|
||||||
if (getPresenter().mangaSync == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Context ctx = getActivity();
|
|
||||||
new MaterialDialog.Builder(ctx)
|
|
||||||
.title(R.string.status)
|
|
||||||
.items(getPresenter().getAllStatus(ctx))
|
|
||||||
.itemsCallbackSingleChoice(getPresenter().getIndexFromStatus(),
|
|
||||||
(materialDialog, view, i, charSequence) -> {
|
|
||||||
getPresenter().setStatus(i);
|
|
||||||
status.setText("...");
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.myanimelist_chapters_layout)
|
|
||||||
void onChaptersClick() {
|
|
||||||
if (getPresenter().mangaSync == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
|
|
||||||
.title(R.string.chapters)
|
|
||||||
.customView(R.layout.dialog_myanimelist_chapters, false)
|
|
||||||
.positiveText(R.string.button_ok)
|
|
||||||
.negativeText(R.string.button_cancel)
|
|
||||||
.onPositive((materialDialog, dialogAction) -> {
|
|
||||||
View view = materialDialog.getCustomView();
|
|
||||||
if (view != null) {
|
|
||||||
NumberPicker np = (NumberPicker) view.findViewById(R.id.chapters_picker);
|
|
||||||
getPresenter().setLastChapterRead(np.getValue());
|
|
||||||
chapters.setText("...");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
|
|
||||||
View view = dialog.getCustomView();
|
|
||||||
if (view != null) {
|
|
||||||
NumberPicker np = (NumberPicker) view.findViewById(R.id.chapters_picker);
|
|
||||||
// Set initial value
|
|
||||||
np.setValue(getPresenter().mangaSync.last_chapter_read);
|
|
||||||
// Don't allow to go from 0 to 9999
|
|
||||||
np.setWrapSelectorWheel(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.myanimelist_score_layout)
|
|
||||||
void onScoreClick() {
|
|
||||||
if (getPresenter().mangaSync == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
|
|
||||||
.title(R.string.score)
|
|
||||||
.customView(R.layout.dialog_myanimelist_score, false)
|
|
||||||
.positiveText(R.string.button_ok)
|
|
||||||
.negativeText(R.string.button_cancel)
|
|
||||||
.onPositive((materialDialog, dialogAction) -> {
|
|
||||||
View view = materialDialog.getCustomView();
|
|
||||||
if (view != null) {
|
|
||||||
NumberPicker np = (NumberPicker) view.findViewById(R.id.score_picker);
|
|
||||||
getPresenter().setScore(np.getValue());
|
|
||||||
score.setText("...");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
|
|
||||||
View view = dialog.getCustomView();
|
|
||||||
if (view != null) {
|
|
||||||
NumberPicker np = (NumberPicker) view.findViewById(R.id.score_picker);
|
|
||||||
// Set initial value
|
|
||||||
np.setValue((int) getPresenter().mangaSync.score);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
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() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMangaSync(mangaSync: MangaSync?) {
|
||||||
|
swipe_refresh.isEnabled = mangaSync != null
|
||||||
|
mangaSync?.let {
|
||||||
|
myanimelist_title.text = it.title
|
||||||
|
val chaptersText = if (it.total_chapters > 0)
|
||||||
|
"${it.last_chapter_read}/${it.total_chapters}" else "${it.last_chapter_read}/-"
|
||||||
|
|
||||||
|
myanimelist_chapters.text = chaptersText
|
||||||
|
myanimelist_score.text = if (it.score == 0f) "-" else decimalFormat.format(it.score)
|
||||||
|
myanimelist_status.text = presenter.myAnimeList.getStatus(it.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRefreshDone() {
|
||||||
|
swipe_refresh.isRefreshing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRefreshError() {
|
||||||
|
swipe_refresh.isRefreshing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
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,197 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.myanimelist;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
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.data.mangasync.services.MyAnimeList;
|
|
||||||
import eu.kanade.tachiyomi.event.MangaEvent;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
|
||||||
import rx.schedulers.Schedulers;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
|
|
||||||
|
|
||||||
@Inject DatabaseHelper db;
|
|
||||||
@Inject MangaSyncManager syncManager;
|
|
||||||
|
|
||||||
protected MyAnimeList myAnimeList;
|
|
||||||
protected Manga manga;
|
|
||||||
protected MangaSync mangaSync;
|
|
||||||
|
|
||||||
private String query;
|
|
||||||
|
|
||||||
private static final int GET_MANGA_SYNC = 1;
|
|
||||||
private static final int GET_SEARCH_RESULTS = 2;
|
|
||||||
private static final int REFRESH = 3;
|
|
||||||
|
|
||||||
private static final String PREFIX_MY = "my:";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedState) {
|
|
||||||
super.onCreate(savedState);
|
|
||||||
|
|
||||||
myAnimeList = syncManager.getMyAnimeList();
|
|
||||||
|
|
||||||
startableLatestCache(GET_MANGA_SYNC,
|
|
||||||
() -> db.getMangaSync(manga, myAnimeList).asRxObservable()
|
|
||||||
.doOnNext(mangaSync -> this.mangaSync = mangaSync)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread()),
|
|
||||||
MyAnimeListFragment::setMangaSync);
|
|
||||||
|
|
||||||
startableLatestCache(GET_SEARCH_RESULTS,
|
|
||||||
this::getSearchResultsObservable,
|
|
||||||
(view, results) -> {
|
|
||||||
view.setSearchResults(results);
|
|
||||||
}, (view, error) -> {
|
|
||||||
Timber.e(error.getMessage());
|
|
||||||
view.setSearchResultsError();
|
|
||||||
});
|
|
||||||
|
|
||||||
startableFirst(REFRESH,
|
|
||||||
() -> myAnimeList.getList()
|
|
||||||
.flatMap(myList -> {
|
|
||||||
for (MangaSync myManga : myList) {
|
|
||||||
if (myManga.remote_id == mangaSync.remote_id) {
|
|
||||||
mangaSync.copyPersonalFrom(myManga);
|
|
||||||
mangaSync.total_chapters = myManga.total_chapters;
|
|
||||||
return Observable.just(mangaSync);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Observable.error(new Exception("Could not find manga"));
|
|
||||||
})
|
|
||||||
.flatMap(myManga -> db.insertMangaSync(myManga).asRxObservable())
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread()),
|
|
||||||
(view, result) -> view.onRefreshDone(),
|
|
||||||
(view, error) -> view.onRefreshError());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onTakeView(MyAnimeListFragment view) {
|
|
||||||
super.onTakeView(view);
|
|
||||||
registerForEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDropView() {
|
|
||||||
unregisterForEvents();
|
|
||||||
super.onDropView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
|
||||||
public void onEvent(MangaEvent event) {
|
|
||||||
this.manga = event.manga;
|
|
||||||
start(GET_MANGA_SYNC);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<List<MangaSync>> getSearchResultsObservable() {
|
|
||||||
Observable<List<MangaSync>> observable;
|
|
||||||
if (query.startsWith(PREFIX_MY)) {
|
|
||||||
String realQuery = query.substring(PREFIX_MY.length()).toLowerCase().trim();
|
|
||||||
observable = myAnimeList.getList()
|
|
||||||
.flatMap(Observable::from)
|
|
||||||
.filter(manga -> manga.title.toLowerCase().contains(realQuery))
|
|
||||||
.toList();
|
|
||||||
} else {
|
|
||||||
observable = myAnimeList.search(query);
|
|
||||||
}
|
|
||||||
return observable
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRemote() {
|
|
||||||
add(myAnimeList.update(mangaSync)
|
|
||||||
.flatMap(response -> db.insertMangaSync(mangaSync).asRxObservable())
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(next -> {},
|
|
||||||
error -> {
|
|
||||||
Timber.e(error.getMessage());
|
|
||||||
// Restart on error to set old values
|
|
||||||
start(GET_MANGA_SYNC);
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void searchManga(String query) {
|
|
||||||
if (TextUtils.isEmpty(query) || query.equals(this.query))
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.query = query;
|
|
||||||
start(GET_SEARCH_RESULTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restartSearch() {
|
|
||||||
this.query = null;
|
|
||||||
stop(GET_SEARCH_RESULTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerManga(MangaSync manga) {
|
|
||||||
manga.manga_id = this.manga.id;
|
|
||||||
add(myAnimeList.bind(manga)
|
|
||||||
.flatMap(response -> {
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
return db.insertMangaSync(manga).asRxObservable();
|
|
||||||
}
|
|
||||||
return Observable.error(new Exception("Could not bind manga"));
|
|
||||||
})
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(manga2 -> {},
|
|
||||||
error -> ToastUtil.showShort(getContext(), error.getMessage())));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getAllStatus(Context context) {
|
|
||||||
return new String[] {
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIndexFromStatus() {
|
|
||||||
return mangaSync.status == 6 ? 4 : mangaSync.status - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(int index) {
|
|
||||||
mangaSync.status = index == 4 ? 6 : index + 1;
|
|
||||||
updateRemote();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setScore(int score) {
|
|
||||||
mangaSync.score = score;
|
|
||||||
updateRemote();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastChapterRead(int chapterNumber) {
|
|
||||||
mangaSync.last_chapter_read = chapterNumber;
|
|
||||||
updateRemote();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refresh() {
|
|
||||||
if (mangaSync != null) {
|
|
||||||
start(REFRESH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
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.event.MangaEvent
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.util.toast
|
||||||
|
import org.greenrobot.eventbus.Subscribe
|
||||||
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
import rx.Observable
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
|
||||||
|
|
||||||
|
@Inject lateinit var db: DatabaseHelper
|
||||||
|
@Inject lateinit var syncManager: MangaSyncManager
|
||||||
|
|
||||||
|
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 }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.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() })
|
||||||
|
|
||||||
|
registerForEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
unregisterForEvents()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
|
fun onEvent(event: MangaEvent) {
|
||||||
|
manga = event.manga
|
||||||
|
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()
|
||||||
|
.flatMap { myList ->
|
||||||
|
for (myManga in myList) {
|
||||||
|
if (myManga.remote_id == mangaSync.remote_id) {
|
||||||
|
mangaSync.copyPersonalFrom(myManga)
|
||||||
|
mangaSync.total_chapters = myManga.total_chapters
|
||||||
|
return@flatMap Observable.just(mangaSync)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Observable.error<MangaSync>(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)
|
||||||
|
.flatMap { response -> db.insertMangaSync(mangaSync).asRxObservable() }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ next -> },
|
||||||
|
{ error ->
|
||||||
|
Timber.e(error.message)
|
||||||
|
// 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) {
|
||||||
|
sync.manga_id = manga.id
|
||||||
|
add(myAnimeList.bind(sync)
|
||||||
|
.flatMap { response ->
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
db.insertMangaSync(sync).asRxObservable()
|
||||||
|
} else {
|
||||||
|
Observable.error(Exception("Could not bind manga"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ },
|
||||||
|
{ error -> context.toast(error.message) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
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,61 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.myanimelist;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync;
|
|
||||||
|
|
||||||
public class MyAnimeListSearchAdapter extends ArrayAdapter<MangaSync> {
|
|
||||||
|
|
||||||
public MyAnimeListSearchAdapter(Context context) {
|
|
||||||
super(context, R.layout.dialog_myanimelist_search_item, new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View view, ViewGroup parent) {
|
|
||||||
// Get the data item for this position
|
|
||||||
MangaSync sync = getItem(position);
|
|
||||||
// Check if an existing view is being reused, otherwise inflate the view
|
|
||||||
SearchViewHolder holder; // view lookup cache stored in tag
|
|
||||||
if (view == null) {
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
|
||||||
view = inflater.inflate(R.layout.dialog_myanimelist_search_item, parent, false);
|
|
||||||
holder = new SearchViewHolder(view);
|
|
||||||
view.setTag(holder);
|
|
||||||
} else {
|
|
||||||
holder = (SearchViewHolder) view.getTag();
|
|
||||||
}
|
|
||||||
holder.onSetValues(sync);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItems(List<MangaSync> syncs) {
|
|
||||||
setNotifyOnChange(false);
|
|
||||||
clear();
|
|
||||||
addAll(syncs);
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SearchViewHolder {
|
|
||||||
|
|
||||||
@Bind(R.id.myanimelist_result_title) TextView title;
|
|
||||||
|
|
||||||
public SearchViewHolder(View view) {
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSetValues(MangaSync sync) {
|
|
||||||
title.setText(sync.title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.updater.GithubUpdateChecker
|
import eu.kanade.tachiyomi.data.updater.GithubUpdateChecker
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateDownloader
|
import eu.kanade.tachiyomi.data.updater.UpdateDownloader
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil
|
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
@ -109,7 +108,7 @@ class SettingsAboutFragment : SettingsNestedFragment() {
|
||||||
UpdateDownloader(activity.applicationContext).execute(downloadLink)
|
UpdateDownloader(activity.applicationContext).execute(downloadLink)
|
||||||
}.show()
|
}.show()
|
||||||
} else {
|
} else {
|
||||||
ToastUtil.showShort(activity, getString(R.string.update_check_no_new_updates))
|
context.toast(R.string.update_check_no_new_updates)
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
@ -74,10 +74,10 @@ class SettingsAdvancedFragment : SettingsNestedFragment() {
|
||||||
dialog.incrementProgress(1)
|
dialog.incrementProgress(1)
|
||||||
}, {
|
}, {
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
ToastUtil.showShort(activity, getString(R.string.cache_delete_error))
|
context.toast(R.string.cache_delete_error)
|
||||||
}, {
|
}, {
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
ToastUtil.showShort(activity, getString(R.string.cache_deleted, deletedFiles.get()))
|
context.toast(getString(R.string.cache_deleted, deletedFiles.get()))
|
||||||
preference.summary = getString(R.string.used_cache, chapterCache.readableSize)
|
preference.summary = getString(R.string.used_cache, chapterCache.readableSize)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,15 @@ fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT)
|
||||||
Toast.makeText(this, resource, duration).show()
|
Toast.makeText(this, resource, duration).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a toast in this context.
|
||||||
|
* @param text the text to display.
|
||||||
|
* @param duration the duration of the toast. Defaults to short.
|
||||||
|
*/
|
||||||
|
fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT) {
|
||||||
|
Toast.makeText(this, text, duration).show()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to create a notification.
|
* Helper method to create a notification.
|
||||||
* @param func the function that will execute inside the builder.
|
* @param func the function that will execute inside the builder.
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
import android.support.annotation.DrawableRes
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.widget.ImageView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a drawable on a [ImageView] using [ContextCompat] for backwards compatibility.
|
||||||
|
*
|
||||||
|
* @param drawable id of drawable resource
|
||||||
|
*/
|
||||||
|
fun ImageView.setDrawableCompat(@DrawableRes drawable: Int?) {
|
||||||
|
if (drawable != null) {
|
||||||
|
setImageDrawable(ContextCompat.getDrawable(context, drawable))
|
||||||
|
} else {
|
||||||
|
setImageResource(android.R.color.transparent)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
|
||||||
|
open class SimpleTextWatcher : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable) {}
|
||||||
|
}
|
|
@ -5,8 +5,6 @@ import android.app.DialogFragment
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.text.method.PasswordTransformationMethod
|
import android.text.method.PasswordTransformationMethod
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
@ -14,6 +12,7 @@ import com.dd.processbutton.iml.ActionProcessButton
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
|
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
|
||||||
|
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
|
||||||
import kotlinx.android.synthetic.main.pref_account_login.view.*
|
import kotlinx.android.synthetic.main.pref_account_login.view.*
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
|
|
||||||
|
@ -54,11 +53,7 @@ abstract class LoginDialogPreference : DialogFragment() {
|
||||||
|
|
||||||
show_password.isEnabled = password.text.isNullOrEmpty()
|
show_password.isEnabled = password.text.isNullOrEmpty()
|
||||||
|
|
||||||
password.addTextChangedListener(object : TextWatcher {
|
password.addTextChangedListener(object : SimpleTextWatcher() {
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable) {}
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
if (s.length == 0) {
|
if (s.length == 0) {
|
||||||
show_password.isEnabled = true
|
show_password.isEnabled = true
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/chapter_list"
|
android:id="@+id/recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginLeft="16dp"
|
android:layout_marginLeft="16dp"
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
android:theme="@style/AppTheme.Popup">
|
android:theme="@style/AppTheme.Popup">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/action_sort"
|
android:id="@+id/sort_btn"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
@ -52,9 +52,9 @@
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_toEndOf="@+id/action_sort"
|
android:layout_toEndOf="@+id/sort_btn"
|
||||||
android:layout_toLeftOf="@+id/action_next_unread"
|
android:layout_toLeftOf="@+id/next_unread_btn"
|
||||||
android:layout_toRightOf="@+id/action_sort"
|
android:layout_toRightOf="@+id/sort_btn"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
android:background="@color/white"/>
|
android:background="@color/white"/>
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/action_show_unread"
|
android:id="@+id/show_unread"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
android:title="@string/action_show_unread"/>
|
android:title="@string/action_show_unread"/>
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/action_show_downloaded"
|
android:id="@+id/show_downloaded"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/action_next_unread"
|
android:id="@+id/next_unread_btn"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
|
|
|
@ -6,7 +6,7 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.0.0-beta6'
|
classpath 'com.android.tools.build:gradle:2.1.0-alpha1'
|
||||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||||
classpath 'me.tatarka:gradle-retrolambda:3.2.4'
|
classpath 'me.tatarka:gradle-retrolambda:3.2.4'
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
||||||
|
|
Loading…
Reference in a new issue