diff --git a/app/build.gradle b/app/build.gradle index e1900aa5ff..63fcd4d963 100644 --- a/app/build.gradle +++ b/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' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8bf4d710ec..47deba4508 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,10 +15,10 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:hardwareAccelerated="true" - android:theme="@style/AppTheme" > + android:theme="@style/Theme.Tachiyomi" > + android:theme="@style/Theme.BrandedLaunch"> diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 965a9f8984..76e36e7a44 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.preference import android.content.Context import android.os.Environment import android.preference.PreferenceManager +import android.support.annotation.StyleRes import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.RxSharedPreferences import eu.kanade.tachiyomi.R @@ -188,6 +189,10 @@ class PreferencesHelper(private val context: Context) { return rxPrefs.getInteger(getKey(R.string.pref_library_update_interval_key), 0) } + fun getTheme(): Preference { + return rxPrefs.getInteger(getKey(R.string.pref_theme_key), R.style.Theme_Tachiyomi) + } + fun filterDownloaded(): Preference { return rxPrefs.getBoolean(getKey(R.string.pref_filter_downloaded), false) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt index cc2749fffa..879caa31f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt +++ b/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() }) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt index 02f640f510..1a8a5e3d95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt +++ b/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.* @@ -179,7 +179,7 @@ class CatalogueFragment : BaseRxFragment(), 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) @@ -431,7 +431,7 @@ class CatalogueFragment : BaseRxFragment(), 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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt index 8eb52283f0..d46749b970 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt @@ -4,6 +4,7 @@ import android.support.v4.content.ContextCompat import android.view.View import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.util.getResourceColor import kotlinx.android.synthetic.main.item_catalogue_list.view.* /** @@ -18,8 +19,8 @@ import kotlinx.android.synthetic.main.item_catalogue_list.view.* class CatalogueListHolder(private val view: View, adapter: CatalogueAdapter, listener: OnListItemClickListener) : CatalogueHolder(view, adapter, listener) { - private val favoriteColor = ContextCompat.getColor(view.context, R.color.hint_text) - private val unfavoriteColor = ContextCompat.getColor(view.context, R.color.primary_text) + private val favoriteColor = view.context.theme.getResourceColor(android.R.attr.textColorHint) + private val unfavoriteColor = view.context.theme.getResourceColor(android.R.attr.textColorPrimary) /** * Method called from [CatalogueAdapter.onBindViewHolder]. It updates the data for this diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt index 3d24d793d0..2be5417360 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt @@ -12,7 +12,9 @@ import android.view.MenuItem import com.afollestad.materialdialogs.MaterialDialog import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.setTheme import eu.kanade.tachiyomi.data.database.models.Category +import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener @@ -57,6 +59,7 @@ class CategoryActivity : BaseRxActivity(), ActionMode.Callbac } override fun onCreate(savedInstanceState: Bundle?) { + this.setTheme() super.onCreate(savedInstanceState) // Inflate activity_edit_categories.xml. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt index fd6436062f..7864456bb0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt @@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.util.setInformationDrawable +import eu.kanade.tachiyomi.util.setDrawableCompat import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_download_queue.* import nucleus.factory.RequiresPresenter @@ -191,7 +191,7 @@ class DownloadFragment : BaseRxFragment() { */ private fun setInformationView() { if (presenter.downloadQueue.isEmpty()) { - ( activity as MainActivity).image_view.setInformationDrawable(R.drawable.ic_file_download_grey_128dp) + ( activity as MainActivity).image_view.setDrawableCompat(R.drawable.ic_file_download_grey_128dp) ( activity as MainActivity).text_label.text = getString(R.string.information_no_downloads) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index 9e941e3df4..89ce1ead83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/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,9 +19,8 @@ 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.setInformationDrawable +import eu.kanade.tachiyomi.util.setDrawableCompat import eu.kanade.tachiyomi.util.toast import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_library.* @@ -127,7 +125,6 @@ class LibraryFragment : BaseRxFragment(), 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 @@ -264,10 +261,10 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback fun onNextLibraryUpdate(categories: List, mangaMap: Map>) { // Check if library is empty and update information accordingly. if (mangaMap.isEmpty()) { - (activity as MainActivity).image_view.setInformationDrawable(R.drawable.ic_book_grey_128dp) + (activity as MainActivity).image_view.setDrawableCompat(R.drawable.ic_book_grey_128dp) (activity as MainActivity).text_label.text = getString(R.string.information_empty_library) } else { - ( activity as MainActivity).image_view.setInformationDrawable(null) + ( activity as MainActivity).image_view.setDrawableCompat(null) ( activity as MainActivity).text_label.text = "" } @@ -380,7 +377,7 @@ class LibraryFragment : BaseRxFragment(), 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) } } @@ -430,8 +427,8 @@ class LibraryFragment : BaseRxFragment(), 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() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 770ef5d32d..bdecd356c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -6,13 +6,15 @@ import android.support.v4.app.Fragment import android.support.v4.view.GravityCompat import android.view.MenuItem import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment import eu.kanade.tachiyomi.ui.download.DownloadFragment import eu.kanade.tachiyomi.ui.library.LibraryFragment import eu.kanade.tachiyomi.ui.recent.RecentChaptersFragment import eu.kanade.tachiyomi.ui.setting.SettingsActivity -import eu.kanade.tachiyomi.util.setInformationDrawable +import eu.kanade.tachiyomi.util.setDrawableCompat +import eu.kanade.tachiyomi.util.setTheme import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.toolbar.* import nucleus.view.ViewWithPresenter @@ -22,7 +24,7 @@ class MainActivity : BaseActivity() { override fun onCreate(savedState: Bundle?) { - setTheme(R.style.AppTheme); + this.setTheme() super.onCreate(savedState) // Do not let the launcher create a new activity @@ -48,7 +50,7 @@ class MainActivity : BaseActivity() { nav_view.setNavigationItemSelectedListener( { menuItem -> // Make information view invisible - image_view.setInformationDrawable(null) + image_view.setDrawableCompat(null) text_label.text = "" when (menuItem.itemId) { @@ -84,6 +86,8 @@ class MainActivity : BaseActivity() { setFragment(LibraryFragment.newInstance()) } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { android.R.id.home -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.java deleted file mode 100644 index d357b0503b..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.java +++ /dev/null @@ -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 { - - @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]; - } - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt new file mode 100644 index 0000000000..e65b7eb70e --- /dev/null +++ b/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() { + + 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] + } + + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.java deleted file mode 100644 index c976f9e28f..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.java +++ /dev/null @@ -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 { - - @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 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); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt new file mode 100644 index 0000000000..3b68a39869 --- /dev/null +++ b/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() { + + /** + * 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) + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.java deleted file mode 100644 index ba7db26bcb..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.java +++ /dev/null @@ -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 { - - 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 chapters) { - mItems = chapters; - notifyDataSetChanged(); - } - - public ChaptersFragment getFragment() { - return fragment; - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt new file mode 100644 index 0000000000..9282d425b1 --- /dev/null +++ b/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() { + + 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) { + mItems = chapters + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.java deleted file mode 100644 index 462f13df1d..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.java +++ /dev/null @@ -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 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 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 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 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 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 chapters) { - getPresenter().markChaptersRead(chapters, true); - return true; - } - - protected boolean onMarkAsUnread(Observable chapters) { - getPresenter().markChaptersRead(chapters, false); - return true; - } - - public boolean onMarkPreviousAsRead(Chapter chapter) { - getPresenter().markPreviousChaptersAsRead(chapter); - return true; - } - - protected boolean onDownload(Observable chapters) { - DownloadService.start(getActivity()); - - Observable observable = chapters - .doOnCompleted(adapter::notifyDataSetChanged); - - getPresenter().downloadChapters(observable); - destroyActionModeIfNeeded(); - return true; - } - - protected boolean onDelete(Observable chapters) { - int size = adapter.getSelectedItemCount(); - - MaterialDialog dialog = new MaterialDialog.Builder(getActivity()) - .title(R.string.deleting) - .progress(false, size, true) - .cancelable(false) - .show(); - - Observable 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()); - } - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt new file mode 100644 index 0000000000..942f78c724 --- /dev/null +++ b/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(), 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) { + // 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() + + 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 { + 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) { + presenter.markChaptersRead(chapters, true) + } + + fun onMarkAsUnread(chapters: Observable) { + presenter.markChaptersRead(chapters, false) + } + + fun onMarkPreviousAsRead(chapter: Chapter) { + presenter.markPreviousChaptersAsRead(chapter) + } + + fun onDownload(chapters: Observable) { + DownloadService.start(activity) + + val observable = chapters.doOnCompleted { adapter.notifyDataSetChanged() } + + presenter.downloadChapters(observable) + destroyActionModeIfNeeded() + } + + fun onDelete(chapters: Observable) { + 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() + } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.java deleted file mode 100644 index 883cc79cf7..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.java +++ /dev/null @@ -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 = 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(); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt new file mode 100644 index 0000000000..5e8ec0b435 --- /dev/null +++ b/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() + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.java deleted file mode 100644 index 8a86e277b2..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.java +++ /dev/null @@ -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 { - - @Inject DatabaseHelper db; - @Inject SourceManager sourceManager; - @Inject PreferencesHelper preferences; - @Inject DownloadManager downloadManager; - - private Manga manga; - private Source source; - private List chapters; - @State boolean hasRequested; - - private PublishSubject> 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> getOnlineChaptersObs() { - return source.pullChaptersFromNetwork(manga.url) - .subscribeOn(Schedulers.io()) - .flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters, source)) - .observeOn(AndroidSchedulers.mainThread()); - } - - private Observable> getDbChaptersObs() { - return chaptersSubject.flatMap(this::applyChapterFilters) - .observeOn(AndroidSchedulers.mainThread()); - } - - private Observable> applyChapterFilters(List chapters) { - Observable 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 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 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 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 selectedChapters) { - add(selectedChapters - .toList() - .subscribe(chapters -> { - EventBus.getDefault().postSticky(new DownloadChaptersEvent(manga, chapters)); - })); - } - - public void deleteChapters(Observable 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 getChapters() { - return chapters; - } - - public boolean hasRequested() { - return hasRequested; - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt new file mode 100644 index 0000000000..9dc5cc5b07 --- /dev/null +++ b/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() { + + @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 + private set + + lateinit var chaptersSubject: PublishSubject> + 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> { + return source.pullChaptersFromNetwork(manga.url) + .subscribeOn(Schedulers.io()) + .flatMap { chapters -> db.insertOrRemoveChapters(manga, chapters, source) } + .observeOn(AndroidSchedulers.mainThread()) + } + + fun getDbChaptersObs(): Observable> { + return chaptersSubject + .flatMap { applyChapterFilters(it) } + .observeOn(AndroidSchedulers.mainThread()) + } + + fun getChapterStatusObs(): Observable { + return downloadManager.queue.statusObservable + .observeOn(AndroidSchedulers.mainThread()) + .filter { download -> download.manga.id == manga.id } + .doOnNext { updateChapterStatus(it) } + } + + private fun applyChapterFilters(chapters: List): Observable> { + 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, 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) { + add(selectedChapters.toList() + .subscribe { chapters -> EventBus.getDefault().postSticky(DownloadChaptersEvent(manga, chapters)) }) + } + + fun deleteChapters(selectedChapters: Observable) { + 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() + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java deleted file mode 100644 index 1c1e855f21..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java +++ /dev/null @@ -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 { - /** - * 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); - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt new file mode 100644 index 0000000000..0840e4b44c --- /dev/null +++ b/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() { + + 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 + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.java deleted file mode 100644 index 8fcbe9ec72..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.java +++ /dev/null @@ -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 { - - /** - * 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 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); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt new file mode 100644 index 0000000000..d472e829cb --- /dev/null +++ b/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() { + + /** + * 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 { + return source.pullMangaFromNetwork(manga.url) + .flatMap { networkManga -> + manga.copyFrom(networkManga) + db.insertManga(manga).executeAsBlocking() + Observable.just(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) + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListDialogFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListDialogFragment.java deleted file mode 100644 index 6d4d9f3bc2..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListDialogFragment.java +++ /dev/null @@ -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 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 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) { - - } - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListDialogFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListDialogFragment.kt new file mode 100644 index 0000000000..1ac704282b --- /dev/null +++ b/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 + 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() + + 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) { + 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 + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListFragment.java deleted file mode 100644 index e532c1c190..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListFragment.java +++ /dev/null @@ -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 { - - @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 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); - } - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListFragment.kt new file mode 100644 index 0000000000..7d31eeb026 --- /dev/null +++ b/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() { + + 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) { + 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() + } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.java deleted file mode 100644 index 0ccff700dd..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.java +++ /dev/null @@ -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 { - - @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> getSearchResultsObservable() { - Observable> 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); - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt new file mode 100644 index 0000000000..16abc10f6a --- /dev/null +++ b/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() { + + @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> { + return query?.let { query -> + val observable: Observable> + 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 { + 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(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 { + 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) + } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListSearchAdapter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListSearchAdapter.java deleted file mode 100644 index 9eed6d2690..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListSearchAdapter.java +++ /dev/null @@ -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 { - - 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 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); - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListSearchAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListSearchAdapter.kt new file mode 100644 index 0000000000..8ce5207d18 --- /dev/null +++ b/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(context, R.layout.dialog_myanimelist_search_item, ArrayList()) { + + 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) { + setNotifyOnChange(false) + clear() + addAll(syncs) + notifyDataSetChanged() + } + + class SearchViewHolder(private val view: View) { + + fun onSetValues(sync: MangaSync) { + view.myanimelist_result_title.text = sync.title + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java index cec18311ee..40ac08f6e3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; import android.os.Bundle; @@ -85,6 +86,7 @@ public class ReaderActivity extends BaseRxActivity { initializeSettings(); + maxBitmapSize = GLUtil.getMaxTextureSize(); } @@ -393,12 +395,12 @@ public class ReaderActivity extends BaseRxActivity { View rootView = getWindow().getDecorView().getRootView(); if (theme == BLACK_THEME) { rootView.setBackgroundColor(Color.BLACK); - pageNumber.setTextColor(ContextCompat.getColor(this, R.color.light_grey)); - pageNumber.setBackgroundColor(ContextCompat.getColor(this, R.color.page_number_background_black)); + pageNumber.setTextColor(ContextCompat.getColor(this, R.color.textColorPrimaryDark)); + pageNumber.setBackgroundColor(ContextCompat.getColor(this, R.color.backgroundDark)); } else { rootView.setBackgroundColor(Color.WHITE); - pageNumber.setTextColor(ContextCompat.getColor(this, R.color.primary_text)); - pageNumber.setBackgroundColor(ContextCompat.getColor(this, R.color.page_number_background)); + pageNumber.setTextColor(ContextCompat.getColor(this, R.color.textColorPrimaryLight)); + pageNumber.setBackgroundColor(ContextCompat.getColor(this, R.color.backgroundLight)); } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt index 351f3beab8..7686fd0eec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt @@ -17,8 +17,8 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity class PageDecodeErrorLayout(context: Context) : LinearLayout(context) { - private val lightGreyColor = ContextCompat.getColor(context, R.color.light_grey) - private val blackColor = ContextCompat.getColor(context, R.color.primary_text) + private val lightGreyColor = ContextCompat.getColor(context, android.R.attr.textColorHint) + private val blackColor = ContextCompat.getColor(context, android.R.attr.textColorPrimary) init { orientation = LinearLayout.VERTICAL diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt index f7c91069f1..467b083afe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt @@ -74,12 +74,12 @@ class PagerReaderFragment : BaseFragment() { /** * Text color for black theme. */ - private val lightGreyColor by lazy { ContextCompat.getColor(context, R.color.light_grey) } + private val lightGreyColor by lazy { ContextCompat.getColor(context, android.R.attr.textColorHint) } /** * Text color for white theme. */ - private val blackColor by lazy { ContextCompat.getColor(context, R.color.primary_text) } + private val blackColor by lazy { ContextCompat.getColor(context, android.R.attr.textColorPrimary) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { return inflater.inflate(R.layout.item_pager_reader, container, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt index ac7d9e8a99..2f3ed8d64c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt @@ -17,7 +17,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.reader.ReaderActivity -import eu.kanade.tachiyomi.util.setInformationDrawable +import eu.kanade.tachiyomi.util.setDrawableCompat import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_recent_chapters.* import nucleus.factory.RequiresPresenter @@ -80,7 +80,7 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib setToolbarTitle(R.string.label_recent_updates) // Check if recent chapters is empty and update information accordingly. - (activity as MainActivity).image_view.setInformationDrawable(R.drawable.ic_history_grey_128dp) + (activity as MainActivity).image_view.setDrawableCompat(R.drawable.ic_history_grey_128dp) (activity as MainActivity).text_label.text = getString(R.string.information_no_recent) (activity as MainActivity).information_layout.bringToFront() } @@ -130,7 +130,7 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib */ fun onNextMangaChapters(chapters: List) { if (!chapters.isEmpty()) { - ( activity as MainActivity).image_view.setInformationDrawable(null) + ( activity as MainActivity).image_view.setDrawableCompat(null) ( activity as MainActivity).text_label.text = "" } adapter.setItems(chapters) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt index 0b811cd860..25eb0bd945 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.recent -import android.support.v4.content.ContextCompat +import android.content.Context import android.view.View import android.widget.PopupMenu import eu.kanade.tachiyomi.R @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.MangaChapter import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder +import eu.kanade.tachiyomi.util.getResourceColor import kotlinx.android.synthetic.main.item_recent_chapter.view.* import rx.Observable @@ -26,12 +27,12 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte /** * Color of read chapter */ - private val readColor = ContextCompat.getColor(view.context, R.color.hint_text) + private var readColor = view.context.theme.getResourceColor(android.R.attr.textColorHint) /** * Color of unread chapter */ - private val unreadColor = ContextCompat.getColor(view.context, R.color.primary_text) + private var unreadColor = view.context.theme.getResourceColor(android.R.attr.textColorPrimary) /** * Object containing chapter information @@ -41,6 +42,7 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte init { //Set OnClickListener for download menu itemView.chapterMenu.setOnClickListener { v -> v.post({ showPopupMenu(v) }) } + } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt index 6bcfa82cd4..9b9e5d390b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt +++ b/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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt index 59486fbf6f..e0482e65fe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.setting +import android.content.Intent import android.os.Bundle import android.support.v14.preference.PreferenceFragment import eu.kanade.tachiyomi.R @@ -9,7 +10,9 @@ import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.ui.base.activity.BaseActivity +import eu.kanade.tachiyomi.ui.main.MainActivity import kotlinx.android.synthetic.main.toolbar.* +import eu.kanade.tachiyomi.util.setTheme import javax.inject.Inject class SettingsActivity : BaseActivity() { @@ -21,6 +24,7 @@ class SettingsActivity : BaseActivity() { @Inject lateinit var syncManager: MangaSyncManager override fun onCreate(savedState: Bundle?) { + setTheme() super.onCreate(savedState) setContentView(R.layout.activity_preferences) applicationComponent.inject(this) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt index 2ef8aa44b2..e51c9c7ab4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt +++ b/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) }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt index 7b6f58f172..de1aab9bde 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt @@ -1,10 +1,15 @@ package eu.kanade.tachiyomi.ui.setting +import android.content.Intent import android.os.Bundle +import android.support.v4.app.TaskStackBuilder import android.support.v7.preference.Preference import android.view.View +import android.widget.Toast import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.library.LibraryUpdateAlarm +import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.util.ToastUtil import eu.kanade.tachiyomi.widget.preference.IntListPreference import eu.kanade.tachiyomi.widget.preference.LibraryColumnsDialog import eu.kanade.tachiyomi.widget.preference.SimpleDialogPreference @@ -29,6 +34,10 @@ class SettingsGeneralFragment : SettingsNestedFragment() { findPreference(getString(R.string.pref_library_update_interval_key)) as IntListPreference } + val themePreference by lazy { + findPreference(getString(R.string.pref_theme_key)) as IntListPreference + } + var columnsSubscription: Subscription? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -36,6 +45,15 @@ class SettingsGeneralFragment : SettingsNestedFragment() { LibraryUpdateAlarm.startAlarm(activity, (newValue as String).toInt()) true } + + themePreference.setOnPreferenceChangeListener { preference, newValue -> + // Rebuild activity's to apply themes. + TaskStackBuilder.create(activity) + .addNextIntent(Intent(activity, MainActivity::class.java)) + .addNextIntent(activity.intent) + .startActivities() + true + } } override fun onResume() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt index fcd1bb7142..e7474967c0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt @@ -7,6 +7,8 @@ import android.content.Context import android.support.annotation.StringRes import android.support.v4.app.NotificationCompat import android.widget.Toast +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper /** * Display a toast in this context. @@ -17,6 +19,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. @@ -39,4 +50,13 @@ val Context.notificationManager : NotificationManager * @return the alarm manager. */ val Context.alarmManager: AlarmManager - get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager \ No newline at end of file + get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager + +fun Context.setTheme() +{ + when (PreferencesHelper(this).getTheme().get()) + { + 1 -> this.setTheme(R.style.Theme_Tachiyomi) + 2 -> this.setTheme(R.style.Theme_Tachiyomi_Dark) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt index 312ead5afa..2456e4f856 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt @@ -1,19 +1,18 @@ package eu.kanade.tachiyomi.util +import android.support.annotation.DrawableRes import android.support.v4.content.ContextCompat import android.widget.ImageView -import org.jetbrains.annotations.Nullable /** - * Set information image + * Set a drawable on a [ImageView] using [ContextCompat] for backwards compatibility. * - * @param drawable id of image - * @null makes image transparent + * @param drawable id of drawable resource */ -fun ImageView.setInformationDrawable(@Nullable drawable: Int?) { - if (drawable == null) { - setImageResource(android.R.color.transparent) - } else { +fun ImageView.setDrawableCompat(@DrawableRes drawable: Int?) { + if (drawable != null) { setImageDrawable(ContextCompat.getDrawable(context, drawable)) + } else { + setImageResource(android.R.color.transparent) } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ThemeExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ThemeExtensions.kt new file mode 100644 index 0000000000..8c5afdc5ca --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ThemeExtensions.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.util + +import android.content.Context +import android.content.res.Resources +import android.support.annotation.StringRes +import android.support.annotation.StyleRes +import android.support.v7.view.ContextThemeWrapper + +fun Resources.Theme.getResourceColor(@StringRes resource: Int) : Int { + val typedArray = this.obtainStyledAttributes(intArrayOf(resource)) + val attrValue = typedArray.getColor(0, 0) + typedArray.recycle() + return attrValue +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleTextWatcher.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleTextWatcher.kt new file mode 100644 index 0000000000..ee39d58719 --- /dev/null +++ b/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) {} +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt index e37ded4754..7fa2473496 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt +++ b/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 diff --git a/app/src/main/res/drawable-hdpi/reader_background_checkbox_selected.png b/app/src/main/res/drawable-hdpi/reader_background_checkbox_selected.png deleted file mode 100644 index 9908433c07..0000000000 Binary files a/app/src/main/res/drawable-hdpi/reader_background_checkbox_selected.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/reader_background_checkbox_unselected.png b/app/src/main/res/drawable-hdpi/reader_background_checkbox_unselected.png deleted file mode 100644 index 195e875fbd..0000000000 Binary files a/app/src/main/res/drawable-hdpi/reader_background_checkbox_unselected.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/reader_background_checkbox_selected.png b/app/src/main/res/drawable-ldpi/reader_background_checkbox_selected.png deleted file mode 100644 index d06d87c957..0000000000 Binary files a/app/src/main/res/drawable-ldpi/reader_background_checkbox_selected.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/reader_background_checkbox_unselected.png b/app/src/main/res/drawable-ldpi/reader_background_checkbox_unselected.png deleted file mode 100644 index 4899a16ffc..0000000000 Binary files a/app/src/main/res/drawable-ldpi/reader_background_checkbox_unselected.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/reader_background_checkbox_selected.png b/app/src/main/res/drawable-mdpi/reader_background_checkbox_selected.png deleted file mode 100644 index f0faf295f8..0000000000 Binary files a/app/src/main/res/drawable-mdpi/reader_background_checkbox_selected.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/reader_background_checkbox_unselected.png b/app/src/main/res/drawable-mdpi/reader_background_checkbox_unselected.png deleted file mode 100644 index 69e1ee734a..0000000000 Binary files a/app/src/main/res/drawable-mdpi/reader_background_checkbox_unselected.png and /dev/null differ diff --git a/app/src/main/res/drawable-v21/selector_chapter_light.xml b/app/src/main/res/drawable-v21/selector_chapter_light.xml index f92e970b2a..d1d55a05b4 100644 --- a/app/src/main/res/drawable-v21/selector_chapter_light.xml +++ b/app/src/main/res/drawable-v21/selector_chapter_light.xml @@ -4,10 +4,10 @@ android:color="?android:attr/colorControlHighlight"> + android:drawable="@color/dividerLight" /> - + diff --git a/app/src/main/res/drawable-v21/selector_item_edit_categories_dark.xml b/app/src/main/res/drawable-v21/selector_item_edit_categories_dark.xml new file mode 100644 index 0000000000..3391e4ea2c --- /dev/null +++ b/app/src/main/res/drawable-v21/selector_item_edit_categories_dark.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/selector_item_edit_categories_light.xml b/app/src/main/res/drawable-v21/selector_item_edit_categories_light.xml new file mode 100644 index 0000000000..57bb65957a --- /dev/null +++ b/app/src/main/res/drawable-v21/selector_item_edit_categories_light.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/touchable_background_white.xml b/app/src/main/res/drawable-v21/touchable_background_white.xml index 322353b5ff..5d6a94ecca 100644 --- a/app/src/main/res/drawable-v21/touchable_background_white.xml +++ b/app/src/main/res/drawable-v21/touchable_background_white.xml @@ -1,5 +1,5 @@ - + android:color="@color/dividerLight"> + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxhdpi/reader_background_checkbox_selected.png b/app/src/main/res/drawable-xxhdpi/reader_background_checkbox_selected.png deleted file mode 100644 index 287892b71b..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/reader_background_checkbox_selected.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/reader_background_checkbox_unselected.png b/app/src/main/res/drawable-xxhdpi/reader_background_checkbox_unselected.png deleted file mode 100644 index b279adbbd4..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/reader_background_checkbox_unselected.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/reader_background_checkbox_selected.png b/app/src/main/res/drawable-xxxhdpi/reader_background_checkbox_selected.png deleted file mode 100644 index 90c1bd6f56..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/reader_background_checkbox_selected.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/reader_background_checkbox_unselected.png b/app/src/main/res/drawable-xxxhdpi/reader_background_checkbox_unselected.png deleted file mode 100644 index 5a3af58d65..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/reader_background_checkbox_unselected.png and /dev/null differ diff --git a/app/src/main/res/drawable/header.png b/app/src/main/res/drawable/header.png index 9379dcb71e..504673244c 100644 Binary files a/app/src/main/res/drawable/header.png and b/app/src/main/res/drawable/header.png differ diff --git a/app/src/main/res/drawable/ic_check_box_outline_blank_white_24dp.xml b/app/src/main/res/drawable/ic_check_box_outline_blank_white_24dp.xml new file mode 100644 index 0000000000..3ecf661587 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_outline_blank_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/line_divider.xml b/app/src/main/res/drawable/line_divider.xml index 7c873bd96f..eca76daba5 100644 --- a/app/src/main/res/drawable/line_divider.xml +++ b/app/src/main/res/drawable/line_divider.xml @@ -6,6 +6,6 @@ android:width="1dp" android:height="1dp" /> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/reader_background_checkbox.xml b/app/src/main/res/drawable/reader_background_checkbox.xml deleted file mode 100644 index 2ace26fced..0000000000 --- a/app/src/main/res/drawable/reader_background_checkbox.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_chapter_light.xml b/app/src/main/res/drawable/selector_chapter_light.xml index 58df6ebf87..175d980ea9 100644 --- a/app/src/main/res/drawable/selector_chapter_light.xml +++ b/app/src/main/res/drawable/selector_chapter_light.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_item_edit_categories_dark.xml b/app/src/main/res/drawable/selector_item_edit_categories_dark.xml new file mode 100644 index 0000000000..2607d316f3 --- /dev/null +++ b/app/src/main/res/drawable/selector_item_edit_categories_dark.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_item_edit_categories_light.xml b/app/src/main/res/drawable/selector_item_edit_categories_light.xml new file mode 100644 index 0000000000..4cb278d904 --- /dev/null +++ b/app/src/main/res/drawable/selector_item_edit_categories_light.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/touchable_background_white.xml b/app/src/main/res/drawable/touchable_background_white.xml index 26044d48fb..4f6f0d76aa 100644 --- a/app/src/main/res/drawable/touchable_background_white.xml +++ b/app/src/main/res/drawable/touchable_background_white.xml @@ -1,6 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_edit_categories.xml b/app/src/main/res/layout/activity_edit_categories.xml index 55ae9acc0b..3e8ac1595f 100644 --- a/app/src/main/res/layout/activity_edit_categories.xml +++ b/app/src/main/res/layout/activity_edit_categories.xml @@ -15,8 +15,8 @@ android:layout_marginTop="?attr/actionBarSize" android:id="@+id/recycler" android:choiceMode="multipleChoice" - android:listSelector="@color/list_choice_pressed_bg_light" - tools:listitem="@layout/item_edit_categories"/> + tools:listitem="@layout/item_edit_categories" + /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index feb03e35d6..9ce768072e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -57,7 +57,7 @@ diff --git a/app/src/main/res/layout/card_myanimelist_personal.xml b/app/src/main/res/layout/card_myanimelist_personal.xml index 428984ced7..4ae8b21a1d 100644 --- a/app/src/main/res/layout/card_myanimelist_personal.xml +++ b/app/src/main/res/layout/card_myanimelist_personal.xml @@ -35,7 +35,7 @@ android:layout_width="fill_parent" android:layout_height="1dp" android:layout_below="@id/myanimelist_title_layout" - android:background="@color/list_choice_pressed_bg_light" /> + android:background="@color/dividerLight" /> + android:background="@color/dividerLight" /> + android:background="@color/dividerLight" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_catalogue.xml b/app/src/main/res/layout/fragment_catalogue.xml index 38956c3f0f..7f84049bd7 100644 --- a/app/src/main/res/layout/fragment_catalogue.xml +++ b/app/src/main/res/layout/fragment_catalogue.xml @@ -23,7 +23,7 @@ diff --git a/app/src/main/res/layout/fragment_library_category.xml b/app/src/main/res/layout/fragment_library_category.xml index 06ab5a9151..a19f0f7192 100644 --- a/app/src/main/res/layout/fragment_library_category.xml +++ b/app/src/main/res/layout/fragment_library_category.xml @@ -6,7 +6,7 @@ diff --git a/app/src/main/res/layout/fragment_manga_chapters.xml b/app/src/main/res/layout/fragment_manga_chapters.xml index d2aa66dee1..267404551b 100644 --- a/app/src/main/res/layout/fragment_manga_chapters.xml +++ b/app/src/main/res/layout/fragment_manga_chapters.xml @@ -14,7 +14,7 @@ android:orientation="vertical"> diff --git a/app/src/main/res/layout/item_catalogue_grid.xml b/app/src/main/res/layout/item_catalogue_grid.xml index 52b8f93573..a9d57f2615 100644 --- a/app/src/main/res/layout/item_catalogue_grid.xml +++ b/app/src/main/res/layout/item_catalogue_grid.xml @@ -5,7 +5,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/selector_chapter_light"> + android:background="?selectable_list_drawable"> @@ -39,12 +39,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" - android:background="@color/manga_unread_bg" + android:background="?android:attr/colorBackground" android:paddingBottom="1dp" android:paddingLeft="3dp" android:paddingRight="3dp" android:paddingTop="1dp" - android:textColor="@color/white" + android:textColor="?android:attr/textColorPrimary" android:textSize="12sp" android:visibility="gone"/> @@ -71,11 +71,11 @@ android:lineSpacingExtra="-4dp" android:maxLines="2" android:padding="8dp" - android:shadowColor="@color/primary_text" + android:shadowColor="?android:attr/textColorPrimary" android:shadowDx="0" android:shadowDy="0" android:shadowRadius="4" - android:textColor="@color/white" + android:textColor="@color/md_white_1000" android:textSize="14sp" app:typeface="ptsansNarrowBold" tools:text="Sample name"/> diff --git a/app/src/main/res/layout/item_chapter.xml b/app/src/main/res/layout/item_chapter.xml index 0c3635b7ce..749ff6b87a 100644 --- a/app/src/main/res/layout/item_chapter.xml +++ b/app/src/main/res/layout/item_chapter.xml @@ -51,7 +51,7 @@ android:layout_alignParentRight="true" android:layout_centerVertical="true" android:textAllCaps="true" - android:textColor="@color/accent_text" + android:textColor="?android:attr/textColorSecondary" android:textSize="12sp"/> diff --git a/app/src/main/res/layout/item_edit_categories.xml b/app/src/main/res/layout/item_edit_categories.xml index aa80ba9f36..b80cefbb6f 100644 --- a/app/src/main/res/layout/item_edit_categories.xml +++ b/app/src/main/res/layout/item_edit_categories.xml @@ -1,13 +1,13 @@ - + android:background="?attr/selectable_list_drawable" + > - + app:srcCompat="@drawable/ic_reorder_grey_24dp" + android:tint="?android:attr/textColorPrimary"/> \ No newline at end of file diff --git a/app/src/main/res/layout/item_recent_chapter.xml b/app/src/main/res/layout/item_recent_chapter.xml index cbf5798910..81819d6757 100644 --- a/app/src/main/res/layout/item_recent_chapter.xml +++ b/app/src/main/res/layout/item_recent_chapter.xml @@ -31,7 +31,6 @@ android:layout_alignParentRight="true" android:layout_centerVertical="true" android:textAllCaps="true" - android:textColor="@color/accent_text" android:textSize="12sp"/> @@ -51,7 +50,7 @@ android:layout_height="wrap_content" android:ellipsize="end" android:singleLine="true" - android:textAppearance="@style/TextAppearance.AppCompat.Medium" + android:textAppearance="@style/TextAppearance.Medium" tools:text="My manga"/> @@ -82,7 +81,7 @@ android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"> - \ No newline at end of file + app:tabMinWidth="75dp"/> \ No newline at end of file diff --git a/app/src/main/res/layout/navigation_header.xml b/app/src/main/res/layout/navigation_header.xml index 56a288c306..52bcad0d36 100644 --- a/app/src/main/res/layout/navigation_header.xml +++ b/app/src/main/res/layout/navigation_header.xml @@ -9,7 +9,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="centerCrop" - android:background="@drawable/header" /> + android:src="@drawable/header" /> + android:src="@drawable/test"/> + android:text="John Doe" + android:textAppearance="@style/TextAppearance.AppCompat.Body2" + android:visibility="gone"/> + android:text="email@email.com" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:visibility="gone"/> diff --git a/app/src/main/res/layout/pref_account_login.xml b/app/src/main/res/layout/pref_account_login.xml index 696abd9e23..bdceb41c1e 100644 --- a/app/src/main/res/layout/pref_account_login.xml +++ b/app/src/main/res/layout/pref_account_login.xml @@ -17,7 +17,7 @@ diff --git a/app/src/main/res/layout/reader_menu.xml b/app/src/main/res/layout/reader_menu.xml index d319134243..155f90b7fc 100644 --- a/app/src/main/res/layout/reader_menu.xml +++ b/app/src/main/res/layout/reader_menu.xml @@ -12,16 +12,15 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - android:background="@color/reader_menu_background" - android:theme="@style/AppTheme.Overlay.Dark" - app:popupTheme="@style/AppTheme.Popup" - android:elevation="4dp" /> + android:background="?colorPrimary" + android:elevation="4dp" + android:theme="?attr/actionBarTheme"/> @@ -35,7 +34,7 @@ android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_gravity="center_vertical" - android:textColor="@color/light_grey" + android:textColor="?android:attr/textColorHint" android:textSize="15sp" android:gravity="center_horizontal" android:layout_marginTop="12dp" @@ -47,7 +46,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_vertical" - android:theme="@style/AppTheme.Overlay.Dark" /> @@ -70,7 +68,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"> - - - - - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?colorPrimary" + android:orientation="vertical" + android:paddingBottom="5dp" + android:paddingLeft="5dp" + android:paddingRight="10dp" + android:paddingTop="5dp"> + android:layout_height="wrap_content"> + android:textColor="@color/colorAccent" + android:textSize="16sp"/> + android:layout_width="wrap_content" + android:layout_height="36dp" + android:gravity="center" + android:text="@string/pref_image_decoder" + style="@style/TextAppearance.Light"/> - + android:layout_width="wrap_content" + android:layout_height="36dp" + android:gravity="center" + android:text="@string/pref_reader_theme" + style="@style/AppTheme.CheckBox.Light"/> - + android:layout_width="wrap_content" + android:layout_height="36dp" + android:gravity="center" + android:text="@string/pref_enable_transitions" + style="@style/AppTheme.CheckBox.Light"/> - + android:layout_width="wrap_content" + android:layout_height="36dp" + android:gravity="center" + android:text="@string/pref_show_page_number" + style="@style/AppTheme.CheckBox.Light"/> - + android:layout_width="wrap_content" + android:layout_height="36dp" + android:gravity="center" + android:text="@string/pref_hide_status_bar" + style="@style/AppTheme.CheckBox.Light"/> - + android:layout_width="wrap_content" + android:layout_height="36dp" + android:gravity="center" + android:text="@string/pref_keep_screen_on" + style="@style/AppTheme.CheckBox.Light"/> - + style="@style/AppTheme.CheckBox.Light"/> + android:layout_height="wrap_content"/> \ No newline at end of file diff --git a/app/src/main/res/layout/tab_layout.xml b/app/src/main/res/layout/tab_layout.xml index 8737533de7..9d15feb5f4 100644 --- a/app/src/main/res/layout/tab_layout.xml +++ b/app/src/main/res/layout/tab_layout.xml @@ -5,7 +5,7 @@ android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" - android:theme="@style/AppTheme.Overlay.Dark" app:tabGravity="fill" android:background="@color/colorPrimary" - app:tabIndicatorColor="@color/white"/> \ No newline at end of file + android:theme="@style/AppTheme.Tab" + app:tabIndicatorColor="@android:color/white"/> \ No newline at end of file diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml index c621b5a32b..70ab813aec 100644 --- a/app/src/main/res/layout/toolbar.xml +++ b/app/src/main/res/layout/toolbar.xml @@ -1,7 +1,8 @@ - \ No newline at end of file + android:theme="?attr/actionBarTheme"/> \ No newline at end of file diff --git a/app/src/main/res/values-sw376dp/styles.xml b/app/src/main/res/values-sw376dp/styles.xml new file mode 100644 index 0000000000..6eab5b8a54 --- /dev/null +++ b/app/src/main/res/values-sw376dp/styles.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/styles.xml b/app/src/main/res/values-sw600dp/styles.xml new file mode 100644 index 0000000000..03a169806a --- /dev/null +++ b/app/src/main/res/values-sw600dp/styles.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml deleted file mode 100644 index 9413e05862..0000000000 --- a/app/src/main/res/values-v21/styles.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/values-v21/themes.xml b/app/src/main/res/values-v21/themes.xml new file mode 100644 index 0000000000..6a96facbb8 --- /dev/null +++ b/app/src/main/res/values-v21/themes.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 031c189eb8..8362ec2e99 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -22,6 +22,16 @@ @string/webtoon_viewer + + 1 + 2 + + + + "Light Theme" + "Dark Theme" + + 1 2 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index d9bca79b44..9726c8a626 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -16,4 +16,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 29ffc74df0..5f98319993 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,718 +1,62 @@ - @color/md_blue_A400 + + #54759e #435e7e - @color/md_blue_grey_900 - @color/md_blue_grey_100 + @color/md_blue_400 - @color/md_blue_grey_50 + + @color/md_black_1000_87 + @color/md_black_1000_54 + @color/md_black_1000_38 + @color/md_white_1000_12 - @color/colorPrimary - @color/colorPrimaryDark - @color/colorPrimaryLight - #E9F1FF + @color/md_grey_300 + @color/md_grey_100 + @color/md_grey_50 + @color/md_white_1000 - @color/md_light_dividers + @color/md_blue_400_38 - @color/md_white_1000 - #000000 - #000000 - #000000 - @color/md_green_900 + + @color/md_white_1000 + @color/md_white_1000_70 + @color/md_white_1000_50 + @color/md_black_1000_12 - @color/colorPrimaryLight + @color/md_black_1000 + @color/md_grey_900 + #303030 + @color/md_grey_800 - @color/colorAccent - #55333333 + @color/md_blue_400_50 - @color/md_grey_50 - @color/md_light_dividers - @color/md_grey_300 - #AAE9E9E9 - #99252525 - @color/colorPrimarySuperDark + + #DE000000 + #8A000000 + #61000000 + #1F000000 - @color/md_blue_400 + #FFFFFFFF + #B3FFFFFF + #80FFFFFF + #1FFFFFFF - - - - @color/md_red_500 - @color/md_red_50 - @color/md_red_100 - @color/md_red_200 - @color/md_red_300 - @color/md_red_400 - @color/md_red_600 - @color/md_red_700 - @color/md_red_800 - @color/md_red_900 - @color/md_red_A100 - @color/md_red_A200 - @color/md_red_A400 - @color/md_red_A700 - - #ffebee - #ffcdd2 - #ef9a9a - #e57373 - #ef5350 - #f44336 - #e53935 - #d32f2f - #c62828 - #b71c1c - #ff8a80 - #ff5252 - #ff1744 - #d50000 - #40f44336 - #80f44336 - #c0f44336 - - - @color/md_pink_500 - @color/md_pink_50 - @color/md_pink_100 - @color/md_pink_200 - @color/md_pink_300 - @color/md_pink_400 - @color/md_pink_600 - @color/md_pink_700 - @color/md_pink_800 - @color/md_pink_900 - @color/md_pink_A100 - @color/md_pink_A200 - @color/md_pink_A400 - @color/md_pink_A700 - - #fce4ec - #f8bbd0 - #f48fb1 - #f06292 - #ec407a - #e91e63 - #d81b60 - #c2185b - #ad1457 - #880e4f - #ff80ab - #ff4081 - #f50057 - #c51162 - #40e91e63 - #80e91e63 - #c0e91e63 - - - @color/md_purple_500 - @color/md_purple_50 - @color/md_purple_100 - @color/md_purple_200 - @color/md_purple_300 - @color/md_purple_400 - @color/md_purple_600 - @color/md_purple_700 - @color/md_purple_800 - @color/md_purple_900 - @color/md_purple_A100 - @color/md_purple_A200 - @color/md_purple_A400 - @color/md_purple_A700 - - #f3e5f5 - #e1bee7 - #ce93d8 - #ba68c8 - #ab47bc - #9c27b0 - #8e24aa - #7b1fa2 - #6a1b9a - #4a148c - #ea80fc - #e040fb - #d500f9 - #aa00ff - #409c27b0 - #809c27b0 - #c09c27b0 - - - @color/md_deep_purple_500 - @color/md_deep_purple_50 - @color/md_deep_purple_100 - @color/md_deep_purple_200 - @color/md_deep_purple_300 - @color/md_deep_purple_400 - @color/md_deep_purple_600 - @color/md_deep_purple_700 - @color/md_deep_purple_800 - @color/md_deep_purple_900 - @color/md_deep_purple_A100 - @color/md_deep_purple_A200 - @color/md_deep_purple_A400 - @color/md_deep_purple_A700 - - #ede7f6 - #d1c4e9 - #b39ddb - #9575cd - #7e57c2 - #673ab7 - #5e35b1 - #512da8 - #4527a0 - #311b92 - #b388ff - #7c4dff - #651fff - #6200ea - #40673ab7 - #80673ab7 - #c0673ab7 - - - @color/md_indigo_500 - @color/md_indigo_50 - @color/md_indigo_100 - @color/md_indigo_200 - @color/md_indigo_300 - @color/md_indigo_400 - @color/md_indigo_600 - @color/md_indigo_700 - @color/md_indigo_800 - @color/md_indigo_900 - @color/md_indigo_A100 - @color/md_indigo_A200 - @color/md_indigo_A400 - @color/md_indigo_A700 - - #e8eaf6 - #c5cae9 - #9fa8da - #7986cb - #5c6bc0 - #3f51b5 - #3949ab - #303f9f - #283593 - #1a237e - #8c9eff - #536dfe - #3d5afe - #304ffe - #403f51b5 - #803f51b5 - #c03f51b5 - - - @color/md_blue_500 - @color/md_blue_50 - @color/md_blue_100 - @color/md_blue_200 - @color/md_blue_300 - @color/md_blue_400 - @color/md_blue_600 - @color/md_blue_700 - @color/md_blue_800 - @color/md_blue_900 - @color/md_blue_A100 - @color/md_blue_A200 - @color/md_blue_A400 - @color/md_blue_A700 - - #e3f2fd - #bbdefb - #90caf9 - #64b5f6 - #42a5f5 - #2196f3 - #1e88e5 - #1976d2 - #1565c0 - #0d47a1 - #82b1ff - #448aff - #2979ff - #2962ff - #402196f3 - #802196f3 - #c02196f3 - - - @color/md_light_blue_500 - @color/md_light_blue_50 - @color/md_light_blue_100 - @color/md_light_blue_200 - @color/md_light_blue_300 - @color/md_light_blue_400 - @color/md_light_blue_600 - @color/md_light_blue_700 - @color/md_light_blue_800 - @color/md_light_blue_900 - @color/md_light_blue_A100 - @color/md_light_blue_A200 - @color/md_light_blue_A400 - @color/md_light_blue_A700 - - #e1f5fe - #b3e5fc - #81d4fa - #4fc3f7 - #29b6f6 - #03a9f4 - #039be5 - #0288d1 - #0277bd - #01579b - #80d8ff - #40c4ff - #00b0ff - #0091ea - #4003a9f4 - #8003a9f4 - #c003a9f4 - - - @color/md_cyan_500 - @color/md_cyan_50 - @color/md_cyan_100 - @color/md_cyan_200 - @color/md_cyan_300 - @color/md_cyan_400 - @color/md_cyan_600 - @color/md_cyan_700 - @color/md_cyan_800 - @color/md_cyan_900 - @color/md_cyan_A100 - @color/md_cyan_A200 - @color/md_cyan_A400 - @color/md_cyan_A700 - - #e0f7fa - #b2ebf2 - #80deea - #4dd0e1 - #26c6da - #00bcd4 - #00acc1 - #0097a7 - #00838f - #006064 - #84ffff - #18ffff - #00e5ff - #00b8d4 - #4000bcd4 - #8000bcd4 - #c000bcd4 - - - @color/md_teal_500 - @color/md_teal_50 - @color/md_teal_100 - @color/md_teal_200 - @color/md_teal_300 - @color/md_teal_400 - @color/md_teal_600 - @color/md_teal_700 - @color/md_teal_800 - @color/md_teal_900 - @color/md_teal_A100 - @color/md_teal_A200 - @color/md_teal_A400 - @color/md_teal_A700 - - #e0f2f1 - #b2dfdb - #80cbc4 - #4db6ac - #26a69a - #009688 - #00897b - #00796b - #00695c - #004d40 - #a7ffeb - #64ffda - #1de9b6 - #00bfa5 - #40009688 - #80009688 - #c8009688 - - - @color/md_green_500 - @color/md_green_50 - @color/md_green_100 - @color/md_green_200 - @color/md_green_300 - @color/md_green_400 - @color/md_green_600 - @color/md_green_700 - @color/md_green_800 - @color/md_green_900 - @color/md_green_A100 - @color/md_green_A200 - @color/md_green_A400 - @color/md_green_A700 - - #e8f5e9 - #c8e6c9 - #a5d6a7 - #81c784 - #66bb6a - #4caf50 - #43a047 - #388e3c - #2e7d32 - #1b5e20 - #b9f6ca - #69f0ae - #00e676 - #00c853 - #404caf50 - #804caf50 - #c04caf50 - - - @color/md_light_green_500 - @color/md_light_green_50 - @color/md_light_green_100 - @color/md_light_green_200 - @color/md_light_green_300 - @color/md_light_green_400 - @color/md_light_green_600 - @color/md_light_green_700 - @color/md_light_green_800 - @color/md_light_green_900 - @color/md_light_green_A100 - @color/md_light_green_A200 - @color/md_light_green_A400 - @color/md_light_green_A700 - - #f1f8e9 - #dcedc8 - #c5e1a5 - #aed581 - #9ccc65 - #8bc34a - #7cb342 - #689f38 - #558b2f - #33691e - #ccff90 - #b2ff59 - #76ff03 - #64dd17 - #408bc34a - #808bc34a - #c88bc34a - - - @color/md_lime_500 - @color/md_lime_50 - @color/md_lime_100 - @color/md_lime_200 - @color/md_lime_300 - @color/md_lime_400 - @color/md_lime_600 - @color/md_lime_700 - @color/md_lime_800 - @color/md_lime_900 - @color/md_lime_A100 - @color/md_lime_A200 - @color/md_lime_A400 - @color/md_lime_A700 - - #f9fbe7 - #f0f4c3 - #e6ee9c - #dce775 - #d4e157 - #cddc39 - #c0ca33 - #afb42b - #9e9d24 - #827717 - #f4ff81 - #eeff41 - #c6ff00 - #aeea00 - #40cddc39 - #80cddc39 - #c0cddc39 - - - @color/md_yellow_500 - @color/md_yellow_50 - @color/md_yellow_100 - @color/md_yellow_200 - @color/md_yellow_300 - @color/md_yellow_400 - @color/md_yellow_600 - @color/md_yellow_700 - @color/md_yellow_800 - @color/md_yellow_900 - @color/md_yellow_A100 - @color/md_yellow_A200 - @color/md_yellow_A400 - @color/md_yellow_A700 - - #fffde7 - #fff9c4 - #fff59d - #fff176 - #ffee58 - #ffeb3b - #fdd835 - #fbc02d - #f9a825 - #f57f17 - #ffff8d - #ffff00 - #ffea00 - #ffd600 - #40ffeb3b - #80ffeb3b - #c0ffeb3b - - - @color/md_amber_500 - @color/md_amber_50 - @color/md_amber_100 - @color/md_amber_200 - @color/md_amber_300 - @color/md_amber_400 - @color/md_amber_600 - @color/md_amber_700 - @color/md_amber_800 - @color/md_amber_900 - @color/md_amber_A100 - @color/md_amber_A200 - @color/md_amber_A400 - @color/md_amber_A700 - - #fff8e1 - #ffecb3 - #ffe082 - #ffd54f - #ffca28 - #ffc107 - #ffb300 - #ffa000 - #ff8f00 - #ff6f00 - #ffe57f - #ffd740 - #ffc400 - #ffab00 - #40ffc107 - #80ffc107 - #c0ffc107 - - - @color/md_orange_500 - @color/md_orange_50 - @color/md_orange_100 - @color/md_orange_200 - @color/md_orange_300 - @color/md_orange_400 - @color/md_orange_600 - @color/md_orange_700 - @color/md_orange_800 - @color/md_orange_900 - @color/md_orange_A100 - @color/md_orange_A200 - @color/md_orange_A400 - @color/md_orange_A700 - - #fff3e0 - #ffe0b2 - #ffcc80 - #ffb74d - #ffa726 - #ff9800 - #fb8c00 - #f57c00 - #ef6c00 - #e65100 - #ffd180 - #ffab40 - #ff9100 - #ff6d00 - #40ff9800 - #80ff9800 - #c0ff9800 - - - @color/md_deep_orange_500 - @color/md_deep_orange_50 - @color/md_deep_orange_100 - @color/md_deep_orange_200 - @color/md_deep_orange_300 - @color/md_deep_orange_400 - @color/md_deep_orange_600 - @color/md_deep_orange_700 - @color/md_deep_orange_800 - @color/md_deep_orange_900 - @color/md_deep_orange_A100 - @color/md_deep_orange_A200 - @color/md_deep_orange_A400 - @color/md_deep_orange_A700 - - #fbe9e7 - #ffccbc - #ffab91 - #ff8a65 - #ff7043 - #ff5722 - #f4511e - #e64a19 - #d84315 - #bf360c - #ff9e80 - #ff6e40 - #ff3d00 - #dd2c00 - #40ff5722 - #80ff5722 - #c0ff5722 - - - @color/md_brown_500 - @color/md_brown_50 - @color/md_brown_100 - @color/md_brown_200 - @color/md_brown_300 - @color/md_brown_400 - @color/md_brown_600 - @color/md_brown_700 - @color/md_brown_800 - @color/md_brown_900 - - #efebe9 - #d7ccc8 - #bcaaa4 - #a1887f - #8d6e63 - #795548 - #6d4c41 - #5d4037 - #4e342e - #3e2723 - #40795548 - #80795548 - #c0795548 - - - @color/md_grey_500 - @color/md_grey_50 - @color/md_grey_100 - @color/md_grey_200 - @color/md_grey_300 - @color/md_grey_400 - @color/md_grey_600 - @color/md_grey_700 - @color/md_grey_800 - @color/md_grey_900 - @color/md_black_1000 - @color/md_white_1000 - - #fafafa - #f5f5f5 - #eeeeee - #e0e0e0 - #bdbdbd - #9e9e9e - #757575 - #616161 + + #000000 + + #FAFAFA + #F5F5F5 + #E0E0E0 #424242 #212121 - #409e9e9e - #809e9e9e - #c09e9e9e - #ffffff - #1affffff - #22ffffff - #33ffffff - #40ffffff - #80ffffff - #99ffffff - #c0ffffff - #1a000000 - #26000000 - #33000000 - #40000000 - #80000000 - #c0000000 - #000000 - - - @color/md_blue_grey_500 - @color/md_blue_grey_50 - @color/md_blue_grey_100 - @color/md_blue_grey_200 - @color/md_blue_grey_300 - @color/md_blue_grey_400 - @color/md_blue_grey_600 - @color/md_blue_grey_700 - @color/md_blue_grey_800 - @color/md_blue_grey_900 - - #eceff1 - #cfd8dc - #b0bec5 - #90a4ae - #78909c - #607d8b - #546e7a - #455a64 - #37474f - #263238 - #40607d8b - #80607d8b - #c0607d8b - - - @color/md_text_white - @color/md_text_white_87 - @color/md_secondary_text_icons_white - @color/md_disabled_hint_text_white - @color/md_divider_white - - #ffffffff - #dfffffff - #b3ffffff - #4dffffff - #1fffffff - - @color/md_text - @color/md_secondary_text_icons - @color/md_disabled_hint_text - @color/md_divider - - #df000000 - #8a000000 - #4c000000 - #1f000000 - - - @color/md_falcon_400 - @color/md_falcon_500 - @color/md_falcon_700 - - #ff38628b - #384e77 - #2b3e5f - #598bae - #40384e77 - #80384e77 - #c0384e77 - #40598bae + + #42A5F5 + #8042A5F5 + #6142A5F5 + + #FFCDD2 #4000 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index e36f06a21b..9ef33863d4 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,16 +1,16 @@ - 0dp - 0dp - 0dp - 0dp - 0dp - 0dp - 0dp + 16dp + 16dp + 16dp + 16dp + 16dp + 16dp + 16dp 56dp - 0dp - 0dp + 24dp + 20dp 24sp 22sp diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index c6174e94d5..a5d8cebae3 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -15,6 +15,7 @@ pref_update_only_non_completed_key pref_auto_update_manga_sync_key pref_ask_update_manga_sync_key + pref_theme_key pref_default_viewer_key pref_image_scale_type_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 90b6bfe5d2..c1e208a323 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -86,6 +86,8 @@ Every 2 days Sync chapters after reading Confirm before updating + Application Theme + Hide status bar @@ -96,8 +98,8 @@ Seamless chapter transitions Keep screen on Background color - White - Black + Main Theme + Dark Theme Default viewer Default Left to right diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index daed9974ce..f7fd97a560 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,61 +1,69 @@ + - - - - - - - - + + + + + + + + + + + + + + + - + + + + - + + + - - - - + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000000..dcba3b5f0f --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index a05f27340d..7450c8bf2f 100644 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -16,6 +16,14 @@ android:defaultValue="0" android:summary="%s"/> + +