Browse Source

Manga in Kotlin. Expect some errors yet

len 9 years ago
parent
commit
f49577bc77
36 changed files with 1928 additions and 2196 deletions
  1. 1 1
      app/build.gradle
  2. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt
  3. 3 3
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt
  4. 3 6
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt
  5. 0 164
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.java
  6. 118 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt
  7. 0 56
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.java
  8. 82 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
  9. 0 57
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.java
  10. 40 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt
  11. 0 439
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.java
  12. 362 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt
  13. 0 150
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.java
  14. 116 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt
  15. 0 286
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.java
  16. 264 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt
  17. 0 244
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java
  18. 179 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt
  19. 0 177
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.java
  20. 173 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt
  21. 0 151
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListDialogFragment.java
  22. 126 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListDialogFragment.kt
  23. 0 181
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListFragment.java
  24. 168 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListFragment.kt
  25. 0 197
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.java
  26. 191 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt
  27. 0 61
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListSearchAdapter.java
  28. 46 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListSearchAdapter.kt
  29. 1 2
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt
  30. 3 3
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt
  31. 9 0
      app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt
  32. 18 0
      app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt
  33. 12 0
      app/src/main/java/eu/kanade/tachiyomi/widget/SimpleTextWatcher.kt
  34. 2 7
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt
  35. 8 8
      app/src/main/res/layout/fragment_manga_chapters.xml
  36. 1 1
      build.gradle

+ 1 - 1
app/build.gradle

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

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt

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

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt

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

+ 3 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt

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

+ 0 - 164
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.java

@@ -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 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt

@@ -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]
+        }
+
+    }
+
+}

+ 0 - 56
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.java

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

+ 82 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt

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

+ 0 - 57
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.java

@@ -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;
-    }
-}

+ 40 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt

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

+ 0 - 439
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.java

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

+ 362 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt

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

+ 0 - 150
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.java

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

+ 116 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt

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

+ 0 - 286
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.java

@@ -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;
-    }
-
-}

+ 264 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt

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

+ 0 - 244
app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java

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

+ 179 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt

@@ -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
+    }
+
+}

+ 0 - 177
app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.java

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

+ 173 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt

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

+ 0 - 151
app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListDialogFragment.java

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

+ 126 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListDialogFragment.kt

@@ -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
+
+}

+ 0 - 181
app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListFragment.java

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

+ 168 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListFragment.kt

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

+ 0 - 197
app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.java

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

+ 191 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt

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

+ 0 - 61
app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListSearchAdapter.java

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

+ 46 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListSearchAdapter.kt

@@ -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
+        }
+    }
+}

+ 1 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt

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

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt

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

+ 9 - 0
app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt

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

+ 18 - 0
app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt

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

+ 12 - 0
app/src/main/java/eu/kanade/tachiyomi/widget/SimpleTextWatcher.kt

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

+ 2 - 7
app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt

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

+ 8 - 8
app/src/main/res/layout/fragment_manga_chapters.xml

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

+ 1 - 1
build.gradle

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