Browse Source

Manga in Kotlin. Expect some errors yet

len 10 năm trước cách đây
mục cha
commit
f49577bc77
36 tập tin đã thay đổi với 1928 bổ sung2196 xóa
  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'