mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 21:41:50 +01:00
Manga in Kotlin. Expect some errors yet
This commit is contained in:
parent
a87c65872c
commit
f49577bc77
@ -100,7 +100,7 @@ apt {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
final SUPPORT_LIBRARY_VERSION = '23.2.0'
|
||||
final SUPPORT_LIBRARY_VERSION = '23.2.1'
|
||||
final DAGGER_VERSION = '2.0.2'
|
||||
final OKHTTP_VERSION = '3.2.0'
|
||||
final RETROFIT_VERSION = '2.0.0-beta4'
|
||||
|
@ -66,14 +66,14 @@ open class BaseActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
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
|
||||
textView.setTextColor(Color.WHITE)
|
||||
snack.show()
|
||||
}
|
||||
|
||||
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)
|
||||
.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.main.MainActivity
|
||||
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.EndlessListScrollListener
|
||||
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
|
||||
if (!presenter.isValidSource(source)) {
|
||||
spinner.setSelection(presenter.findFirstValidSource())
|
||||
ToastUtil.showShort(activity, R.string.source_requires_login)
|
||||
context.toast(R.string.source_requires_login)
|
||||
} else {
|
||||
selectedIndex = position
|
||||
presenter.setEnabledSource(selectedIndex)
|
||||
@ -430,7 +430,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
||||
val selectedManga = adapter.getItem(position)
|
||||
|
||||
val intent = MangaActivity.newIntent(activity, selectedManga)
|
||||
intent.putExtra(MangaActivity.MANGA_ONLINE, true)
|
||||
intent.putExtra(MangaActivity.FROM_CATALOGUE, true)
|
||||
startActivity(intent)
|
||||
return false
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import android.support.design.widget.TabLayout
|
||||
import android.support.v7.view.ActionMode
|
||||
import android.support.v7.widget.SearchView
|
||||
import android.view.*
|
||||
import butterknife.ButterKnife
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
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.category.CategoryActivity
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.ToastUtil
|
||||
import eu.kanade.tachiyomi.util.inflate
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import kotlinx.android.synthetic.main.fragment_library.*
|
||||
@ -125,7 +123,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||
|
||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||
setToolbarTitle(getString(R.string.label_library))
|
||||
ButterKnife.bind(this, view)
|
||||
|
||||
appBar = (activity as MainActivity).appBar
|
||||
tabs = appBar.inflate(R.layout.library_tab_layout) as TabLayout
|
||||
@ -369,7 +366,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||
startActivityForResult(Intent.createChooser(intent,
|
||||
getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN)
|
||||
} 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()
|
||||
true
|
||||
}
|
||||
.positiveText(R.string.button_ok)
|
||||
.negativeText(R.string.button_cancel)
|
||||
.positiveText(android.R.string.ok)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.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.data.updater.GithubUpdateChecker
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateDownloader
|
||||
import eu.kanade.tachiyomi.util.ToastUtil
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
@ -109,7 +108,7 @@ class SettingsAboutFragment : SettingsNestedFragment() {
|
||||
UpdateDownloader(activity.applicationContext).execute(downloadLink)
|
||||
}.show()
|
||||
} else {
|
||||
ToastUtil.showShort(activity, getString(R.string.update_check_no_new_updates))
|
||||
context.toast(R.string.update_check_no_new_updates)
|
||||
}
|
||||
}, {
|
||||
it.printStackTrace()
|
||||
|
@ -7,7 +7,7 @@ import com.afollestad.materialdialogs.MaterialDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.util.ToastUtil
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
@ -74,10 +74,10 @@ class SettingsAdvancedFragment : SettingsNestedFragment() {
|
||||
dialog.incrementProgress(1)
|
||||
}, {
|
||||
dialog.dismiss()
|
||||
ToastUtil.showShort(activity, getString(R.string.cache_delete_error))
|
||||
context.toast(R.string.cache_delete_error)
|
||||
}, {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
@ -17,6 +17,15 @@ fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT)
|
||||
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.
|
||||
* @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.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.text.method.PasswordTransformationMethod
|
||||
import android.view.View
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
@ -14,6 +12,7 @@ import com.dd.processbutton.iml.ActionProcessButton
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
|
||||
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
|
||||
import kotlinx.android.synthetic.main.pref_account_login.view.*
|
||||
import rx.Subscription
|
||||
|
||||
@ -54,11 +53,7 @@ abstract class LoginDialogPreference : DialogFragment() {
|
||||
|
||||
show_password.isEnabled = password.text.isNullOrEmpty()
|
||||
|
||||
password.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {}
|
||||
|
||||
password.addTextChangedListener(object : SimpleTextWatcher() {
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
if (s.length == 0) {
|
||||
show_password.isEnabled = true
|
||||
|
@ -14,7 +14,7 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/chapter_list"
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
@ -41,7 +41,7 @@
|
||||
android:theme="@style/AppTheme.Popup">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/action_sort"
|
||||
android:id="@+id/sort_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
@ -52,9 +52,9 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toEndOf="@+id/action_sort"
|
||||
android:layout_toLeftOf="@+id/action_next_unread"
|
||||
android:layout_toRightOf="@+id/action_sort"
|
||||
android:layout_toEndOf="@+id/sort_btn"
|
||||
android:layout_toLeftOf="@+id/next_unread_btn"
|
||||
android:layout_toRightOf="@+id/sort_btn"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<View
|
||||
@ -64,7 +64,7 @@
|
||||
android:background="@color/white"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/action_show_unread"
|
||||
android:id="@+id/show_unread"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
@ -73,7 +73,7 @@
|
||||
android:title="@string/action_show_unread"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/action_show_downloaded"
|
||||
android:id="@+id/show_downloaded"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
@ -90,7 +90,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/action_next_unread"
|
||||
android:id="@+id/next_unread_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
|
@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
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 'me.tatarka:gradle-retrolambda:3.2.4'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
||||
|
Loading…
Reference in New Issue
Block a user