Rewrote Theme

This commit is contained in:
NoodleMage 2016-03-12 14:22:40 +01:00 committed by NoodleMage
parent 96c63d1fb8
commit ac50130308
105 changed files with 2449 additions and 3107 deletions

View File

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

View File

@ -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" >
<activity
android:name=".ui.main.MainActivity"
android:theme="@style/AppTheme.BrandedLaunch">
android:theme="@style/Theme.BrandedLaunch">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -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<Int> {
return rxPrefs.getInteger(getKey(R.string.pref_theme_key), R.style.Theme_Tachiyomi)
}
fun filterDownloaded(): Preference<Boolean> {
return rxPrefs.getBoolean(getKey(R.string.pref_filter_downloaded), false)
}

View File

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

View File

@ -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<CataloguePresenter>(), FlexibleViewHold
// Set previous selection if it's not a valid source and notify the user
if (!presenter.isValidSource(source)) {
spinner.setSelection(presenter.findFirstValidSource())
ToastUtil.showShort(activity, R.string.source_requires_login)
context.toast(R.string.source_requires_login)
} else {
selectedIndex = position
presenter.setEnabledSource(selectedIndex)
@ -431,7 +431,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
val selectedManga = adapter.getItem(position)
val intent = MangaActivity.newIntent(activity, selectedManga)
intent.putExtra(MangaActivity.MANGA_ONLINE, true)
intent.putExtra(MangaActivity.FROM_CATALOGUE, true)
startActivity(intent)
return false
}

View File

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

View File

@ -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<CategoryPresenter>(), ActionMode.Callbac
}
override fun onCreate(savedInstanceState: Bundle?) {
this.setTheme()
super.onCreate(savedInstanceState)
// Inflate activity_edit_categories.xml.

View File

@ -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<DownloadPresenter>() {
*/
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)
}
}

View File

@ -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<LibraryPresenter>(), ActionMode.Callback
override fun onViewCreated(view: View, savedState: Bundle?) {
setToolbarTitle(getString(R.string.label_library))
ButterKnife.bind(this, view)
appBar = (activity as MainActivity).appbar
tabs = appBar.inflate(R.layout.library_tab_layout) as TabLayout
@ -264,10 +261,10 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
fun onNextLibraryUpdate(categories: List<Category>, mangaMap: Map<Int, List<Manga>>) {
// 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<LibraryPresenter>(), ActionMode.Callback
startActivityForResult(Intent.createChooser(intent,
getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN)
} else {
ToastUtil.showShort(context, R.string.notification_first_add_to_library)
context.toast(R.string.notification_first_add_to_library)
}
}
@ -430,8 +427,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
destroyActionModeIfNeeded()
true
}
.positiveText(R.string.button_ok)
.negativeText(R.string.button_cancel)
.positiveText(android.R.string.ok)
.negativeText(android.R.string.cancel)
.show()
}

View File

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

View File

@ -1,164 +0,0 @@
package eu.kanade.tachiyomi.ui.manga;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.Toolbar;
import org.greenrobot.eventbus.EventBus;
import javax.inject.Inject;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.App;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity;
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersFragment;
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoFragment;
import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListFragment;
import nucleus.factory.RequiresPresenter;
@RequiresPresenter(MangaPresenter.class)
public class MangaActivity extends BaseRxActivity<MangaPresenter> {
@Bind(R.id.toolbar) Toolbar toolbar;
@Bind(R.id.tabs) TabLayout tabs;
@Bind(R.id.view_pager) ViewPager viewPager;
@Inject PreferencesHelper preferences;
@Inject MangaSyncManager mangaSyncManager;
private MangaDetailAdapter adapter;
private boolean isOnline;
public final static String MANGA_ONLINE = "manga_online";
public static Intent newIntent(Context context, Manga manga) {
Intent intent = new Intent(context, MangaActivity.class);
if (manga != null) {
EventBus.getDefault().postSticky(manga);
}
return intent;
}
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
App.get(this).getComponent().inject(this);
setContentView(R.layout.activity_manga);
ButterKnife.bind(this);
setupToolbar(toolbar);
Intent intent = getIntent();
isOnline = intent.getBooleanExtra(MANGA_ONLINE, false);
setupViewPager();
requestPermissionsOnMarshmallow();
}
private void setupViewPager() {
adapter = new MangaDetailAdapter(getSupportFragmentManager(), this);
viewPager.setAdapter(adapter);
// Workaround to prevent: Tab belongs to a different TabLayout.
// Internal bug in Support library v23.2.0.
// See https://code.google.com/p/android/issues/detail?id=201827
for (int j = 0; j < 17; j++)
tabs.newTab();
tabs.setupWithViewPager(viewPager);
if (!isOnline)
viewPager.setCurrentItem(MangaDetailAdapter.CHAPTERS_FRAGMENT);
}
public void setManga(Manga manga) {
setToolbarTitle(manga.title);
}
public boolean isCatalogueManga() {
return isOnline;
}
private void requestPermissionsOnMarshmallow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE},
1);
}
}
}
class MangaDetailAdapter extends FragmentPagerAdapter {
private int pageCount;
private String tabTitles[];
final static int INFO_FRAGMENT = 0;
final static int CHAPTERS_FRAGMENT = 1;
final static int MYANIMELIST_FRAGMENT = 2;
public MangaDetailAdapter(FragmentManager fm, Context context) {
super(fm);
tabTitles = new String[]{
context.getString(R.string.manga_detail_tab),
context.getString(R.string.manga_chapters_tab),
"MAL"
};
pageCount = 2;
if (!isOnline && mangaSyncManager.getMyAnimeList().isLogged())
pageCount++;
}
@Override
public int getCount() {
return pageCount;
}
@Override
public Fragment getItem(int position) {
switch (position) {
case INFO_FRAGMENT:
return MangaInfoFragment.newInstance();
case CHAPTERS_FRAGMENT:
return ChaptersFragment.newInstance();
case MYANIMELIST_FRAGMENT:
return MyAnimeListFragment.newInstance();
default:
return null;
}
}
@Override
public CharSequence getPageTitle(int position) {
// Generate title based on item position
return tabTitles[position];
}
}
}

View File

@ -0,0 +1,118 @@
package eu.kanade.tachiyomi.ui.manga
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentPagerAdapter
import android.support.v4.content.ContextCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersFragment
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoFragment
import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListFragment
import kotlinx.android.synthetic.main.activity_manga.*
import kotlinx.android.synthetic.main.tab_layout.*
import kotlinx.android.synthetic.main.toolbar.*
import nucleus.factory.RequiresPresenter
import org.greenrobot.eventbus.EventBus
@RequiresPresenter(MangaPresenter::class)
class MangaActivity : BaseRxActivity<MangaPresenter>() {
companion object {
val FROM_CATALOGUE = "from_catalogue"
val INFO_FRAGMENT = 0
val CHAPTERS_FRAGMENT = 1
val MYANIMELIST_FRAGMENT = 2
fun newIntent(context: Context, manga: Manga?): Intent {
val intent = Intent(context, MangaActivity::class.java)
if (manga != null) {
EventBus.getDefault().postSticky(manga)
}
return intent
}
}
private lateinit var adapter: MangaDetailAdapter
var isCatalogueManga: Boolean = false
private set
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
setContentView(R.layout.activity_manga)
setupToolbar(toolbar)
isCatalogueManga = intent.getBooleanExtra(FROM_CATALOGUE, false)
adapter = MangaDetailAdapter(supportFragmentManager, this)
view_pager.adapter = adapter
tabs.setupWithViewPager(view_pager)
if (!isCatalogueManga)
view_pager.currentItem = CHAPTERS_FRAGMENT
requestPermissionsOnMarshmallow()
}
fun onSetManga(manga: Manga) {
setToolbarTitle(manga.title)
}
private fun requestPermissionsOnMarshmallow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE),
1)
}
}
}
internal class MangaDetailAdapter(fm: FragmentManager, activity: MangaActivity) : FragmentPagerAdapter(fm) {
private var pageCount: Int = 0
private val tabTitles = arrayOf(activity.getString(R.string.manga_detail_tab),
activity.getString(R.string.manga_chapters_tab), "MAL")
init {
pageCount = 2
if (!activity.isCatalogueManga && activity.presenter.syncManager.myAnimeList.isLogged)
pageCount++
}
override fun getCount(): Int {
return pageCount
}
override fun getItem(position: Int): Fragment? {
when (position) {
INFO_FRAGMENT -> return MangaInfoFragment.newInstance()
CHAPTERS_FRAGMENT -> return ChaptersFragment.newInstance()
MYANIMELIST_FRAGMENT -> return MyAnimeListFragment.newInstance()
else -> return null
}
}
override fun getPageTitle(position: Int): CharSequence {
// Generate title based on item position
return tabTitles[position]
}
}
}

View File

@ -1,56 +0,0 @@
package eu.kanade.tachiyomi.ui.manga;
import android.os.Bundle;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import javax.inject.Inject;
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.event.MangaEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import icepick.State;
import rx.Observable;
public class MangaPresenter extends BasePresenter<MangaActivity> {
@Inject DatabaseHelper db;
@State Manga manga;
private static final int GET_MANGA = 1;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
restartableLatestCache(GET_MANGA, this::getMangaObservable, MangaActivity::setManga);
if (savedState == null)
registerForEvents();
}
@Override
protected void onDestroy() {
super.onDestroy();
// Avoid new instances receiving wrong manga
EventBus.getDefault().removeStickyEvent(MangaEvent.class);
}
private Observable<Manga> getMangaObservable() {
return Observable.just(manga)
.doOnNext(manga -> EventBus.getDefault().postSticky(new MangaEvent(manga)));
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(Manga manga) {
EventBus.getDefault().removeStickyEvent(manga);
unregisterForEvents();
this.manga = manga;
start(GET_MANGA);
}
}

View File

@ -0,0 +1,82 @@
package eu.kanade.tachiyomi.ui.manga
import android.os.Bundle
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
import eu.kanade.tachiyomi.event.MangaEvent
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import rx.Observable
import javax.inject.Inject
/**
* Presenter of [MangaActivity].
*/
class MangaPresenter : BasePresenter<MangaActivity>() {
/**
* Database helper.
*/
@Inject lateinit var db: DatabaseHelper
/**
* Manga sync manager.
*/
@Inject lateinit var syncManager: MangaSyncManager
/**
* Manga associated with this instance.
*/
lateinit var manga: Manga
/**
* Key to save and restore [manga] from a bundle.
*/
private val MANGA_KEY = "manga_key"
/**
* Id of the restartable that notifies the view of a manga.
*/
private val GET_MANGA = 1
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
if (savedState != null) {
manga = savedState.getSerializable(MANGA_KEY) as Manga
}
restartableLatestCache(GET_MANGA,
{ Observable.just(manga)
.doOnNext { EventBus.getDefault().postSticky(MangaEvent(it)) } },
{ view, manga -> view.onSetManga(manga) })
if (savedState == null) {
registerForEvents()
}
}
override fun onDestroy() {
// Avoid new instances receiving wrong manga
EventBus.getDefault().removeStickyEvent(MangaEvent::class.java)
super.onDestroy()
}
override fun onSave(state: Bundle) {
state.putSerializable(MANGA_KEY, manga)
super.onSave(state)
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEvent(manga: Manga) {
EventBus.getDefault().removeStickyEvent(manga)
unregisterForEvents()
this.manga = manga
start(GET_MANGA)
}
}

View File

@ -1,57 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.chapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.Chapter;
import eu.kanade.tachiyomi.data.database.models.Manga;
public class ChaptersAdapter extends FlexibleAdapter<ChaptersHolder, Chapter> {
private ChaptersFragment fragment;
public ChaptersAdapter(ChaptersFragment fragment) {
this.fragment = fragment;
mItems = new ArrayList<>();
setHasStableIds(true);
}
@Override
public void updateDataSet(String param) {}
@Override
public ChaptersHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(fragment.getActivity()).inflate(R.layout.item_chapter, parent, false);
return new ChaptersHolder(v, this, fragment);
}
@Override
public void onBindViewHolder(ChaptersHolder holder, int position) {
final Chapter chapter = getItem(position);
final Manga manga = fragment.getPresenter().getManga();
holder.onSetValues(chapter, manga);
//When user scrolls this bind the correct selection status
holder.itemView.setActivated(isSelected(position));
}
@Override
public long getItemId(int position) {
return mItems.get(position).id;
}
public void setItems(List<Chapter> chapters) {
mItems = chapters;
notifyDataSetChanged();
}
public ChaptersFragment getFragment() {
return fragment;
}
}

View File

@ -0,0 +1,40 @@
package eu.kanade.tachiyomi.ui.manga.chapter
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.util.inflate
class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<ChaptersHolder, Chapter>() {
init {
setHasStableIds(true)
}
override fun updateDataSet(param: String) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChaptersHolder {
val v = parent.inflate(R.layout.item_chapter)
return ChaptersHolder(v, this, fragment)
}
override fun onBindViewHolder(holder: ChaptersHolder, position: Int) {
val chapter = getItem(position)
val manga = fragment.presenter.manga
holder.onSetValues(chapter, manga)
//When user scrolls this bind the correct selection status
holder.itemView.isActivated = isSelected(position)
}
override fun getItemId(position: Int): Long {
return mItems[position].id
}
fun setItems(chapters: List<Chapter>) {
mItems = chapters
notifyDataSetChanged()
}
}

View File

@ -1,439 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.chapter;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import com.afollestad.materialdialogs.MaterialDialog;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.Chapter;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.download.DownloadService;
import eu.kanade.tachiyomi.data.download.model.Download;
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration;
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
import eu.kanade.tachiyomi.ui.manga.MangaActivity;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
import eu.kanade.tachiyomi.util.ToastUtil;
import nucleus.factory.RequiresPresenter;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
@RequiresPresenter(ChaptersPresenter.class)
public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implements
ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
@Bind(R.id.chapter_list) RecyclerView recyclerView;
@Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefresh;
@Bind(R.id.toolbar_bottom) ViewGroup toolbarBottom;
@Bind(R.id.action_sort) ImageView sortBtn;
@Bind(R.id.action_next_unread) ImageView nextUnreadBtn;
@Bind(R.id.action_show_unread) CheckBox readCb;
@Bind(R.id.action_show_downloaded) CheckBox downloadedCb;
private ChaptersAdapter adapter;
private LinearLayoutManager linearLayout;
private ActionMode actionMode;
private Subscription downloadProgressSubscription;
public static ChaptersFragment newInstance() {
return new ChaptersFragment();
}
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_manga_chapters, container, false);
ButterKnife.bind(this, view);
// Init RecyclerView and adapter
linearLayout = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayout);
recyclerView.addItemDecoration(new DividerItemDecoration(
ContextCompat.getDrawable(getContext(), R.drawable.line_divider)));
recyclerView.setHasFixedSize(true);
adapter = new ChaptersAdapter(this);
recyclerView.setAdapter(adapter);
swipeRefresh.setOnRefreshListener(this::fetchChapters);
nextUnreadBtn.setOnClickListener(v -> {
Chapter chapter = getPresenter().getNextUnreadChapter();
if (chapter != null) {
openChapter(chapter);
} else {
ToastUtil.showShort(getContext(), R.string.no_next_chapter);
}
});
return view;
}
@Override
public void onPause() {
// Stop recycler's scrolling when onPause is called. If the activity is finishing
// the presenter will be destroyed, and it could cause NPE
// https://github.com/inorichi/tachiyomi/issues/159
recyclerView.stopScroll();
super.onPause();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.chapters, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_display_mode:
showDisplayModeDialog();
return true;
case R.id.manga_download:
showDownloadDialog();
return true;
}
return false;
}
public void onNextManga(Manga manga) {
// Remove listeners before setting the values
readCb.setOnCheckedChangeListener(null);
downloadedCb.setOnCheckedChangeListener(null);
sortBtn.setOnClickListener(null);
// Set initial values
setReadFilter();
setDownloadedFilter();
setSortIcon();
// Init listeners
readCb.setOnCheckedChangeListener((arg, isChecked) ->
getPresenter().setReadFilter(isChecked));
downloadedCb.setOnCheckedChangeListener((v, isChecked) ->
getPresenter().setDownloadedFilter(isChecked));
sortBtn.setOnClickListener(v -> {
getPresenter().revertSortOrder();
setSortIcon();
});
}
public void onNextChapters(List<Chapter> chapters) {
// If the list is empty, fetch chapters from source if the conditions are met
// We use presenter chapters instead because they are always unfiltered
if (getPresenter().getChapters().isEmpty())
initialFetchChapters();
destroyActionModeIfNeeded();
adapter.setItems(chapters);
}
private void initialFetchChapters() {
// Only fetch if this view is from the catalog and it hasn't requested previously
if (isCatalogueManga() && !getPresenter().hasRequested()) {
fetchChapters();
}
}
public void fetchChapters() {
if (getPresenter().getManga() != null) {
swipeRefresh.setRefreshing(true);
getPresenter().fetchChaptersFromSource();
}
}
public void onFetchChaptersDone() {
swipeRefresh.setRefreshing(false);
}
public void onFetchChaptersError(Throwable error) {
swipeRefresh.setRefreshing(false);
ToastUtil.showShort(getContext(), error.getMessage());
}
public boolean isCatalogueManga() {
return ((MangaActivity) getActivity()).isCatalogueManga();
}
protected void openChapter(Chapter chapter) {
getPresenter().onOpenChapter(chapter);
Intent intent = ReaderActivity.newIntent(getActivity());
startActivity(intent);
}
private void showDisplayModeDialog() {
final Manga manga = getPresenter().getManga();
if (manga == null)
return;
// Get available modes, ids and the selected mode
String[] modes = {getString(R.string.show_title), getString(R.string.show_chapter_number)};
int[] ids = {Manga.DISPLAY_NAME, Manga.DISPLAY_NUMBER};
int selectedIndex = manga.getDisplayMode() == Manga.DISPLAY_NAME ? 0 : 1;
new MaterialDialog.Builder(getActivity())
.title(R.string.action_display_mode)
.items(modes)
.itemsIds(ids)
.itemsCallbackSingleChoice(selectedIndex, (dialog, itemView, which, text) -> {
// Save the new display mode
getPresenter().setDisplayMode(itemView.getId());
// Refresh ui
adapter.notifyDataSetChanged();
return true;
})
.show();
}
private void showDownloadDialog() {
// Get available modes
String[] modes = {getString(R.string.download_all), getString(R.string.download_unread)};
new MaterialDialog.Builder(getActivity())
.title(R.string.manga_download)
.items(modes)
.itemsCallback((dialog, view, i, charSequence) -> {
List<Chapter> chapters = new ArrayList<>();
for(Chapter chapter : getPresenter().getChapters()) {
if(!chapter.isDownloaded()) {
if(i == 0 || (i == 1 && !chapter.read)) {
chapters.add(chapter);
}
}
}
if(chapters.size() > 0) {
onDownload(Observable.from(chapters));
}
})
.negativeText(R.string.button_cancel)
.show();
}
private void observeChapterDownloadProgress() {
downloadProgressSubscription = getPresenter().getDownloadProgressObs()
.subscribe(this::onDownloadProgressChange,
error -> { /* TODO getting a NPE sometimes on 'manga' from presenter */ });
}
private void unsubscribeChapterDownloadProgress() {
if (downloadProgressSubscription != null)
downloadProgressSubscription.unsubscribe();
}
private void onDownloadProgressChange(Download download) {
ChaptersHolder holder = getHolder(download.chapter);
if (holder != null)
holder.onProgressChange(getContext(), download.downloadedImages, download.pages.size());
}
public void onChapterStatusChange(Download download) {
ChaptersHolder holder = getHolder(download.chapter);
if (holder != null)
holder.onStatusChange(download.getStatus());
}
@Nullable
private ChaptersHolder getHolder(Chapter chapter) {
return (ChaptersHolder) recyclerView.findViewHolderForItemId(chapter.id);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.chapter_selection, menu);
adapter.setMode(ChaptersAdapter.MODE_MULTI);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.action_select_all:
return onSelectAll();
case R.id.action_mark_as_read:
return onMarkAsRead(getSelectedChapters());
case R.id.action_mark_as_unread:
return onMarkAsUnread(getSelectedChapters());
case R.id.action_download:
return onDownload(getSelectedChapters());
case R.id.action_delete:
return onDelete(getSelectedChapters());
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
adapter.setMode(ChaptersAdapter.MODE_SINGLE);
adapter.clearSelection();
actionMode = null;
}
private Observable<Chapter> getSelectedChapters() {
// Create a blocking copy of the selected chapters.
// When the action mode is closed the list is cleared. If we use background
// threads with this observable, some emissions could be lost.
List<Chapter> chapters = Observable.from(adapter.getSelectedItems())
.map(adapter::getItem).toList().toBlocking().single();
return Observable.from(chapters);
}
public void destroyActionModeIfNeeded() {
if (actionMode != null) {
actionMode.finish();
}
}
protected boolean onSelectAll() {
adapter.selectAll();
setContextTitle(adapter.getSelectedItemCount());
return true;
}
protected boolean onMarkAsRead(Observable<Chapter> chapters) {
getPresenter().markChaptersRead(chapters, true);
return true;
}
protected boolean onMarkAsUnread(Observable<Chapter> chapters) {
getPresenter().markChaptersRead(chapters, false);
return true;
}
public boolean onMarkPreviousAsRead(Chapter chapter) {
getPresenter().markPreviousChaptersAsRead(chapter);
return true;
}
protected boolean onDownload(Observable<Chapter> chapters) {
DownloadService.start(getActivity());
Observable<Chapter> observable = chapters
.doOnCompleted(adapter::notifyDataSetChanged);
getPresenter().downloadChapters(observable);
destroyActionModeIfNeeded();
return true;
}
protected boolean onDelete(Observable<Chapter> chapters) {
int size = adapter.getSelectedItemCount();
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
.title(R.string.deleting)
.progress(false, size, true)
.cancelable(false)
.show();
Observable<Chapter> observable = chapters
.concatMap(chapter -> {
getPresenter().deleteChapter(chapter);
return Observable.just(chapter);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(chapter -> {
dialog.incrementProgress(1);
chapter.status = Download.NOT_DOWNLOADED;
})
.doOnCompleted(adapter::notifyDataSetChanged)
.finallyDo(dialog::dismiss);
getPresenter().deleteChapters(observable);
destroyActionModeIfNeeded();
return true;
}
@Override
public boolean onListItemClick(int position) {
if (actionMode != null && adapter.getMode() == ChaptersAdapter.MODE_MULTI) {
toggleSelection(position);
return true;
} else {
openChapter(adapter.getItem(position));
return false;
}
}
@Override
public void onListItemLongClick(int position) {
if (actionMode == null)
actionMode = getBaseActivity().startSupportActionMode(this);
toggleSelection(position);
}
private void toggleSelection(int position) {
adapter.toggleSelection(position, false);
int count = adapter.getSelectedItemCount();
if (count == 0) {
actionMode.finish();
} else {
setContextTitle(count);
actionMode.invalidate();
}
}
private void setContextTitle(int count) {
actionMode.setTitle(getString(R.string.label_selected, count));
}
public void setSortIcon() {
if (sortBtn != null) {
boolean aToZ = getPresenter().getSortOrder();
sortBtn.setImageResource(!aToZ ? R.drawable.ic_expand_less_white_36dp : R.drawable.ic_expand_more_white_36dp);
}
}
public void setReadFilter() {
if (readCb != null) {
readCb.setChecked(getPresenter().onlyUnread());
}
}
public void setDownloadedFilter() {
if (downloadedCb != null) {
downloadedCb.setChecked(getPresenter().onlyDownloaded());
}
}
}

View File

@ -0,0 +1,362 @@
package eu.kanade.tachiyomi.ui.manga.chapter
import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v7.view.ActionMode
import android.support.v7.widget.LinearLayoutManager
import android.view.*
import com.afollestad.materialdialogs.MaterialDialog
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.manga.MangaActivity
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.toast
import kotlinx.android.synthetic.main.fragment_manga_chapters.*
import nucleus.factory.RequiresPresenter
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import java.util.*
@RequiresPresenter(ChaptersPresenter::class)
class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
companion object {
/**
* Creates a new instance of this fragment.
*
* @return a new instance of [ChaptersFragment].
*/
fun newInstance(): ChaptersFragment {
return ChaptersFragment()
}
}
/**
* Adapter containing a list of chapters.
*/
private lateinit var adapter: ChaptersAdapter
/**
* Action mode for multiple selection.
*/
private var actionMode: ActionMode? = null
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_manga_chapters, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Init RecyclerView and adapter
adapter = ChaptersAdapter(this)
recycler.adapter = adapter
recycler.layoutManager = LinearLayoutManager(activity)
recycler.addItemDecoration(DividerItemDecoration(
ContextCompat.getDrawable(context, R.drawable.line_divider)))
recycler.setHasFixedSize(true)
swipe_refresh.setOnRefreshListener { fetchChapters() }
next_unread_btn.setOnClickListener { v ->
val chapter = presenter.getNextUnreadChapter()
if (chapter != null) {
openChapter(chapter)
} else {
context.toast(R.string.no_next_chapter)
}
}
}
override fun onPause() {
// Stop recycler's scrolling when onPause is called. If the activity is finishing
// the presenter will be destroyed, and it could cause NPE
// https://github.com/inorichi/tachiyomi/issues/159
recycler.stopScroll()
super.onPause()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.chapters, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_display_mode -> showDisplayModeDialog()
R.id.manga_download -> showDownloadDialog()
else -> return super.onOptionsItemSelected(item)
}
return true
}
fun onNextManga(manga: Manga) {
// Remove listeners before setting the values
show_unread.setOnCheckedChangeListener(null)
show_downloaded.setOnCheckedChangeListener(null)
sort_btn.setOnClickListener(null)
// Set initial values
setReadFilter()
setDownloadedFilter()
setSortIcon()
// Init listeners
show_unread.setOnCheckedChangeListener { arg, isChecked -> presenter.setReadFilter(isChecked) }
show_downloaded.setOnCheckedChangeListener { v, isChecked -> presenter.setDownloadedFilter(isChecked) }
sort_btn.setOnClickListener {
presenter.revertSortOrder()
setSortIcon()
}
}
fun onNextChapters(chapters: List<Chapter>) {
// If the list is empty, fetch chapters from source if the conditions are met
// We use presenter chapters instead because they are always unfiltered
if (presenter.chapters.isEmpty())
initialFetchChapters()
destroyActionModeIfNeeded()
adapter.setItems(chapters)
}
private fun initialFetchChapters() {
// Only fetch if this view is from the catalog and it hasn't requested previously
if (isCatalogueManga && !presenter.hasRequested) {
fetchChapters()
}
}
fun fetchChapters() {
swipe_refresh.isRefreshing = true
presenter.fetchChaptersFromSource()
}
fun onFetchChaptersDone() {
swipe_refresh.isRefreshing = false
}
fun onFetchChaptersError(error: Throwable) {
swipe_refresh.isRefreshing = false
context.toast(error.message)
}
val isCatalogueManga: Boolean
get() = (activity as MangaActivity).isCatalogueManga
protected fun openChapter(chapter: Chapter) {
presenter.onOpenChapter(chapter)
val intent = ReaderActivity.newIntent(activity)
startActivity(intent)
}
private fun showDisplayModeDialog() {
// Get available modes, ids and the selected mode
val modes = listOf(getString(R.string.show_title), getString(R.string.show_chapter_number))
val ids = intArrayOf(Manga.DISPLAY_NAME, Manga.DISPLAY_NUMBER)
val selectedIndex = if (presenter.manga.displayMode == Manga.DISPLAY_NAME) 0 else 1
MaterialDialog.Builder(activity)
.title(R.string.action_display_mode)
.items(modes)
.itemsIds(ids)
.itemsCallbackSingleChoice(selectedIndex) { dialog, itemView, which, text ->
// Save the new display mode
presenter.setDisplayMode(itemView.id)
// Refresh ui
adapter.notifyDataSetChanged()
true
}
.show()
}
private fun showDownloadDialog() {
// Get available modes
val modes = listOf(getString(R.string.download_all), getString(R.string.download_unread))
MaterialDialog.Builder(activity)
.title(R.string.manga_download)
.negativeText(android.R.string.cancel)
.items(modes)
.itemsCallback { dialog, view, i, charSequence ->
val chapters = ArrayList<Chapter>()
for (chapter in presenter.chapters) {
if (!chapter.isDownloaded) {
if (i == 0 || (i == 1 && !chapter.read)) {
chapters.add(chapter)
}
}
}
if (chapters.size > 0) {
onDownload(Observable.from(chapters))
}
}
.show()
}
fun onChapterStatusChange(download: Download) {
getHolder(download.chapter)?.notifyStatus(download.status)
}
private fun getHolder(chapter: Chapter): ChaptersHolder? {
return recycler.findViewHolderForItemId(chapter.id) as? ChaptersHolder
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.chapter_selection, menu)
adapter.mode = FlexibleAdapter.MODE_MULTI
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return false
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_select_all -> onSelectAll()
R.id.action_mark_as_read -> onMarkAsRead(getSelectedChapters())
R.id.action_mark_as_unread -> onMarkAsUnread(getSelectedChapters())
R.id.action_download -> onDownload(getSelectedChapters())
R.id.action_delete -> onDelete(getSelectedChapters())
else -> return false
}
return true
}
override fun onDestroyActionMode(mode: ActionMode) {
adapter.mode = FlexibleAdapter.MODE_SINGLE
adapter.clearSelection()
actionMode = null
}
fun getSelectedChapters(): Observable<Chapter> {
val chapters = adapter.selectedItems.map { adapter.getItem(it) }
return Observable.from(chapters)
}
fun destroyActionModeIfNeeded() {
actionMode?.finish()
}
protected fun onSelectAll() {
adapter.selectAll()
setContextTitle(adapter.selectedItemCount)
}
fun onMarkAsRead(chapters: Observable<Chapter>) {
presenter.markChaptersRead(chapters, true)
}
fun onMarkAsUnread(chapters: Observable<Chapter>) {
presenter.markChaptersRead(chapters, false)
}
fun onMarkPreviousAsRead(chapter: Chapter) {
presenter.markPreviousChaptersAsRead(chapter)
}
fun onDownload(chapters: Observable<Chapter>) {
DownloadService.start(activity)
val observable = chapters.doOnCompleted { adapter.notifyDataSetChanged() }
presenter.downloadChapters(observable)
destroyActionModeIfNeeded()
}
fun onDelete(chapters: Observable<Chapter>) {
val size = adapter.selectedItemCount
val dialog = MaterialDialog.Builder(activity)
.title(R.string.deleting)
.progress(false, size, true)
.cancelable(false)
.show()
val observable = chapters
.concatMap { chapter ->
presenter.deleteChapter(chapter)
Observable.just(chapter)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { chapter ->
dialog.incrementProgress(1)
chapter.status = Download.NOT_DOWNLOADED
}
.doOnCompleted { adapter.notifyDataSetChanged() }
.doAfterTerminate { dialog.dismiss() }
presenter.deleteChapters(observable)
destroyActionModeIfNeeded()
}
override fun onListItemClick(position: Int): Boolean {
if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) {
toggleSelection(position)
return true
} else {
openChapter(adapter.getItem(position))
return false
}
}
override fun onListItemLongClick(position: Int) {
if (actionMode == null)
actionMode = baseActivity.startSupportActionMode(this)
toggleSelection(position)
}
private fun toggleSelection(position: Int) {
adapter.toggleSelection(position, false)
val count = adapter.selectedItemCount
if (count == 0) {
actionMode?.finish()
} else {
setContextTitle(count)
actionMode?.invalidate()
}
}
private fun setContextTitle(count: Int) {
actionMode?.title = getString(R.string.label_selected, count)
}
fun setSortIcon() {
sort_btn?.let {
val aToZ = presenter.sortOrder()
it.setImageResource(if (!aToZ) R.drawable.ic_expand_less_white_36dp else R.drawable.ic_expand_more_white_36dp)
}
}
fun setReadFilter() {
show_unread?.let {
it.isChecked = presenter.onlyUnread()
}
}
fun setDownloadedFilter() {
show_downloaded?.let {
it.isChecked = presenter.onlyDownloaded()
}
}
}

View File

@ -1,150 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.chapter;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.view.Menu;
import android.view.View;
import android.widget.PopupMenu;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Date;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.Chapter;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.download.model.Download;
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
import rx.Observable;
public class ChaptersHolder extends FlexibleViewHolder {
private final ChaptersAdapter adapter;
private final int readColor;
private final int unreadColor;
private final DecimalFormat decimalFormat;
private final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
@Bind(R.id.chapter_title) TextView title;
@Bind(R.id.download_text) TextView downloadText;
@Bind(R.id.chapter_menu) RelativeLayout chapterMenu;
@Bind(R.id.chapter_pages) TextView pages;
@Bind(R.id.chapter_date) TextView date;
private Context context;
private Chapter item;
public ChaptersHolder(View view, ChaptersAdapter adapter, OnListItemClickListener listener) {
super(view, adapter, listener);
this.adapter = adapter;
context = view.getContext();
ButterKnife.bind(this, view);
readColor = ContextCompat.getColor(view.getContext(), R.color.hint_text);
unreadColor = ContextCompat.getColor(view.getContext(), R.color.primary_text);
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
decimalFormat = new DecimalFormat("#.###", symbols);
chapterMenu.setOnClickListener(v -> v.post(() -> showPopupMenu(v)));
}
public void onSetValues(Chapter chapter, Manga manga) {
this.item = chapter;
String name;
switch (manga.getDisplayMode()) {
case Manga.DISPLAY_NAME:
default:
name = chapter.name;
break;
case Manga.DISPLAY_NUMBER:
String formattedNumber = decimalFormat.format(chapter.chapter_number);
name = context.getString(R.string.display_mode_chapter, formattedNumber);
break;
}
title.setText(name);
title.setTextColor(chapter.read ? readColor : unreadColor);
date.setTextColor(chapter.read ? readColor : unreadColor);
if (!chapter.read && chapter.last_page_read > 0) {
pages.setText(context.getString(R.string.chapter_progress, chapter.last_page_read + 1));
} else {
pages.setText("");
}
onStatusChange(chapter.status);
date.setText(df.format(new Date(chapter.date_upload)));
}
public void onStatusChange(int status) {
switch (status) {
case Download.QUEUE:
downloadText.setText(R.string.chapter_queued); break;
case Download.DOWNLOADING:
downloadText.setText(R.string.chapter_downloading); break;
case Download.DOWNLOADED:
downloadText.setText(R.string.chapter_downloaded); break;
case Download.ERROR:
downloadText.setText(R.string.chapter_error); break;
default:
downloadText.setText(""); break;
}
}
public void onProgressChange(Context context, int downloaded, int total) {
downloadText.setText(context.getString(
R.string.chapter_downloading_progress, downloaded, total));
}
private void showPopupMenu(View view) {
// Create a PopupMenu, giving it the clicked view for an anchor
PopupMenu popup = new PopupMenu(adapter.getFragment().getActivity(), view);
// Inflate our menu resource into the PopupMenu's Menu
popup.getMenuInflater().inflate(R.menu.chapter_single, popup.getMenu());
// Hide download and show delete if the chapter is downloaded and
if(item.isDownloaded()) {
Menu menu = popup.getMenu();
menu.findItem(R.id.action_download).setVisible(false);
menu.findItem(R.id.action_delete).setVisible(true);
}
// Hide mark as unread when the chapter is unread
if(!item.read && item.last_page_read == 0) {
popup.getMenu().findItem(R.id.action_mark_as_unread).setVisible(false);
}
// Hide mark as read when the chapter is read
if(item.read) {
popup.getMenu().findItem(R.id.action_mark_as_read).setVisible(false);
}
// Set a listener so we are notified if a menu item is clicked
popup.setOnMenuItemClickListener(menuItem -> {
Observable<Chapter> chapter = Observable.just(item);
switch (menuItem.getItemId()) {
case R.id.action_download:
return adapter.getFragment().onDownload(chapter);
case R.id.action_delete:
return adapter.getFragment().onDelete(chapter);
case R.id.action_mark_as_read:
return adapter.getFragment().onMarkAsRead(chapter);
case R.id.action_mark_as_unread:
return adapter.getFragment().onMarkAsUnread(chapter);
case R.id.action_mark_previous_as_read:
return adapter.getFragment().onMarkPreviousAsRead(item);
}
return false;
});
// Finally show the PopupMenu
popup.show();
}
}

View File

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

View File

@ -1,286 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.chapter;
import android.os.Bundle;
import android.util.Pair;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
import javax.inject.Inject;
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
import eu.kanade.tachiyomi.data.database.models.Chapter;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.download.DownloadManager;
import eu.kanade.tachiyomi.data.download.model.Download;
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
import eu.kanade.tachiyomi.data.source.SourceManager;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.event.ChapterCountEvent;
import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
import eu.kanade.tachiyomi.event.MangaEvent;
import eu.kanade.tachiyomi.event.ReaderEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import icepick.State;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import rx.subjects.PublishSubject;
import timber.log.Timber;
public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
@Inject DatabaseHelper db;
@Inject SourceManager sourceManager;
@Inject PreferencesHelper preferences;
@Inject DownloadManager downloadManager;
private Manga manga;
private Source source;
private List<Chapter> chapters;
@State boolean hasRequested;
private PublishSubject<List<Chapter>> chaptersSubject;
private static final int GET_MANGA = 1;
private static final int DB_CHAPTERS = 2;
private static final int FETCH_CHAPTERS = 3;
private static final int CHAPTER_STATUS_CHANGES = 4;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
chaptersSubject = PublishSubject.create();
startableLatestCache(GET_MANGA,
() -> Observable.just(manga),
ChaptersFragment::onNextManga);
startableLatestCache(DB_CHAPTERS,
this::getDbChaptersObs,
ChaptersFragment::onNextChapters);
startableFirst(FETCH_CHAPTERS,
this::getOnlineChaptersObs,
(view, result) -> view.onFetchChaptersDone(),
(view, error) -> view.onFetchChaptersError(error));
startableLatestCache(CHAPTER_STATUS_CHANGES,
this::getChapterStatusObs,
(view, download) -> view.onChapterStatusChange(download),
(view, error) -> Timber.e(error.getCause(), error.getMessage()));
registerForEvents();
}
@Override
protected void onDestroy() {
unregisterForEvents();
EventBus.getDefault().removeStickyEvent(ChapterCountEvent.class);
super.onDestroy();
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(MangaEvent event) {
this.manga = event.manga;
start(GET_MANGA);
if (isUnsubscribed(DB_CHAPTERS)) {
source = sourceManager.get(manga.source);
start(DB_CHAPTERS);
add(db.getChapters(manga).asRxObservable()
.subscribeOn(Schedulers.io())
.doOnNext(chapters -> {
this.chapters = chapters;
EventBus.getDefault().postSticky(new ChapterCountEvent(chapters.size()));
for (Chapter chapter : chapters) {
setChapterStatus(chapter);
}
start(CHAPTER_STATUS_CHANGES);
})
.subscribe(chaptersSubject::onNext));
}
}
public void fetchChaptersFromSource() {
hasRequested = true;
start(FETCH_CHAPTERS);
}
private void refreshChapters() {
chaptersSubject.onNext(chapters);
}
private Observable<Pair<Integer, Integer>> getOnlineChaptersObs() {
return source.pullChaptersFromNetwork(manga.url)
.subscribeOn(Schedulers.io())
.flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters, source))
.observeOn(AndroidSchedulers.mainThread());
}
private Observable<List<Chapter>> getDbChaptersObs() {
return chaptersSubject.flatMap(this::applyChapterFilters)
.observeOn(AndroidSchedulers.mainThread());
}
private Observable<List<Chapter>> applyChapterFilters(List<Chapter> chapters) {
Observable<Chapter> observable = Observable.from(chapters)
.subscribeOn(Schedulers.io());
if (onlyUnread()) {
observable = observable.filter(chapter -> !chapter.read);
}
if (onlyDownloaded()) {
observable = observable.filter(chapter -> chapter.status == Download.DOWNLOADED);
}
return observable.toSortedList((chapter, chapter2) -> getSortOrder() ?
Float.compare(chapter2.chapter_number, chapter.chapter_number) :
Float.compare(chapter.chapter_number, chapter2.chapter_number));
}
private void setChapterStatus(Chapter chapter) {
for (Download download : downloadManager.getQueue()) {
if (chapter.id.equals(download.chapter.id)) {
chapter.status = download.getStatus();
return;
}
}
if (downloadManager.isChapterDownloaded(source, manga, chapter)) {
chapter.status = Download.DOWNLOADED;
} else {
chapter.status = Download.NOT_DOWNLOADED;
}
}
private Observable<Download> getChapterStatusObs() {
return downloadManager.getQueue().getStatusObservable()
.observeOn(AndroidSchedulers.mainThread())
.filter(download -> download.manga.id.equals(manga.id))
.doOnNext(this::updateChapterStatus);
}
public void updateChapterStatus(Download download) {
for (Chapter chapter : chapters) {
if (download.chapter.id.equals(chapter.id)) {
chapter.status = download.getStatus();
break;
}
}
if (onlyDownloaded() && download.getStatus() == Download.DOWNLOADED)
refreshChapters();
}
public Observable<Download> getDownloadProgressObs() {
return downloadManager.getQueue().getProgressObservable()
.filter(download -> download.manga.id.equals(manga.id))
.observeOn(AndroidSchedulers.mainThread());
}
public void onOpenChapter(Chapter chapter) {
EventBus.getDefault().postSticky(new ReaderEvent(source, manga, chapter));
}
public Chapter getNextUnreadChapter() {
return db.getNextUnreadChapter(manga).executeAsBlocking();
}
public void markChaptersRead(Observable<Chapter> selectedChapters, boolean read) {
add(selectedChapters
.subscribeOn(Schedulers.io())
.map(chapter -> {
chapter.read = read;
if (!read) chapter.last_page_read = 0;
return chapter;
})
.toList()
.flatMap(chapters -> db.insertChapters(chapters).asRxObservable())
.observeOn(AndroidSchedulers.mainThread())
.subscribe());
}
public void markPreviousChaptersAsRead(Chapter selected) {
Observable.from(chapters)
.filter(c -> c.chapter_number > -1 && c.chapter_number < selected.chapter_number)
.doOnNext(c -> c.read = true)
.toList()
.flatMap(chapters -> db.insertChapters(chapters).asRxObservable())
.subscribe();
}
public void downloadChapters(Observable<Chapter> selectedChapters) {
add(selectedChapters
.toList()
.subscribe(chapters -> {
EventBus.getDefault().postSticky(new DownloadChaptersEvent(manga, chapters));
}));
}
public void deleteChapters(Observable<Chapter> selectedChapters) {
add(selectedChapters
.subscribe(chapter -> {
downloadManager.getQueue().remove(chapter);
}, error -> {
Timber.e(error.getMessage());
}, () -> {
if (onlyDownloaded())
refreshChapters();
}));
}
public void deleteChapter(Chapter chapter) {
downloadManager.deleteChapter(source, manga, chapter);
}
public void revertSortOrder() {
manga.setChapterOrder(getSortOrder() ? Manga.SORT_ZA : Manga.SORT_AZ);
db.insertManga(manga).executeAsBlocking();
refreshChapters();
}
public void setReadFilter(boolean onlyUnread) {
manga.setReadFilter(onlyUnread ? Manga.SHOW_UNREAD : Manga.SHOW_ALL);
db.insertManga(manga).executeAsBlocking();
refreshChapters();
}
public void setDownloadedFilter(boolean onlyDownloaded) {
manga.setDownloadedFilter(onlyDownloaded ? Manga.SHOW_DOWNLOADED : Manga.SHOW_ALL);
db.insertManga(manga).executeAsBlocking();
refreshChapters();
}
public void setDisplayMode(int mode) {
manga.setDisplayMode(mode);
db.insertManga(manga).executeAsBlocking();
}
public boolean onlyDownloaded() {
return manga.getDownloadedFilter() == Manga.SHOW_DOWNLOADED;
}
public boolean onlyUnread() {
return manga.getReadFilter() == Manga.SHOW_UNREAD;
}
public boolean getSortOrder() {
return manga.sortChaptersAZ();
}
public Manga getManga() {
return manga;
}
public List<Chapter> getChapters() {
return chapters;
}
public boolean hasRequested() {
return hasRequested;
}
}

View File

@ -0,0 +1,264 @@
package eu.kanade.tachiyomi.ui.manga.chapter
import android.os.Bundle
import android.util.Pair
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.data.source.base.Source
import eu.kanade.tachiyomi.event.ChapterCountEvent
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
import eu.kanade.tachiyomi.event.MangaEvent
import eu.kanade.tachiyomi.event.ReaderEvent
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import rx.subjects.PublishSubject
import timber.log.Timber
import javax.inject.Inject
class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
@Inject lateinit var db: DatabaseHelper
@Inject lateinit var sourceManager: SourceManager
@Inject lateinit var preferences: PreferencesHelper
@Inject lateinit var downloadManager: DownloadManager
lateinit var manga: Manga
private set
lateinit var source: Source
private set
lateinit var chapters: List<Chapter>
private set
lateinit var chaptersSubject: PublishSubject<List<Chapter>>
private set
var hasRequested: Boolean = false
private set
private val GET_MANGA = 1
private val DB_CHAPTERS = 2
private val FETCH_CHAPTERS = 3
private val CHAPTER_STATUS_CHANGES = 4
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
chaptersSubject = PublishSubject.create()
startableLatestCache(GET_MANGA,
{ Observable.just(manga) },
{ view, manga -> view.onNextManga(manga) })
startableLatestCache(DB_CHAPTERS,
{ getDbChaptersObs() },
{ view, chapters -> view.onNextChapters(chapters) })
startableFirst(FETCH_CHAPTERS,
{ getOnlineChaptersObs() },
{ view, result -> view.onFetchChaptersDone() },
{ view, error -> view.onFetchChaptersError(error) })
startableLatestCache(CHAPTER_STATUS_CHANGES,
{ getChapterStatusObs() },
{ view, download -> view.onChapterStatusChange(download) },
{ view, error -> Timber.e(error.cause, error.message) })
registerForEvents()
}
override fun onDestroy() {
unregisterForEvents()
EventBus.getDefault().removeStickyEvent(ChapterCountEvent::class.java)
super.onDestroy()
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEvent(event: MangaEvent) {
this.manga = event.manga
start(GET_MANGA)
if (isUnsubscribed(DB_CHAPTERS)) {
source = sourceManager.get(manga.source)!!
start(DB_CHAPTERS)
add(db.getChapters(manga).asRxObservable()
.subscribeOn(Schedulers.io())
.doOnNext { chapters ->
this.chapters = chapters
EventBus.getDefault().postSticky(ChapterCountEvent(chapters.size))
for (chapter in chapters) {
setChapterStatus(chapter)
}
start(CHAPTER_STATUS_CHANGES)
}
.subscribe{ chaptersSubject.onNext(it) })
}
}
fun fetchChaptersFromSource() {
hasRequested = true
start(FETCH_CHAPTERS)
}
private fun refreshChapters() {
chaptersSubject.onNext(chapters)
}
fun getOnlineChaptersObs(): Observable<Pair<Int, Int>> {
return source.pullChaptersFromNetwork(manga.url)
.subscribeOn(Schedulers.io())
.flatMap { chapters -> db.insertOrRemoveChapters(manga, chapters, source) }
.observeOn(AndroidSchedulers.mainThread())
}
fun getDbChaptersObs(): Observable<List<Chapter>> {
return chaptersSubject
.flatMap { applyChapterFilters(it) }
.observeOn(AndroidSchedulers.mainThread())
}
fun getChapterStatusObs(): Observable<Download> {
return downloadManager.queue.statusObservable
.observeOn(AndroidSchedulers.mainThread())
.filter { download -> download.manga.id == manga.id }
.doOnNext { updateChapterStatus(it) }
}
private fun applyChapterFilters(chapters: List<Chapter>): Observable<List<Chapter>> {
var observable = Observable.from(chapters).subscribeOn(Schedulers.io())
if (onlyUnread()) {
observable = observable.filter { chapter -> !chapter.read }
}
if (onlyDownloaded()) {
observable = observable.filter { chapter -> chapter.status == Download.DOWNLOADED }
}
return observable.toSortedList { chapter, chapter2 ->
if (sortOrder())
chapter2.chapter_number.compareTo(chapter.chapter_number)
else
chapter.chapter_number.compareTo(chapter2.chapter_number)
}
}
private fun setChapterStatus(chapter: Chapter) {
for (download in downloadManager.queue) {
if (chapter.id == download.chapter.id) {
chapter.status = download.status
return
}
}
if (downloadManager.isChapterDownloaded(source, manga, chapter)) {
chapter.status = Download.DOWNLOADED
} else {
chapter.status = Download.NOT_DOWNLOADED
}
}
fun updateChapterStatus(download: Download) {
for (chapter in chapters) {
if (download.chapter.id == chapter.id) {
chapter.status = download.status
break
}
}
if (onlyDownloaded() && download.status == Download.DOWNLOADED)
refreshChapters()
}
fun onOpenChapter(chapter: Chapter) {
EventBus.getDefault().postSticky(ReaderEvent(source, manga, chapter))
}
fun getNextUnreadChapter(): Chapter? {
return db.getNextUnreadChapter(manga).executeAsBlocking()
}
fun markChaptersRead(selectedChapters: Observable<Chapter>, read: Boolean) {
add(selectedChapters.subscribeOn(Schedulers.io())
.doOnNext { chapter ->
chapter.read = read
if (!read) chapter.last_page_read = 0
}
.toList()
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe())
}
fun markPreviousChaptersAsRead(selected: Chapter) {
Observable.from(chapters)
.filter { c -> c.chapter_number > -1 && c.chapter_number < selected.chapter_number }
.doOnNext { c -> c.read = true }
.toList()
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
.subscribe()
}
fun downloadChapters(selectedChapters: Observable<Chapter>) {
add(selectedChapters.toList()
.subscribe { chapters -> EventBus.getDefault().postSticky(DownloadChaptersEvent(manga, chapters)) })
}
fun deleteChapters(selectedChapters: Observable<Chapter>) {
add(selectedChapters.subscribe(
{ chapter -> downloadManager.queue.remove(chapter) },
{ error -> Timber.e(error.message) },
{
if (onlyDownloaded())
refreshChapters()
}))
}
fun deleteChapter(chapter: Chapter) {
downloadManager.deleteChapter(source, manga, chapter)
}
fun revertSortOrder() {
manga.setChapterOrder(if (sortOrder()) Manga.SORT_ZA else Manga.SORT_AZ)
db.insertManga(manga).executeAsBlocking()
refreshChapters()
}
fun setReadFilter(onlyUnread: Boolean) {
manga.readFilter = if (onlyUnread) Manga.SHOW_UNREAD else Manga.SHOW_ALL
db.insertManga(manga).executeAsBlocking()
refreshChapters()
}
fun setDownloadedFilter(onlyDownloaded: Boolean) {
manga.downloadedFilter = if (onlyDownloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL
db.insertManga(manga).executeAsBlocking()
refreshChapters()
}
fun setDisplayMode(mode: Int) {
manga.displayMode = mode
db.insertManga(manga).executeAsBlocking()
}
fun onlyDownloaded(): Boolean {
return manga.downloadedFilter == Manga.SHOW_DOWNLOADED
}
fun onlyUnread(): Boolean {
return manga.readFilter == Manga.SHOW_UNREAD
}
fun sortOrder(): Boolean {
return manga.sortChaptersAZ()
}
}

View File

@ -1,244 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.info;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.load.model.LazyHeaders;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.cache.CoverCache;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
import nucleus.factory.RequiresPresenter;
/**
* Fragment that shows manga information.
* Uses R.layout.fragment_manga_info.
* UI related actions should be called from here.
*/
@RequiresPresenter(MangaInfoPresenter.class)
public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
/**
* SwipeRefreshLayout showing refresh status
*/
@Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefresh;
/**
* TextView containing artist information.
*/
@Bind(R.id.manga_artist) TextView artist;
/**
* TextView containing author information.
*/
@Bind(R.id.manga_author) TextView author;
/**
* TextView containing chapter count.
*/
@Bind(R.id.manga_chapters) TextView chapterCount;
/**
* TextView containing genres.
*/
@Bind(R.id.manga_genres) TextView genres;
/**
* TextView containing status (ongoing, finished).
*/
@Bind(R.id.manga_status) TextView status;
/**
* TextView containing source.
*/
@Bind(R.id.manga_source) TextView source;
/**
* TextView containing manga summary.
*/
@Bind(R.id.manga_summary) TextView description;
/**
* ImageView of cover.
*/
@Bind(R.id.manga_cover) ImageView cover;
/**
* ImageView containing manga cover shown as blurred backdrop.
*/
@Bind(R.id.backdrop) ImageView backdrop;
/**
* FAB anchored to bottom of top view used to (add / remove) manga (to / from) library.
*/
@Bind(R.id.fab_favorite) FloatingActionButton fabFavorite;
/**
* Create new instance of MangaInfoFragment.
*
* @return MangaInfoFragment.
*/
public static MangaInfoFragment newInstance() {
return new MangaInfoFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment.
View view = inflater.inflate(R.layout.fragment_manga_info, container, false);
// Bind layout objects.
ButterKnife.bind(this, view);
// Set onclickListener to toggle favorite when FAB clicked.
fabFavorite.setOnClickListener(v -> getPresenter().toggleFavorite());
// Set SwipeRefresh to refresh manga data.
swipeRefresh.setOnRefreshListener(this::fetchMangaFromSource);
return view;
}
/**
* Check if manga is initialized.
* If true update view with manga information,
* if false fetch manga information
*
* @param manga manga object containing information about manga.
* @param source the source of the manga.
*/
public void onNextManga(Manga manga, Source source) {
if (manga.initialized) {
// Update view.
setMangaInfo(manga, source);
} else {
// Initialize manga.
fetchMangaFromSource();
}
}
/**
* Update the view with manga information.
*
* @param manga manga object containing information about manga.
* @param mangaSource the source of the manga.
*/
private void setMangaInfo(Manga manga, Source mangaSource) {
// Update artist TextView.
artist.setText(manga.artist);
// Update author TextView.
author.setText(manga.author);
// If manga source is known update source TextView.
if (mangaSource != null) {
source.setText(mangaSource.getVisibleName());
}
// Update genres TextView.
genres.setText(manga.genre);
// Update status TextView.
status.setText(manga.getStatus(getActivity()));
// Update description TextView.
description.setText(manga.description);
// Set the favorite drawable to the correct one.
setFavoriteDrawable(manga.favorite);
// Initialize CoverCache and Glide headers to retrieve cover information.
CoverCache coverCache = getPresenter().coverCache;
LazyHeaders headers = getPresenter().source.getGlideHeaders();
// Check if thumbnail_url is given.
if (manga.thumbnail_url != null) {
// Check if cover is already drawn.
if (cover.getDrawable() == null) {
// If manga is in library then (download / save) (from / to) local cache if available,
// else download from network.
if (manga.favorite) {
coverCache.saveOrLoadFromCache(cover, manga.thumbnail_url, headers);
} else {
coverCache.loadFromNetwork(cover, manga.thumbnail_url, headers);
}
}
// Check if backdrop is already drawn.
if (backdrop.getDrawable() == null) {
// If manga is in library then (download / save) (from / to) local cache if available,
// else download from network.
if (manga.favorite) {
coverCache.saveOrLoadFromCache(backdrop, manga.thumbnail_url, headers);
} else {
coverCache.loadFromNetwork(backdrop, manga.thumbnail_url, headers);
}
}
}
}
/**
* Update chapter count TextView.
*
* @param count number of chapters.
*/
public void setChapterCount(int count) {
chapterCount.setText(String.valueOf(count));
}
/**
* Update FAB with correct drawable.
*
* @param isFavorite determines if manga is favorite or not.
*/
private void setFavoriteDrawable(boolean isFavorite) {
// Set the Favorite drawable to the correct one.
// Border drawable if false, filled drawable if true.
fabFavorite.setImageDrawable(ContextCompat.getDrawable(getContext(), isFavorite ?
R.drawable.ic_bookmark_white_24dp :
R.drawable.ic_bookmark_border_white_24dp));
}
/**
* Start fetching manga information from source.
*/
private void fetchMangaFromSource() {
setRefreshing(true);
// Call presenter and start fetching manga information
getPresenter().fetchMangaFromSource();
}
/**
* Update swipeRefresh to stop showing refresh in progress spinner.
*/
public void onFetchMangaDone() {
setRefreshing(false);
}
/**
* Update swipeRefresh to start showing refresh in progress spinner.
*/
public void onFetchMangaError() {
setRefreshing(false);
}
/**
* Set swipeRefresh status.
*
* @param value status of manga fetch.
*/
private void setRefreshing(boolean value) {
swipeRefresh.setRefreshing(value);
}
}

View File

@ -0,0 +1,179 @@
package eu.kanade.tachiyomi.ui.manga.info
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.base.Source
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.util.setDrawableCompat
import kotlinx.android.synthetic.main.fragment_manga_info.*
import nucleus.factory.RequiresPresenter
/**
* Fragment that shows manga information.
* Uses R.layout.fragment_manga_info.
* UI related actions should be called from here.
*/
@RequiresPresenter(MangaInfoPresenter::class)
class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
companion object {
/**
* Create new instance of MangaInfoFragment.
*
* @return MangaInfoFragment.
*/
fun newInstance(): MangaInfoFragment {
return MangaInfoFragment()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_manga_info, container, false)
}
override fun onViewCreated(view: View?, savedState: Bundle?) {
// Set onclickListener to toggle favorite when FAB clicked.
fab_favorite.setOnClickListener { presenter.toggleFavorite() }
// Set SwipeRefresh to refresh manga data.
swipe_refresh.setOnRefreshListener { fetchMangaFromSource() }
}
/**
* Check if manga is initialized.
* If true update view with manga information,
* if false fetch manga information
*
* @param manga manga object containing information about manga.
* @param source the source of the manga.
*/
fun onNextManga(manga: Manga, source: Source) {
if (manga.initialized) {
// Update view.
setMangaInfo(manga, source)
} else {
// Initialize manga.
fetchMangaFromSource()
}
}
/**
* Update the view with manga information.
*
* @param manga manga object containing information about manga.
* @param source the source of the manga.
*/
private fun setMangaInfo(manga: Manga, source: Source?) {
// Update artist TextView.
manga_artist.text = manga.artist
// Update author TextView.
manga_author.text = manga.author
// If manga source is known update source TextView.
if (source != null) {
manga_source.text = source.visibleName
}
// Update genres TextView.
manga_genres.text = manga.genre
// Update status TextView.
manga_status.text = manga.getStatus(activity)
// Update description TextView.
manga_summary.text = manga.description
// Set the favorite drawable to the correct one.
setFavoriteDrawable(manga.favorite)
// Initialize CoverCache and Glide headers to retrieve cover information.
val coverCache = presenter.coverCache
val headers = presenter.source.glideHeaders
// Check if thumbnail_url is given.
if (manga.thumbnail_url != null) {
// Check if cover is already drawn.
if (manga_cover.drawable == null) {
// If manga is in library then (download / save) (from / to) local cache if available,
// else download from network.
if (manga.favorite) {
coverCache.saveOrLoadFromCache(manga_cover, manga.thumbnail_url, headers)
} else {
coverCache.loadFromNetwork(manga_cover, manga.thumbnail_url, headers)
}
}
// Check if backdrop is already drawn.
if (backdrop.drawable == null) {
// If manga is in library then (download / save) (from / to) local cache if available,
// else download from network.
if (manga.favorite) {
coverCache.saveOrLoadFromCache(backdrop, manga.thumbnail_url, headers)
} else {
coverCache.loadFromNetwork(backdrop, manga.thumbnail_url, headers)
}
}
}
}
/**
* Update chapter count TextView.
*
* @param count number of chapters.
*/
fun setChapterCount(count: Int) {
manga_chapters.text = count.toString()
}
/**
* Update FAB with correct drawable.
*
* @param isFavorite determines if manga is favorite or not.
*/
private fun setFavoriteDrawable(isFavorite: Boolean) {
// Set the Favorite drawable to the correct one.
// Border drawable if false, filled drawable if true.
fab_favorite.setDrawableCompat(if (isFavorite)
R.drawable.ic_bookmark_white_24dp
else
R.drawable.ic_bookmark_border_white_24dp)
}
/**
* Start fetching manga information from source.
*/
private fun fetchMangaFromSource() {
setRefreshing(true)
// Call presenter and start fetching manga information
presenter.fetchMangaFromSource()
}
/**
* Update swipe refresh to stop showing refresh in progress spinner.
*/
fun onFetchMangaDone() {
setRefreshing(false)
}
/**
* Update swipe refresh to start showing refresh in progress spinner.
*/
fun onFetchMangaError() {
setRefreshing(false)
}
/**
* Set swipe refresh status.
*
* @param value whether it should be refreshing or not.
*/
private fun setRefreshing(value: Boolean) {
swipe_refresh.isRefreshing = value
}
}

View File

@ -1,177 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.info;
import android.os.Bundle;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import javax.inject.Inject;
import eu.kanade.tachiyomi.data.cache.CoverCache;
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.source.SourceManager;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.event.ChapterCountEvent;
import eu.kanade.tachiyomi.event.MangaEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* Presenter of MangaInfoFragment.
* Contains information and data for fragment.
* Observable updates should be called from here.
*/
public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
/**
* The id of the restartable.
*/
private static final int GET_MANGA = 1;
/**
* The id of the restartable.
*/
private static final int GET_CHAPTER_COUNT = 2;
/**
* The id of the restartable.
*/
private static final int FETCH_MANGA_INFO = 3;
/**
* Source information.
*/
protected Source source;
/**
* Used to connect to database.
*/
@Inject DatabaseHelper db;
/**
* Used to connect to different manga sources.
*/
@Inject SourceManager sourceManager;
/**
* Used to connect to cache.
*/
@Inject CoverCache coverCache;
/**
* Selected manga information.
*/
private Manga manga;
/**
* Count of chapters.
*/
private int count = -1;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
// Notify the view a manga is available or has changed.
startableLatestCache(GET_MANGA,
() -> Observable.just(manga),
(view, manga) -> view.onNextManga(manga, source));
// Update chapter count.
startableLatestCache(GET_CHAPTER_COUNT,
() -> Observable.just(count),
MangaInfoFragment::setChapterCount);
// Fetch manga info from source.
startableFirst(FETCH_MANGA_INFO,
this::fetchMangaObs,
(view, manga) -> view.onFetchMangaDone(),
(view, error) -> view.onFetchMangaError());
// Listen for events.
registerForEvents();
}
@Override
protected void onDestroy() {
unregisterForEvents();
super.onDestroy();
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(MangaEvent event) {
this.manga = event.manga;
source = sourceManager.get(manga.source);
refreshManga();
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(ChapterCountEvent event) {
if (count != event.getCount()) {
count = event.getCount();
// Update chapter count
start(GET_CHAPTER_COUNT);
}
}
/**
* Fetch manga information from source.
*/
public void fetchMangaFromSource() {
if (isUnsubscribed(FETCH_MANGA_INFO)) {
start(FETCH_MANGA_INFO);
}
}
/**
* Fetch manga information from source.
*
* @return manga information.
*/
private Observable<Manga> fetchMangaObs() {
return source.pullMangaFromNetwork(manga.url)
.flatMap(networkManga -> {
manga.copyFrom(networkManga);
db.insertManga(manga).executeAsBlocking();
return Observable.just(manga);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(manga -> refreshManga());
}
/**
* Update favorite status of manga, (removes / adds) manga (to / from) library.
*/
public void toggleFavorite() {
manga.favorite = !manga.favorite;
onMangaFavoriteChange(manga.favorite);
db.insertManga(manga).executeAsBlocking();
refreshManga();
}
/**
* (Removes / Saves) cover depending on favorite status.
*
* @param isFavorite determines if manga is favorite or not.
*/
private void onMangaFavoriteChange(boolean isFavorite) {
if (isFavorite) {
coverCache.save(manga.thumbnail_url, source.getGlideHeaders());
} else {
coverCache.deleteCoverFromCache(manga.thumbnail_url);
}
}
/**
* Refresh MangaInfo view.
*/
private void refreshManga() {
start(GET_MANGA);
}
}

View File

@ -0,0 +1,173 @@
package eu.kanade.tachiyomi.ui.manga.info
import android.os.Bundle
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.data.source.base.Source
import eu.kanade.tachiyomi.event.ChapterCountEvent
import eu.kanade.tachiyomi.event.MangaEvent
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import javax.inject.Inject
/**
* Presenter of MangaInfoFragment.
* Contains information and data for fragment.
* Observable updates should be called from here.
*/
class MangaInfoPresenter : BasePresenter<MangaInfoFragment>() {
/**
* Active manga.
*/
lateinit var manga: Manga
private set
/**
* Source of the manga.
*/
lateinit var source: Source
private set
/**
* Used to connect to database.
*/
@Inject lateinit var db: DatabaseHelper
/**
* Used to connect to different manga sources.
*/
@Inject lateinit var sourceManager: SourceManager
/**
* Used to connect to cache.
*/
@Inject lateinit var coverCache: CoverCache
/**
* Count of chapters.
*/
private var count = -1
/**
* The id of the restartable.
*/
private val GET_MANGA = 1
/**
* The id of the restartable.
*/
private val GET_CHAPTER_COUNT = 2
/**
* The id of the restartable.
*/
private val FETCH_MANGA_INFO = 3
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
// Notify the view a manga is available or has changed.
startableLatestCache(GET_MANGA,
{ Observable.just(manga) },
{ view, manga -> view.onNextManga(manga, source) })
// Update chapter count.
startableLatestCache(GET_CHAPTER_COUNT,
{ Observable.just(count) },
{ view, count -> view.setChapterCount(count) })
// Fetch manga info from source.
startableFirst(FETCH_MANGA_INFO,
{ fetchMangaObs() },
{ view, manga -> view.onFetchMangaDone() },
{ view, error -> view.onFetchMangaError() })
// Listen for events.
registerForEvents()
}
override fun onDestroy() {
unregisterForEvents()
super.onDestroy()
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEvent(event: MangaEvent) {
manga = event.manga
source = sourceManager.get(manga.source)!!
refreshManga()
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEvent(event: ChapterCountEvent) {
if (count != event.count) {
count = event.count
// Update chapter count
start(GET_CHAPTER_COUNT)
}
}
/**
* Fetch manga information from source.
*/
fun fetchMangaFromSource() {
if (isUnsubscribed(FETCH_MANGA_INFO)) {
start(FETCH_MANGA_INFO)
}
}
/**
* Fetch manga information from source.
*
* @return manga information.
*/
private fun fetchMangaObs(): Observable<Manga> {
return source.pullMangaFromNetwork(manga.url)
.flatMap { networkManga ->
manga.copyFrom(networkManga)
db.insertManga(manga).executeAsBlocking()
Observable.just<Manga>(manga)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { refreshManga() }
}
/**
* Update favorite status of manga, (removes / adds) manga (to / from) library.
*/
fun toggleFavorite() {
manga.favorite = !manga.favorite
onMangaFavoriteChange(manga.favorite)
db.insertManga(manga).executeAsBlocking()
refreshManga()
}
/**
* (Removes / Saves) cover depending on favorite status.
*
* @param isFavorite determines if manga is favorite or not.
*/
private fun onMangaFavoriteChange(isFavorite: Boolean) {
if (isFavorite) {
coverCache.save(manga.thumbnail_url, source.glideHeaders)
} else {
coverCache.deleteCoverFromCache(manga.thumbnail_url)
}
}
/**
* Refresh MangaInfo view.
*/
private fun refreshManga() {
start(GET_MANGA)
}
}

View File

@ -1,151 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.myanimelist;
import android.app.Dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.ProgressBar;
import com.afollestad.materialdialogs.MaterialDialog;
import java.util.List;
import java.util.concurrent.TimeUnit;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.MangaSync;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.subjects.PublishSubject;
public class MyAnimeListDialogFragment extends DialogFragment {
@Bind(R.id.myanimelist_search_field) EditText searchText;
@Bind(R.id.myanimelist_search_results) ListView searchResults;
@Bind(R.id.progress) ProgressBar progressBar;
private MyAnimeListSearchAdapter adapter;
private MangaSync selectedItem;
private Subscription searchSubscription;
public static MyAnimeListDialogFragment newInstance() {
return new MyAnimeListDialogFragment();
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedState) {
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
.customView(R.layout.dialog_myanimelist_search, false)
.positiveText(R.string.button_ok)
.negativeText(R.string.button_cancel)
.onPositive((dialog1, which) -> onPositiveButtonClick())
.build();
ButterKnife.bind(this, dialog.getView());
// Create adapter
adapter = new MyAnimeListSearchAdapter(getActivity());
searchResults.setAdapter(adapter);
// Set listeners
searchResults.setOnItemClickListener((parent, viewList, position, id) ->
selectedItem = adapter.getItem(position));
// Do an initial search based on the manga's title
if (savedState == null) {
String title = getPresenter().manga.title;
searchText.append(title);
search(title);
}
return dialog;
}
@Override
public void onResume() {
super.onResume();
PublishSubject<String> querySubject = PublishSubject.create();
searchText.addTextChangedListener(new SimpleTextChangeListener() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
querySubject.onNext(s.toString());
}
});
// Listen to text changes
searchSubscription = querySubject.debounce(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::search);
}
@Override
public void onPause() {
if (searchSubscription != null) {
searchSubscription.unsubscribe();
}
super.onPause();
}
private void onPositiveButtonClick() {
if (adapter != null && selectedItem != null) {
getPresenter().registerManga(selectedItem);
}
}
private void search(String query) {
if (!TextUtils.isEmpty(query)) {
searchResults.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
getPresenter().searchManga(query);
}
}
public void onSearchResults(List<MangaSync> results) {
selectedItem = null;
progressBar.setVisibility(View.GONE);
searchResults.setVisibility(View.VISIBLE);
adapter.setItems(results);
}
public void onSearchResultsError() {
progressBar.setVisibility(View.GONE);
searchResults.setVisibility(View.VISIBLE);
adapter.clear();
}
public MyAnimeListFragment getMALFragment() {
return (MyAnimeListFragment) getParentFragment();
}
public MyAnimeListPresenter getPresenter() {
return getMALFragment().getPresenter();
}
private static class SimpleTextChangeListener implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
}
}
}

View File

@ -0,0 +1,126 @@
package eu.kanade.tachiyomi.ui.manga.myanimelist
import android.app.Dialog
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.view.View
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaSync
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
import kotlinx.android.synthetic.main.dialog_myanimelist_search.view.*
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import rx.subjects.PublishSubject
import java.util.concurrent.TimeUnit
class MyAnimeListDialogFragment : DialogFragment() {
companion object {
fun newInstance(): MyAnimeListDialogFragment {
return MyAnimeListDialogFragment()
}
}
private lateinit var v: View
lateinit var adapter: MyAnimeListSearchAdapter
private set
lateinit var querySubject: PublishSubject<String>
private set
private var selectedItem: MangaSync? = null
private var searchSubscription: Subscription? = null
override fun onCreateDialog(savedState: Bundle?): Dialog {
val dialog = MaterialDialog.Builder(activity)
.customView(R.layout.dialog_myanimelist_search, false)
.positiveText(android.R.string.ok)
.negativeText(android.R.string.cancel)
.onPositive { dialog1, which -> onPositiveButtonClick() }
.build()
onViewCreated(dialog.view, savedState)
return dialog
}
override fun onViewCreated(view: View, savedState: Bundle?) {
v = view
// Create adapter
adapter = MyAnimeListSearchAdapter(activity)
view.myanimelist_search_results.adapter = adapter
// Set listeners
view.myanimelist_search_results.setOnItemClickListener { parent, viewList, position, id ->
selectedItem = adapter.getItem(position)
}
// Do an initial search based on the manga's title
if (savedState == null) {
val title = presenter.manga.title
view.myanimelist_search_field.append(title)
search(title)
}
querySubject = PublishSubject.create<String>()
view.myanimelist_search_field.addTextChangedListener(object : SimpleTextWatcher() {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
querySubject.onNext(s.toString())
}
})
}
override fun onResume() {
super.onResume()
// Listen to text changes
searchSubscription = querySubject.debounce(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { search(it) }
}
override fun onPause() {
searchSubscription?.unsubscribe()
super.onPause()
}
private fun onPositiveButtonClick() {
selectedItem?.let {
presenter.registerManga(it)
}
}
private fun search(query: String) {
if (!query.isNullOrEmpty()) {
v.myanimelist_search_results.visibility = View.GONE
v.progress.visibility = View.VISIBLE
presenter.searchManga(query)
}
}
fun onSearchResults(results: List<MangaSync>) {
selectedItem = null
v.progress.visibility = View.GONE
v.myanimelist_search_results.visibility = View.VISIBLE
adapter.setItems(results)
}
fun onSearchResultsError() {
v.progress.visibility = View.GONE
v.myanimelist_search_results.visibility = View.VISIBLE
adapter.clear()
}
val malFragment: MyAnimeListFragment
get() = parentFragment as MyAnimeListFragment
val presenter: MyAnimeListPresenter
get() = malFragment.presenter
}

View File

@ -1,181 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.myanimelist;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.NumberPicker;
import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import java.text.DecimalFormat;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.MangaSync;
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
import nucleus.factory.RequiresPresenter;
@RequiresPresenter(MyAnimeListPresenter.class)
public class MyAnimeListFragment extends BaseRxFragment<MyAnimeListPresenter> {
@Bind(R.id.myanimelist_title) TextView title;
@Bind(R.id.myanimelist_chapters) TextView chapters;
@Bind(R.id.myanimelist_score) TextView score;
@Bind(R.id.myanimelist_status) TextView status;
@Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefresh;
private MyAnimeListDialogFragment dialog;
private DecimalFormat decimalFormat = new DecimalFormat("#.##");
private final static String SEARCH_FRAGMENT_TAG = "mal_search";
public static MyAnimeListFragment newInstance() {
return new MyAnimeListFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_myanimelist, container, false);
ButterKnife.bind(this, view);
swipeRefresh.setEnabled(false);
swipeRefresh.setOnRefreshListener(() -> getPresenter().refresh());
return view;
}
public void setMangaSync(MangaSync mangaSync) {
swipeRefresh.setEnabled(mangaSync != null);
if (mangaSync != null) {
title.setText(mangaSync.title);
chapters.setText(mangaSync.last_chapter_read + "/" +
(mangaSync.total_chapters > 0 ? mangaSync.total_chapters : "-"));
score.setText(mangaSync.score == 0 ? "-" : decimalFormat.format(mangaSync.score));
status.setText(getPresenter().myAnimeList.getStatus(mangaSync.status));
}
}
public void onRefreshDone() {
swipeRefresh.setRefreshing(false);
}
public void onRefreshError() {
swipeRefresh.setRefreshing(false);
}
public void setSearchResults(List<MangaSync> results) {
findSearchFragmentIfNeeded();
if (dialog != null) {
dialog.onSearchResults(results);
}
}
public void setSearchResultsError() {
findSearchFragmentIfNeeded();
if (dialog != null) {
dialog.onSearchResultsError();
}
}
private void findSearchFragmentIfNeeded() {
if (dialog == null) {
dialog = (MyAnimeListDialogFragment) getChildFragmentManager()
.findFragmentByTag(SEARCH_FRAGMENT_TAG);
}
}
@OnClick(R.id.myanimelist_title_layout)
void onTitleClick() {
if (dialog == null)
dialog = MyAnimeListDialogFragment.newInstance();
getPresenter().restartSearch();
dialog.show(getChildFragmentManager(), SEARCH_FRAGMENT_TAG);
}
@OnClick(R.id.myanimelist_status_layout)
void onStatusClick() {
if (getPresenter().mangaSync == null)
return;
Context ctx = getActivity();
new MaterialDialog.Builder(ctx)
.title(R.string.status)
.items(getPresenter().getAllStatus(ctx))
.itemsCallbackSingleChoice(getPresenter().getIndexFromStatus(),
(materialDialog, view, i, charSequence) -> {
getPresenter().setStatus(i);
status.setText("...");
return true;
})
.show();
}
@OnClick(R.id.myanimelist_chapters_layout)
void onChaptersClick() {
if (getPresenter().mangaSync == null)
return;
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
.title(R.string.chapters)
.customView(R.layout.dialog_myanimelist_chapters, false)
.positiveText(R.string.button_ok)
.negativeText(R.string.button_cancel)
.onPositive((materialDialog, dialogAction) -> {
View view = materialDialog.getCustomView();
if (view != null) {
NumberPicker np = (NumberPicker) view.findViewById(R.id.chapters_picker);
getPresenter().setLastChapterRead(np.getValue());
chapters.setText("...");
}
})
.show();
View view = dialog.getCustomView();
if (view != null) {
NumberPicker np = (NumberPicker) view.findViewById(R.id.chapters_picker);
// Set initial value
np.setValue(getPresenter().mangaSync.last_chapter_read);
// Don't allow to go from 0 to 9999
np.setWrapSelectorWheel(false);
}
}
@OnClick(R.id.myanimelist_score_layout)
void onScoreClick() {
if (getPresenter().mangaSync == null)
return;
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
.title(R.string.score)
.customView(R.layout.dialog_myanimelist_score, false)
.positiveText(R.string.button_ok)
.negativeText(R.string.button_cancel)
.onPositive((materialDialog, dialogAction) -> {
View view = materialDialog.getCustomView();
if (view != null) {
NumberPicker np = (NumberPicker) view.findViewById(R.id.score_picker);
getPresenter().setScore(np.getValue());
score.setText("...");
}
})
.show();
View view = dialog.getCustomView();
if (view != null) {
NumberPicker np = (NumberPicker) view.findViewById(R.id.score_picker);
// Set initial value
np.setValue((int) getPresenter().mangaSync.score);
}
}
}

View File

@ -0,0 +1,168 @@
package eu.kanade.tachiyomi.ui.manga.myanimelist
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.NumberPicker
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaSync
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.util.toast
import kotlinx.android.synthetic.main.card_myanimelist_personal.*
import kotlinx.android.synthetic.main.fragment_myanimelist.*
import nucleus.factory.RequiresPresenter
import java.text.DecimalFormat
@RequiresPresenter(MyAnimeListPresenter::class)
class MyAnimeListFragment : BaseRxFragment<MyAnimeListPresenter>() {
companion object {
fun newInstance(): MyAnimeListFragment {
return MyAnimeListFragment()
}
}
private var dialog: MyAnimeListDialogFragment? = null
private val decimalFormat = DecimalFormat("#.##")
private val SEARCH_FRAGMENT_TAG = "mal_search"
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_myanimelist, container, false)
}
override fun onViewCreated(view: View, savedState: Bundle?) {
swipe_refresh.isEnabled = false
swipe_refresh.setOnRefreshListener { presenter.refresh() }
myanimelist_title_layout.setOnClickListener { onTitleClick() }
myanimelist_status_layout.setOnClickListener { onStatusClick() }
myanimelist_chapters_layout.setOnClickListener { onChaptersClick() }
myanimelist_score_layout.setOnClickListener { onScoreClick() }
}
fun setMangaSync(mangaSync: MangaSync?) {
swipe_refresh.isEnabled = mangaSync != null
mangaSync?.let {
myanimelist_title.text = it.title
val chaptersText = if (it.total_chapters > 0)
"${it.last_chapter_read}/${it.total_chapters}" else "${it.last_chapter_read}/-"
myanimelist_chapters.text = chaptersText
myanimelist_score.text = if (it.score == 0f) "-" else decimalFormat.format(it.score)
myanimelist_status.text = presenter.myAnimeList.getStatus(it.status)
}
}
fun onRefreshDone() {
swipe_refresh.isRefreshing = false
}
fun onRefreshError() {
swipe_refresh.isRefreshing = false
}
fun setSearchResults(results: List<MangaSync>) {
findSearchFragmentIfNeeded()
dialog?.onSearchResults(results)
}
fun setSearchResultsError(error: Throwable) {
findSearchFragmentIfNeeded()
context.toast(error.message)
dialog?.onSearchResultsError()
}
private fun findSearchFragmentIfNeeded() {
if (dialog == null) {
dialog = childFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG) as MyAnimeListDialogFragment
}
}
fun onTitleClick() {
if (dialog == null) {
dialog = MyAnimeListDialogFragment.newInstance()
}
presenter.restartSearch()
dialog?.show(childFragmentManager, SEARCH_FRAGMENT_TAG)
}
fun onStatusClick() {
if (presenter.mangaSync == null)
return
MaterialDialog.Builder(activity)
.title(R.string.status)
.items(presenter.getAllStatus())
.itemsCallbackSingleChoice(presenter.getIndexFromStatus(), { dialog, view, i, charSequence ->
presenter.setStatus(i)
myanimelist_status.text = "..."
true
})
.show()
}
fun onChaptersClick() {
if (presenter.mangaSync == null)
return
val dialog = MaterialDialog.Builder(activity)
.title(R.string.chapters)
.customView(R.layout.dialog_myanimelist_chapters, false)
.positiveText(android.R.string.ok)
.negativeText(android.R.string.cancel)
.onPositive { d, action ->
val view = d.customView
if (view != null) {
val np = view.findViewById(R.id.chapters_picker) as NumberPicker
np.clearFocus()
presenter.setLastChapterRead(np.value)
myanimelist_chapters.text = "..."
}
}
.show()
val view = dialog.customView
if (view != null) {
val np = view.findViewById(R.id.chapters_picker) as NumberPicker
// Set initial value
np.value = presenter.mangaSync!!.last_chapter_read
// Don't allow to go from 0 to 9999
np.wrapSelectorWheel = false
}
}
fun onScoreClick() {
if (presenter.mangaSync == null)
return
val dialog = MaterialDialog.Builder(activity)
.title(R.string.score)
.customView(R.layout.dialog_myanimelist_score, false)
.positiveText(android.R.string.ok)
.negativeText(android.R.string.cancel)
.onPositive { d, action ->
val view = d.customView
if (view != null) {
val np = view.findViewById(R.id.score_picker) as NumberPicker
np.clearFocus()
presenter.setScore(np.value)
myanimelist_score.text = "..."
}
}
.show()
val view = dialog.customView
if (view != null) {
val np = view.findViewById(R.id.score_picker) as NumberPicker
// Set initial value
np.value = presenter.mangaSync!!.score.toInt()
}
}
}

View File

@ -1,197 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.myanimelist;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
import javax.inject.Inject;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.database.models.MangaSync;
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
import eu.kanade.tachiyomi.data.mangasync.services.MyAnimeList;
import eu.kanade.tachiyomi.event.MangaEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import eu.kanade.tachiyomi.util.ToastUtil;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import timber.log.Timber;
public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
@Inject DatabaseHelper db;
@Inject MangaSyncManager syncManager;
protected MyAnimeList myAnimeList;
protected Manga manga;
protected MangaSync mangaSync;
private String query;
private static final int GET_MANGA_SYNC = 1;
private static final int GET_SEARCH_RESULTS = 2;
private static final int REFRESH = 3;
private static final String PREFIX_MY = "my:";
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
myAnimeList = syncManager.getMyAnimeList();
startableLatestCache(GET_MANGA_SYNC,
() -> db.getMangaSync(manga, myAnimeList).asRxObservable()
.doOnNext(mangaSync -> this.mangaSync = mangaSync)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
MyAnimeListFragment::setMangaSync);
startableLatestCache(GET_SEARCH_RESULTS,
this::getSearchResultsObservable,
(view, results) -> {
view.setSearchResults(results);
}, (view, error) -> {
Timber.e(error.getMessage());
view.setSearchResultsError();
});
startableFirst(REFRESH,
() -> myAnimeList.getList()
.flatMap(myList -> {
for (MangaSync myManga : myList) {
if (myManga.remote_id == mangaSync.remote_id) {
mangaSync.copyPersonalFrom(myManga);
mangaSync.total_chapters = myManga.total_chapters;
return Observable.just(mangaSync);
}
}
return Observable.error(new Exception("Could not find manga"));
})
.flatMap(myManga -> db.insertMangaSync(myManga).asRxObservable())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
(view, result) -> view.onRefreshDone(),
(view, error) -> view.onRefreshError());
}
@Override
protected void onTakeView(MyAnimeListFragment view) {
super.onTakeView(view);
registerForEvents();
}
@Override
protected void onDropView() {
unregisterForEvents();
super.onDropView();
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(MangaEvent event) {
this.manga = event.manga;
start(GET_MANGA_SYNC);
}
private Observable<List<MangaSync>> getSearchResultsObservable() {
Observable<List<MangaSync>> observable;
if (query.startsWith(PREFIX_MY)) {
String realQuery = query.substring(PREFIX_MY.length()).toLowerCase().trim();
observable = myAnimeList.getList()
.flatMap(Observable::from)
.filter(manga -> manga.title.toLowerCase().contains(realQuery))
.toList();
} else {
observable = myAnimeList.search(query);
}
return observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private void updateRemote() {
add(myAnimeList.update(mangaSync)
.flatMap(response -> db.insertMangaSync(mangaSync).asRxObservable())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(next -> {},
error -> {
Timber.e(error.getMessage());
// Restart on error to set old values
start(GET_MANGA_SYNC);
}
));
}
public void searchManga(String query) {
if (TextUtils.isEmpty(query) || query.equals(this.query))
return;
this.query = query;
start(GET_SEARCH_RESULTS);
}
public void restartSearch() {
this.query = null;
stop(GET_SEARCH_RESULTS);
}
public void registerManga(MangaSync manga) {
manga.manga_id = this.manga.id;
add(myAnimeList.bind(manga)
.flatMap(response -> {
if (response.isSuccessful()) {
return db.insertMangaSync(manga).asRxObservable();
}
return Observable.error(new Exception("Could not bind manga"));
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(manga2 -> {},
error -> ToastUtil.showShort(getContext(), error.getMessage())));
}
public String[] getAllStatus(Context context) {
return new String[] {
context.getString(R.string.reading),
context.getString(R.string.completed),
context.getString(R.string.on_hold),
context.getString(R.string.dropped),
context.getString(R.string.plan_to_read)
};
}
public int getIndexFromStatus() {
return mangaSync.status == 6 ? 4 : mangaSync.status - 1;
}
public void setStatus(int index) {
mangaSync.status = index == 4 ? 6 : index + 1;
updateRemote();
}
public void setScore(int score) {
mangaSync.score = score;
updateRemote();
}
public void setLastChapterRead(int chapterNumber) {
mangaSync.last_chapter_read = chapterNumber;
updateRemote();
}
public void refresh() {
if (mangaSync != null) {
start(REFRESH);
}
}
}

View File

@ -0,0 +1,191 @@
package eu.kanade.tachiyomi.ui.manga.myanimelist
import android.os.Bundle
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaSync
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
import eu.kanade.tachiyomi.event.MangaEvent
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.toast
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
@Inject lateinit var db: DatabaseHelper
@Inject lateinit var syncManager: MangaSyncManager
val myAnimeList by lazy { syncManager.myAnimeList }
lateinit var manga: Manga
private set
var mangaSync: MangaSync? = null
private set
private var query: String? = null
private val GET_MANGA_SYNC = 1
private val GET_SEARCH_RESULTS = 2
private val REFRESH = 3
private val PREFIX_MY = "my:"
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
startableLatestCache(GET_MANGA_SYNC,
{ db.getMangaSync(manga, myAnimeList).asRxObservable()
.doOnNext { mangaSync = it }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) },
{ view, mangaSync -> view.setMangaSync(mangaSync) })
startableLatestCache(GET_SEARCH_RESULTS,
{ getSearchResultsObservable() },
{ view, results -> view.setSearchResults(results) },
{ view, error -> view.setSearchResultsError(error) })
startableFirst(REFRESH,
{ getRefreshObservable() },
{ view, result -> view.onRefreshDone() },
{ view, error -> view.onRefreshError() })
registerForEvents()
}
override fun onDestroy() {
unregisterForEvents()
super.onDestroy()
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEvent(event: MangaEvent) {
manga = event.manga
start(GET_MANGA_SYNC)
}
fun getSearchResultsObservable(): Observable<List<MangaSync>> {
return query?.let { query ->
val observable: Observable<List<MangaSync>>
if (query.startsWith(PREFIX_MY)) {
val realQuery = query.substring(PREFIX_MY.length).toLowerCase().trim()
observable = myAnimeList.getList()
.flatMap { Observable.from(it) }
.filter { it.title.toLowerCase().contains(realQuery) }
.toList()
} else {
observable = myAnimeList.search(query)
}
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
} ?: Observable.error(Exception("Null query"))
}
fun getRefreshObservable(): Observable<PutResult> {
return mangaSync?.let { mangaSync ->
myAnimeList.getList()
.flatMap { myList ->
for (myManga in myList) {
if (myManga.remote_id == mangaSync.remote_id) {
mangaSync.copyPersonalFrom(myManga)
mangaSync.total_chapters = myManga.total_chapters
return@flatMap Observable.just(mangaSync)
}
}
Observable.error<MangaSync>(Exception("Could not find manga"))
}
.flatMap { db.insertMangaSync(it).asRxObservable() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
} ?: Observable.error(Exception("Not found"))
}
private fun updateRemote() {
mangaSync?.let { mangaSync ->
add(myAnimeList.update(mangaSync)
.flatMap { response -> db.insertMangaSync(mangaSync).asRxObservable() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ next -> },
{ error ->
Timber.e(error.message)
// Restart on error to set old values
start(GET_MANGA_SYNC)
}))
}
}
fun searchManga(query: String) {
if (query.isNullOrEmpty() || query == this.query)
return
this.query = query
start(GET_SEARCH_RESULTS)
}
fun restartSearch() {
query = null
stop(GET_SEARCH_RESULTS)
}
fun registerManga(sync: MangaSync) {
sync.manga_id = manga.id
add(myAnimeList.bind(sync)
.flatMap { response ->
if (response.isSuccessful) {
db.insertMangaSync(sync).asRxObservable()
} else {
Observable.error(Exception("Could not bind manga"))
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ },
{ error -> context.toast(error.message) }))
}
fun getAllStatus(): List<String> {
return listOf(context.getString(R.string.reading),
context.getString(R.string.completed),
context.getString(R.string.on_hold),
context.getString(R.string.dropped),
context.getString(R.string.plan_to_read))
}
fun getIndexFromStatus(): Int {
return mangaSync?.let { mangaSync ->
if (mangaSync.status == 6) 4 else mangaSync.status - 1
} ?: 0
}
fun setStatus(index: Int) {
mangaSync?.status = if (index == 4) 6 else index + 1
updateRemote()
}
fun setScore(score: Int) {
mangaSync?.score = score.toFloat()
updateRemote()
}
fun setLastChapterRead(chapterNumber: Int) {
mangaSync?.last_chapter_read = chapterNumber
updateRemote()
}
fun refresh() {
if (mangaSync != null) {
start(REFRESH)
}
}
}

View File

@ -1,61 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.myanimelist;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.MangaSync;
public class MyAnimeListSearchAdapter extends ArrayAdapter<MangaSync> {
public MyAnimeListSearchAdapter(Context context) {
super(context, R.layout.dialog_myanimelist_search_item, new ArrayList<>());
}
@Override
public View getView(int position, View view, ViewGroup parent) {
// Get the data item for this position
MangaSync sync = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
SearchViewHolder holder; // view lookup cache stored in tag
if (view == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(R.layout.dialog_myanimelist_search_item, parent, false);
holder = new SearchViewHolder(view);
view.setTag(holder);
} else {
holder = (SearchViewHolder) view.getTag();
}
holder.onSetValues(sync);
return view;
}
public void setItems(List<MangaSync> syncs) {
setNotifyOnChange(false);
clear();
addAll(syncs);
notifyDataSetChanged();
}
public static class SearchViewHolder {
@Bind(R.id.myanimelist_result_title) TextView title;
public SearchViewHolder(View view) {
ButterKnife.bind(this, view);
}
public void onSetValues(MangaSync sync) {
title.setText(sync.title);
}
}
}

View File

@ -0,0 +1,46 @@
package eu.kanade.tachiyomi.ui.manga.myanimelist
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaSync
import eu.kanade.tachiyomi.util.inflate
import kotlinx.android.synthetic.main.dialog_myanimelist_search_item.view.*
import java.util.*
class MyAnimeListSearchAdapter(context: Context) :
ArrayAdapter<MangaSync>(context, R.layout.dialog_myanimelist_search_item, ArrayList<MangaSync>()) {
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
var v = view
// Get the data item for this position
val sync = getItem(position)
// Check if an existing view is being reused, otherwise inflate the view
val holder: SearchViewHolder // view lookup cache stored in tag
if (v == null) {
v = parent.inflate(R.layout.dialog_myanimelist_search_item)
holder = SearchViewHolder(v)
v.tag = holder
} else {
holder = v.tag as SearchViewHolder
}
holder.onSetValues(sync)
return v
}
fun setItems(syncs: List<MangaSync>) {
setNotifyOnChange(false)
clear()
addAll(syncs)
notifyDataSetChanged()
}
class SearchViewHolder(private val view: View) {
fun onSetValues(sync: MangaSync) {
view.myanimelist_result_title.text = sync.title
}
}
}

View File

@ -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<ReaderPresenter> {
initializeSettings();
maxBitmapSize = GLUtil.getMaxTextureSize();
}
@ -393,12 +395,12 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
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));
}
}

View File

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

View File

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

View File

@ -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<RecentChaptersPresenter>(), 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<RecentChaptersPresenter>(), Flexib
*/
fun onNextMangaChapters(chapters: List<Any>) {
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.
@ -40,3 +51,12 @@ val Context.notificationManager : NotificationManager
*/
val Context.alarmManager: AlarmManager
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)
}
}

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

View File

@ -4,10 +4,10 @@
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask"
android:drawable="@color/list_choice_pressed_bg_light" />
android:drawable="@color/dividerLight" />
<item>
<selector>
<item android:state_activated="true" android:drawable="@color/list_choice_pressed_bg_light"/>
<item android:state_activated="true" android:drawable="@color/dividerLight"/>
</selector>
</item>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccent"
>
<item>
<selector>
<item android:state_selected="true">
<color android:color="@color/selectorColorDark" />
</item>
<item android:state_activated="true">
<color android:color="@color/selectorColorDark" />
</item>
<item>
<color android:color="@color/backgroundDark" />
</item>
</selector>
</item>
</ripple>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccent"
>
<item>
<selector>
<item android:state_selected="true">
<color android:color="@color/selectorColorLight" />
</item>
<item android:state_activated="true">
<color android:color="@color/selectorColorLight" />
</item>
<item>
<color android:color="@color/backgroundLight" />
</item>
</selector>
</item>
</ripple>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/line_grey">
<item android:drawable="@color/white"/>
android:color="@color/dividerLight">
<item android:drawable="@color/md_white_1000"/>
</ripple>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -6,6 +6,6 @@
android:width="1dp"
android:height="1dp" />
<solid android:color="@color/divider" />
<solid android:color="@color/dividerLight" />
</shape>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/reader_background_checkbox_selected" />
<item android:drawable="@drawable/reader_background_checkbox_unselected" />
</selector>

View File

@ -2,9 +2,9 @@
<selector android:exitFadeDuration="@android:integer/config_longAnimTime"
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@color/list_choice_pressed_bg_light"/>
<item android:state_pressed="true" android:drawable="@color/list_choice_pressed_bg_light"/>
<item android:state_activated="true" android:drawable="@color/list_choice_pressed_bg_light"/>
<item android:state_focused="true" android:drawable="@color/dividerLight"/>
<item android:state_pressed="true" android:drawable="@color/dividerLight"/>
<item android:state_activated="true" android:drawable="@color/dividerLight"/>
<item android:drawable="@android:color/transparent"/>
</selector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector android:exitFadeDuration="@android:integer/config_longAnimTime"
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@color/selectorColorDark"/>
<item android:state_pressed="true" android:drawable="@color/selectorColorDark"/>
<item android:state_activated="true" android:drawable="@color/selectorColorDark"/>
<item android:drawable="@android:color/background_dark"/>
</selector>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--<selector android:exitFadeDuration="@android:integer/config_longAnimTime"-->
<!--xmlns:android="http://schemas.android.com/apk/res/android">-->
<!--<item android:state_focused="true" android:drawable="?attr/colorAccent"/>-->
<!--<item android:state_pressed="true" android:drawable="?attr/colorAccent"/>-->
<!--<item android:state_activated="true" android:drawable="?attr/colorAccent"/>-->
<!--<item android:drawable="?android:attr/colorBackground"/>-->
<!--</selector>-->
<selector android:exitFadeDuration="@android:integer/config_longAnimTime"
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@color/selectorColorLight"/>
<item android:state_pressed="true" android:drawable="@color/selectorColorLight"/>
<item android:state_activated="true" android:drawable="@color/selectorColorLight"/>
<item android:drawable="@color/backgroundLight"/>
</selector>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/super_light_grey" android:state_pressed="true"/>
<item android:drawable="@color/super_light_grey" android:state_focused="true"/>
<item android:drawable="@color/white"/>
<item android:drawable="@color/dividerLight" android:state_pressed="true"/>
<item android:drawable="@color/dividerLight" android:state_focused="true"/>
<item android:drawable="@color/md_white_1000"/>
</selector>

View File

@ -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"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
@ -26,7 +26,6 @@
android:layout_margin="@dimen/fab_margin"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_add_white_24dp"
app:backgroundTint="@color/colorPrimary"
app:layout_anchor="@id/recycler"
app:layout_anchorGravity="bottom|right|end"
app:layout_behavior="eu.kanade.tachiyomi.ui.base.fab.FABAnimationUpDown"/>

View File

@ -57,7 +57,7 @@
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
style="@style/Theme.NavigationDrawer"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"

View File

@ -14,8 +14,8 @@
android:layout_height="wrap_content"
android:padding="4dp"
android:layout_gravity="bottom|left"
android:background="@color/page_number_background"
android:textColor="@color/primary_text"
android:background="?android:attr/colorBackground"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
android:id="@+id/page_number"/>

View File

@ -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" />
<RelativeLayout
android:id="@+id/myanimelist_status_layout"
@ -68,7 +68,7 @@
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_below="@id/myanimelist_status_layout"
android:background="@color/list_choice_pressed_bg_light" />
android:background="@color/dividerLight" />
<RelativeLayout
android:id="@+id/myanimelist_chapters_layout"
@ -101,7 +101,7 @@
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_below="@id/myanimelist_chapters_layout"
android:background="@color/list_choice_pressed_bg_light" />
android:background="@color/dividerLight" />
<RelativeLayout
android:id="@+id/myanimelist_score_layout"

View File

@ -48,7 +48,7 @@
android:dividerHeight="0dp"
android:clipToPadding="false"
android:choiceMode="singleChoice"
android:listSelector="@color/list_choice_pressed_bg_light"
android:listSelector="@color/dividerLight"
android:visibility="gone"/>
</LinearLayout>

View File

@ -23,7 +23,7 @@
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
android:id="@+id/catalogue_grid"
style="@style/AppTheme.GridView"
style="@style/Theme.GridView"
android:columnWidth="140dp"
tools:listitem="@layout/item_catalogue_grid" />

View File

@ -6,7 +6,7 @@
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
android:id="@+id/recycler"
style="@style/AppTheme.GridView"
style="@style/Theme.GridView"
android:columnWidth="140dp"
tools:listitem="@layout/item_catalogue_grid" />

View File

@ -14,7 +14,7 @@
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/chapter_list"
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
@ -41,7 +41,7 @@
android:theme="@style/AppTheme.Popup">
<ImageView
android:id="@+id/action_sort"
android:id="@+id/sort_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
@ -52,9 +52,9 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toEndOf="@+id/action_sort"
android:layout_toLeftOf="@+id/action_next_unread"
android:layout_toRightOf="@+id/action_sort"
android:layout_toEndOf="@+id/sort_btn"
android:layout_toLeftOf="@+id/next_unread_btn"
android:layout_toRightOf="@+id/sort_btn"
android:gravity="center_vertical">
<View
@ -64,7 +64,7 @@
android:background="@color/white"/>
<CheckBox
android:id="@+id/action_show_unread"
android:id="@+id/show_unread"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
@ -73,7 +73,7 @@
android:title="@string/action_show_unread"/>
<CheckBox
android:id="@+id/action_show_downloaded"
android:id="@+id/show_downloaded"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
@ -90,7 +90,7 @@
</LinearLayout>
<ImageView
android:id="@+id/action_next_unread"
android:id="@+id/next_unread_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"

View File

@ -10,7 +10,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants"
android:background="@color/white"
tools:listitem="@layout/item_recent_chapter">
</android.support.v7.widget.RecyclerView>

View File

@ -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">
<RelativeLayout
android:layout_width="match_parent"
@ -21,7 +21,7 @@
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:background="@color/md_white_1000"
tools:background="@color/md_red_100"
tools:src="@mipmap/ic_launcher"/>
@ -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"/>

View File

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

View File

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeightLarge"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:background="@drawable/selector_chapter_light">
android:background="?attr/selectable_list_drawable"
>
<ImageView
android:id="@+id/image"
android:layout_width="50dp"
@ -21,7 +21,7 @@
android:layout_marginRight="@dimen/margin_right"
android:layout_marginEnd="@dimen/margin_right"/>
<ImageView
<android.support.v7.widget.AppCompatImageView
android:id="@+id/reorder"
android:layout_width="50dp"
android:layout_height="50dp"
@ -33,7 +33,8 @@
android:layout_centerInParent="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
app:srcCompat="@drawable/ic_reorder_grey_24dp"/>
app:srcCompat="@drawable/ic_reorder_grey_24dp"
android:tint="?android:attr/textColorPrimary"/>
<TextView
android:id="@+id/title"
@ -47,7 +48,7 @@
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textColor="@color/primary_text"
android:textColor="?android:attr/textColorPrimary"
tools:text="Title"/>
</RelativeLayout>

View File

@ -31,7 +31,6 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:textAllCaps="true"
android:textColor="@color/accent_text"
android:textSize="12sp"/>
</RelativeLayout>
@ -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"/>
<TextView
@ -60,7 +59,7 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textAppearance="@style/TextAppearance.Small"
tools:text="Title"/>
</LinearLayout>
@ -82,7 +81,7 @@
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft">
<ImageView
<android.support.v7.widget.AppCompatImageView
android:id="@+id/chapterMenu"
android:layout_width="24dp"
android:layout_height="24dp"

View File

@ -15,7 +15,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@color/white"
android:textColor="@color/md_white_1000"
android:textSize="16sp"
android:id="@+id/section_text"
android:layout_gravity="center_vertical"

View File

@ -5,10 +5,10 @@
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.Overlay.Dark"
android:background="@color/colorPrimary"
android:visibility="gone"
android:theme="@style/AppTheme.Tab"
app:tabIndicatorColor="@android:color/white"
app:tabGravity="center"
app:tabMode="scrollable"
app:tabMinWidth="75dp"
app:tabIndicatorColor="@color/white"/>
app:tabMinWidth="75dp"/>

View File

@ -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" />
<ImageView
android:layout_marginLeft="16dp"
@ -17,7 +17,7 @@
android:layout_width="64dp"
android:layout_height="64dp"
android:scaleType="centerCrop"
android:src="@drawable/test" />
android:src="@drawable/test"/>
<LinearLayout
android:layout_width="match_parent"
@ -34,16 +34,18 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:text="Desarrollador Android"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
android:text="John Doe"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:visibility="gone"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:text="thedahnark@gmail.com"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
android:text="email@email.com"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone"/>
</LinearLayout>

View File

@ -17,7 +17,7 @@
<View android:id="@+id/titleDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/line_grey"
android:background="@color/dividerLight"
android:layout_marginTop="6dp"
android:layout_marginBottom="24dp"/>

View File

@ -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"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/reader_menu_bottom"
android:background="@color/reader_menu_background"
android:background="?colorPrimary"
android:orientation="vertical"
android:layout_alignParentBottom="true">
@ -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"
/>
<TextView
@ -56,7 +54,7 @@
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_gravity="center_vertical"
android:textColor="@color/light_grey"
android:textColor="?android:attr/textColorHint"
android:textSize="15sp"
android:gravity="center_horizontal" />
@ -70,7 +68,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<ImageButton
<android.support.v7.widget.AppCompatImageButton
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@ -79,7 +77,7 @@
android:layout_gravity="center_vertical"
android:background="?android:selectableItemBackground" />
<ImageButton
<android.support.v7.widget.AppCompatImageButton
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@ -88,7 +86,7 @@
android:layout_gravity="center_vertical"
android:background="?android:selectableItemBackground"/>
<ImageButton
<android.support.v7.widget.AppCompatImageButton
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@ -97,7 +95,7 @@
android:layout_gravity="center_vertical"
android:background="?android:selectableItemBackground" />
<ImageButton
<android.support.v7.widget.AppCompatImageButton
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@ -106,7 +104,7 @@
android:layout_gravity="center_vertical"
android:background="?android:selectableItemBackground" />
<ImageButton
<android.support.v7.widget.AppCompatImageButton
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"

View File

@ -1,69 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/reader_menu_background"
android:paddingRight="10dp"
android:paddingLeft="5dp"
android:paddingTop="5dp"
android:paddingBottom="5dp">
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">
<LinearLayout
android:id="@+id/image_decoder_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/image_decoder_container">
android:layout_height="wrap_content">
<TextView
android:id="@+id/image_decoder_initial"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:textSize="16sp"
android:gravity="center"
android:textColor="@color/colorAccent"/>
android:textColor="@color/colorAccent"
android:textSize="16sp"/>
<TextView
android:id="@+id/image_decoder"
style="@style/reader_menu_settings_item"
android:text="@string/pref_image_decoder"/>
android:layout_width="wrap_content"
android:layout_height="36dp"
android:gravity="center"
android:text="@string/pref_image_decoder"
style="@style/TextAppearance.Light"/>
</LinearLayout>
<CheckBox
<android.support.v7.widget.AppCompatCheckBox
android:id="@+id/reader_theme"
style="@style/reader_menu_settings_item"
android:button="@drawable/reader_background_checkbox"
android:text="@string/pref_reader_theme"/>
android:layout_width="wrap_content"
android:layout_height="36dp"
android:gravity="center"
android:text="@string/pref_reader_theme"
style="@style/AppTheme.CheckBox.Light"/>
<CheckBox
<android.support.v7.widget.AppCompatCheckBox
android:id="@+id/enable_transitions"
style="@style/reader_menu_settings_item"
android:text="@string/pref_enable_transitions"/>
android:layout_width="wrap_content"
android:layout_height="36dp"
android:gravity="center"
android:text="@string/pref_enable_transitions"
style="@style/AppTheme.CheckBox.Light"/>
<CheckBox
<android.support.v7.widget.AppCompatCheckBox
android:id="@+id/show_page_number"
style="@style/reader_menu_settings_item"
android:text="@string/pref_show_page_number"/>
android:layout_width="wrap_content"
android:layout_height="36dp"
android:gravity="center"
android:text="@string/pref_show_page_number"
style="@style/AppTheme.CheckBox.Light"/>
<CheckBox
<android.support.v7.widget.AppCompatCheckBox
android:id="@+id/hide_status_bar"
style="@style/reader_menu_settings_item"
android:text="@string/pref_hide_status_bar"/>
android:layout_width="wrap_content"
android:layout_height="36dp"
android:gravity="center"
android:text="@string/pref_hide_status_bar"
style="@style/AppTheme.CheckBox.Light"/>
<CheckBox
<android.support.v7.widget.AppCompatCheckBox
android:id="@+id/keep_screen_on"
style="@style/reader_menu_settings_item"
android:text="@string/pref_keep_screen_on"/>
android:layout_width="wrap_content"
android:layout_height="36dp"
android:gravity="center"
android:text="@string/pref_keep_screen_on"
style="@style/AppTheme.CheckBox.Light"/>
<CheckBox
<android.support.v7.widget.AppCompatCheckBox
android:id="@+id/custom_brightness"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/reader_menu_settings_item"
android:gravity="center"
android:text="@string/pref_custom_brightness"
android:id="@+id/custom_brightness" />
style="@style/AppTheme.CheckBox.Light"/>
<SeekBar
android:id="@+id/brightness_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/brightness_seekbar" />
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -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"/>
android:theme="@style/AppTheme.Tab"
app:tabIndicatorColor="@android:color/white"/>

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/AppTheme.ActionBar"/>
android:theme="?attr/actionBarTheme"/>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.NavigationDrawer">
<item name="android:layout_width">320dp</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.NavigationDrawer">
<item name="android:layout_width">400dp</item>
</style>
</resources>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="AppTheme.Base">
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Tachiyomi" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@color/md_statusbar_translucent</item>
<item name="android:windowContentTransitions">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
<item name="android:windowSharedElementExitTransition">@android:transition/move</item>
</style>
<style name="Theme.Tachiyomi.Dark" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@color/md_statusbar_translucent</item>
<item name="android:windowContentTransitions">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
<item name="android:windowSharedElementExitTransition">@android:transition/move</item>
</style>
</resources>

View File

@ -22,6 +22,16 @@
<item>@string/webtoon_viewer</item>
</string-array>
<string-array name="themes_values">
<item>1</item>
<item>2</item>
</string-array>
<string-array name="themes">
<item>"Light Theme"</item>
<item>"Dark Theme"</item>
</string-array>
<string-array name="download_slots">
<item>1</item>
<item>2</item>

View File

@ -16,4 +16,7 @@
<attr name="min" format="integer"/>
<attr name="max" format="integer"/>
</declare-styleable>
<attr name="selectable_list_drawable" format="reference|integer" />
</resources>

View File

@ -1,718 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorAccent">@color/md_blue_A400</color>
<!-- Application Colors -->
<color name="colorPrimary">#54759e</color>
<color name="colorPrimaryDark">#435e7e</color>
<color name="colorPrimarySuperDark">@color/md_blue_grey_900</color>
<color name="colorPrimaryLight">@color/md_blue_grey_100</color>
<color name="colorAccent">@color/md_blue_400</color>
<color name="colorBackgroundLight">@color/md_blue_grey_50</color>
<!-- Light Theme -->
<color name="textColorPrimaryLight">@color/md_black_1000_87</color>
<color name="textColorSecondaryLight">@color/md_black_1000_54</color>
<color name="textColorHintLight">@color/md_black_1000_38</color>
<color name="dividerLight">@color/md_white_1000_12</color>
<color name="primary">@color/colorPrimary</color>
<color name="primary_dark">@color/colorPrimaryDark</color>
<color name="primary_light">@color/colorPrimaryLight</color>
<color name="color_ripple">#E9F1FF</color>
<color name="statusBarLight">@color/md_grey_300</color>
<color name="appBarLight">@color/md_grey_100</color>
<color name="backgroundLight">@color/md_grey_50</color>
<color name="dialogLight">@color/md_white_1000</color>
<color name="divider">@color/md_light_dividers</color>
<color name="selectorColorLight">@color/md_blue_400_38</color>
<color name="white">@color/md_white_1000</color>
<color name="primary_text">#000000</color>
<color name="secondary_text">#000000</color>
<color name="hint_text">#000000</color>
<color name="accent_text">@color/md_green_900</color>
<!-- Dark Theme -->
<color name="textColorPrimaryDark">@color/md_white_1000</color>
<color name="textColorSecondaryDark">@color/md_white_1000_70</color>
<color name="textColorHintDark">@color/md_white_1000_50</color>
<color name="dividerDark">@color/md_black_1000_12</color>
<color name="list_choice_pressed_bg_light">@color/colorPrimaryLight</color>
<color name="statusBarDark">@color/md_black_1000</color>
<color name="appBarDark">@color/md_grey_900</color>
<color name="backgroundDark">#303030</color>
<color name="dialogDark">@color/md_grey_800</color>
<color name="manga_unread_bg">@color/colorAccent</color>
<color name="manga_cover_title_background">#55333333</color>
<color name="selectorColorDark">@color/md_blue_400_50</color>
<color name="super_light_grey">@color/md_grey_50</color>
<color name="line_grey">@color/md_light_dividers</color>
<color name="light_grey">@color/md_grey_300</color>
<color name="page_number_background">#AAE9E9E9</color>
<color name="page_number_background_black">#99252525</color>
<color name="reader_menu_background">@color/colorPrimarySuperDark</color>
<!-- Text Colors -->
<color name="md_black_1000_87">#DE000000</color>
<color name="md_black_1000_54">#8A000000</color>
<color name="md_black_1000_38">#61000000</color>
<color name="md_black_1000_12">#1F000000</color>
<color name="md_light_dividers">@color/md_blue_400</color>
<color name="md_white_1000">#FFFFFFFF</color>
<color name="md_white_1000_70">#B3FFFFFF</color>
<color name="md_white_1000_50">#80FFFFFF</color>
<color name="md_white_1000_12">#1FFFFFFF</color>
<!-- google's material design colours from
http://www.google.com/design/spec/style/color.html#color-ui-color-palette -->
<!--reds-->
<array name="reds">
<item>@color/md_red_500</item>
<item>@color/md_red_50</item>
<item>@color/md_red_100</item>
<item>@color/md_red_200</item>
<item>@color/md_red_300</item>
<item>@color/md_red_400</item>
<item>@color/md_red_600</item>
<item>@color/md_red_700</item>
<item>@color/md_red_800</item>
<item>@color/md_red_900</item>
<item>@color/md_red_A100</item>
<item>@color/md_red_A200</item>
<item>@color/md_red_A400</item>
<item>@color/md_red_A700</item>
</array>
<color name="md_red_50">#ffebee</color>
<color name="md_red_100">#ffcdd2</color>
<color name="md_red_200">#ef9a9a</color>
<color name="md_red_300">#e57373</color>
<color name="md_red_400">#ef5350</color>
<color name="md_red_500">#f44336</color>
<color name="md_red_600">#e53935</color>
<color name="md_red_700">#d32f2f</color>
<color name="md_red_800">#c62828</color>
<color name="md_red_900">#b71c1c</color>
<color name="md_red_A100">#ff8a80</color>
<color name="md_red_A200">#ff5252</color>
<color name="md_red_A400">#ff1744</color>
<color name="md_red_A700">#d50000</color>
<color name="md_red_500_25">#40f44336</color>
<color name="md_red_500_50">#80f44336</color>
<color name="md_red_500_75">#c0f44336</color>
<!-- pinks -->
<array name="pinks">
<item>@color/md_pink_500</item>
<item>@color/md_pink_50</item>
<item>@color/md_pink_100</item>
<item>@color/md_pink_200</item>
<item>@color/md_pink_300</item>
<item>@color/md_pink_400</item>
<item>@color/md_pink_600</item>
<item>@color/md_pink_700</item>
<item>@color/md_pink_800</item>
<item>@color/md_pink_900</item>
<item>@color/md_pink_A100</item>
<item>@color/md_pink_A200</item>
<item>@color/md_pink_A400</item>
<item>@color/md_pink_A700</item>
</array>
<color name="md_pink_50">#fce4ec</color>
<color name="md_pink_100">#f8bbd0</color>
<color name="md_pink_200">#f48fb1</color>
<color name="md_pink_300">#f06292</color>
<color name="md_pink_400">#ec407a</color>
<color name="md_pink_500">#e91e63</color>
<color name="md_pink_600">#d81b60</color>
<color name="md_pink_700">#c2185b</color>
<color name="md_pink_800">#ad1457</color>
<color name="md_pink_900">#880e4f</color>
<color name="md_pink_A100">#ff80ab</color>
<color name="md_pink_A200">#ff4081</color>
<color name="md_pink_A400">#f50057</color>
<color name="md_pink_A700">#c51162</color>
<color name="md_pink_500_25">#40e91e63</color>
<color name="md_pink_500_50">#80e91e63</color>
<color name="md_pink_500_75">#c0e91e63</color>
<!-- purples -->
<array name="purples">
<item>@color/md_purple_500</item>
<item>@color/md_purple_50</item>
<item>@color/md_purple_100</item>
<item>@color/md_purple_200</item>
<item>@color/md_purple_300</item>
<item>@color/md_purple_400</item>
<item>@color/md_purple_600</item>
<item>@color/md_purple_700</item>
<item>@color/md_purple_800</item>
<item>@color/md_purple_900</item>
<item>@color/md_purple_A100</item>
<item>@color/md_purple_A200</item>
<item>@color/md_purple_A400</item>
<item>@color/md_purple_A700</item>
</array>
<color name="md_purple_50">#f3e5f5</color>
<color name="md_purple_100">#e1bee7</color>
<color name="md_purple_200">#ce93d8</color>
<color name="md_purple_300">#ba68c8</color>
<color name="md_purple_400">#ab47bc</color>
<color name="md_purple_500">#9c27b0</color>
<color name="md_purple_600">#8e24aa</color>
<color name="md_purple_700">#7b1fa2</color>
<color name="md_purple_800">#6a1b9a</color>
<color name="md_purple_900">#4a148c</color>
<color name="md_purple_A100">#ea80fc</color>
<color name="md_purple_A200">#e040fb</color>
<color name="md_purple_A400">#d500f9</color>
<color name="md_purple_A700">#aa00ff</color>
<color name="md_purple_500_25">#409c27b0</color>
<color name="md_purple_500_50">#809c27b0</color>
<color name="md_purple_500_75">#c09c27b0</color>
<!-- deep purples -->
<array name="deep_purples">
<item>@color/md_deep_purple_500</item>
<item>@color/md_deep_purple_50</item>
<item>@color/md_deep_purple_100</item>
<item>@color/md_deep_purple_200</item>
<item>@color/md_deep_purple_300</item>
<item>@color/md_deep_purple_400</item>
<item>@color/md_deep_purple_600</item>
<item>@color/md_deep_purple_700</item>
<item>@color/md_deep_purple_800</item>
<item>@color/md_deep_purple_900</item>
<item>@color/md_deep_purple_A100</item>
<item>@color/md_deep_purple_A200</item>
<item>@color/md_deep_purple_A400</item>
<item>@color/md_deep_purple_A700</item>
</array>
<color name="md_deep_purple_50">#ede7f6</color>
<color name="md_deep_purple_100">#d1c4e9</color>
<color name="md_deep_purple_200">#b39ddb</color>
<color name="md_deep_purple_300">#9575cd</color>
<color name="md_deep_purple_400">#7e57c2</color>
<color name="md_deep_purple_500">#673ab7</color>
<color name="md_deep_purple_600">#5e35b1</color>
<color name="md_deep_purple_700">#512da8</color>
<color name="md_deep_purple_800">#4527a0</color>
<color name="md_deep_purple_900">#311b92</color>
<color name="md_deep_purple_A100">#b388ff</color>
<color name="md_deep_purple_A200">#7c4dff</color>
<color name="md_deep_purple_A400">#651fff</color>
<color name="md_deep_purple_A700">#6200ea</color>
<color name="md_deep_purple_500_25">#40673ab7</color>
<color name="md_deep_purple_500_50">#80673ab7</color>
<color name="md_deep_purple_500_75">#c0673ab7</color>
<!-- indigos -->
<array name="indigos">
<item>@color/md_indigo_500</item>
<item>@color/md_indigo_50</item>
<item>@color/md_indigo_100</item>
<item>@color/md_indigo_200</item>
<item>@color/md_indigo_300</item>
<item>@color/md_indigo_400</item>
<item>@color/md_indigo_600</item>
<item>@color/md_indigo_700</item>
<item>@color/md_indigo_800</item>
<item>@color/md_indigo_900</item>
<item>@color/md_indigo_A100</item>
<item>@color/md_indigo_A200</item>
<item>@color/md_indigo_A400</item>
<item>@color/md_indigo_A700</item>
</array>
<color name="md_indigo_50">#e8eaf6</color>
<color name="md_indigo_100">#c5cae9</color>
<color name="md_indigo_200">#9fa8da</color>
<color name="md_indigo_300">#7986cb</color>
<color name="md_indigo_400">#5c6bc0</color>
<color name="md_indigo_500">#3f51b5</color>
<color name="md_indigo_600">#3949ab</color>
<color name="md_indigo_700">#303f9f</color>
<color name="md_indigo_800">#283593</color>
<color name="md_indigo_900">#1a237e</color>
<color name="md_indigo_A100">#8c9eff</color>
<color name="md_indigo_A200">#536dfe</color>
<color name="md_indigo_A400">#3d5afe</color>
<color name="md_indigo_A700">#304ffe</color>
<color name="md_indigo_500_25">#403f51b5</color>
<color name="md_indigo_500_50">#803f51b5</color>
<color name="md_indigo_500_75">#c03f51b5</color>
<!--blues-->
<array name="blues">
<item>@color/md_blue_500</item>
<item>@color/md_blue_50</item>
<item>@color/md_blue_100</item>
<item>@color/md_blue_200</item>
<item>@color/md_blue_300</item>
<item>@color/md_blue_400</item>
<item>@color/md_blue_600</item>
<item>@color/md_blue_700</item>
<item>@color/md_blue_800</item>
<item>@color/md_blue_900</item>
<item>@color/md_blue_A100</item>
<item>@color/md_blue_A200</item>
<item>@color/md_blue_A400</item>
<item>@color/md_blue_A700</item>
</array>
<color name="md_blue_50">#e3f2fd</color>
<color name="md_blue_100">#bbdefb</color>
<color name="md_blue_200">#90caf9</color>
<color name="md_blue_300">#64b5f6</color>
<color name="md_blue_400">#42a5f5</color>
<color name="md_blue_500">#2196f3</color>
<color name="md_blue_600">#1e88e5</color>
<color name="md_blue_700">#1976d2</color>
<color name="md_blue_800">#1565c0</color>
<color name="md_blue_900">#0d47a1</color>
<color name="md_blue_A100">#82b1ff</color>
<color name="md_blue_A200">#448aff</color>
<color name="md_blue_A400">#2979ff</color>
<color name="md_blue_A700">#2962ff</color>
<color name="md_blue_500_25">#402196f3</color>
<color name="md_blue_500_50">#802196f3</color>
<color name="md_blue_500_75">#c02196f3</color>
<!-- light blues-->
<array name="light_blues">
<item>@color/md_light_blue_500</item>
<item>@color/md_light_blue_50</item>
<item>@color/md_light_blue_100</item>
<item>@color/md_light_blue_200</item>
<item>@color/md_light_blue_300</item>
<item>@color/md_light_blue_400</item>
<item>@color/md_light_blue_600</item>
<item>@color/md_light_blue_700</item>
<item>@color/md_light_blue_800</item>
<item>@color/md_light_blue_900</item>
<item>@color/md_light_blue_A100</item>
<item>@color/md_light_blue_A200</item>
<item>@color/md_light_blue_A400</item>
<item>@color/md_light_blue_A700</item>
</array>
<color name="md_light_blue_50">#e1f5fe</color>
<color name="md_light_blue_100">#b3e5fc</color>
<color name="md_light_blue_200">#81d4fa</color>
<color name="md_light_blue_300">#4fc3f7</color>
<color name="md_light_blue_400">#29b6f6</color>
<color name="md_light_blue_500">#03a9f4</color>
<color name="md_light_blue_600">#039be5</color>
<color name="md_light_blue_700">#0288d1</color>
<color name="md_light_blue_800">#0277bd</color>
<color name="md_light_blue_900">#01579b</color>
<color name="md_light_blue_A100">#80d8ff</color>
<color name="md_light_blue_A200">#40c4ff</color>
<color name="md_light_blue_A400">#00b0ff</color>
<color name="md_light_blue_A700">#0091ea</color>
<color name="md_light_blue_500_25">#4003a9f4</color>
<color name="md_light_blue_500_50">#8003a9f4</color>
<color name="md_light_blue_500_75">#c003a9f4</color>
<!-- cyans -->
<array name="cyans">
<item>@color/md_cyan_500</item>
<item>@color/md_cyan_50</item>
<item>@color/md_cyan_100</item>
<item>@color/md_cyan_200</item>
<item>@color/md_cyan_300</item>
<item>@color/md_cyan_400</item>
<item>@color/md_cyan_600</item>
<item>@color/md_cyan_700</item>
<item>@color/md_cyan_800</item>
<item>@color/md_cyan_900</item>
<item>@color/md_cyan_A100</item>
<item>@color/md_cyan_A200</item>
<item>@color/md_cyan_A400</item>
<item>@color/md_cyan_A700</item>
</array>
<color name="md_cyan_50">#e0f7fa</color>
<color name="md_cyan_100">#b2ebf2</color>
<color name="md_cyan_200">#80deea</color>
<color name="md_cyan_300">#4dd0e1</color>
<color name="md_cyan_400">#26c6da</color>
<color name="md_cyan_500">#00bcd4</color>
<color name="md_cyan_600">#00acc1</color>
<color name="md_cyan_700">#0097a7</color>
<color name="md_cyan_800">#00838f</color>
<color name="md_cyan_900">#006064</color>
<color name="md_cyan_A100">#84ffff</color>
<color name="md_cyan_A200">#18ffff</color>
<color name="md_cyan_A400">#00e5ff</color>
<color name="md_cyan_A700">#00b8d4</color>
<color name="md_cyan_500_25">#4000bcd4</color>
<color name="md_cyan_500_50">#8000bcd4</color>
<color name="md_cyan_500_75">#c000bcd4</color>
<!-- teals -->
<array name="teals">
<item>@color/md_teal_500</item>
<item>@color/md_teal_50</item>
<item>@color/md_teal_100</item>
<item>@color/md_teal_200</item>
<item>@color/md_teal_300</item>
<item>@color/md_teal_400</item>
<item>@color/md_teal_600</item>
<item>@color/md_teal_700</item>
<item>@color/md_teal_800</item>
<item>@color/md_teal_900</item>
<item>@color/md_teal_A100</item>
<item>@color/md_teal_A200</item>
<item>@color/md_teal_A400</item>
<item>@color/md_teal_A700</item>
</array>
<color name="md_teal_50">#e0f2f1</color>
<color name="md_teal_100">#b2dfdb</color>
<color name="md_teal_200">#80cbc4</color>
<color name="md_teal_300">#4db6ac</color>
<color name="md_teal_400">#26a69a</color>
<color name="md_teal_500">#009688</color>
<color name="md_teal_600">#00897b</color>
<color name="md_teal_700">#00796b</color>
<color name="md_teal_800">#00695c</color>
<color name="md_teal_900">#004d40</color>
<color name="md_teal_A100">#a7ffeb</color>
<color name="md_teal_A200">#64ffda</color>
<color name="md_teal_A400">#1de9b6</color>
<color name="md_teal_A700">#00bfa5</color>
<color name="md_teal_500_25">#40009688</color>
<color name="md_teal_500_50">#80009688</color>
<color name="md_teal_500_75">#c8009688</color>
<!-- greens -->
<array name="greens">
<item>@color/md_green_500</item>
<item>@color/md_green_50</item>
<item>@color/md_green_100</item>
<item>@color/md_green_200</item>
<item>@color/md_green_300</item>
<item>@color/md_green_400</item>
<item>@color/md_green_600</item>
<item>@color/md_green_700</item>
<item>@color/md_green_800</item>
<item>@color/md_green_900</item>
<item>@color/md_green_A100</item>
<item>@color/md_green_A200</item>
<item>@color/md_green_A400</item>
<item>@color/md_green_A700</item>
</array>
<color name="md_green_50">#e8f5e9</color>
<color name="md_green_100">#c8e6c9</color>
<color name="md_green_200">#a5d6a7</color>
<color name="md_green_300">#81c784</color>
<color name="md_green_400">#66bb6a</color>
<color name="md_green_500">#4caf50</color>
<color name="md_green_600">#43a047</color>
<color name="md_green_700">#388e3c</color>
<color name="md_green_800">#2e7d32</color>
<color name="md_green_900">#1b5e20</color>
<color name="md_green_A100">#b9f6ca</color>
<color name="md_green_A200">#69f0ae</color>
<color name="md_green_A400">#00e676</color>
<color name="md_green_A700">#00c853</color>
<color name="md_green_500_25">#404caf50</color>
<color name="md_green_500_50">#804caf50</color>
<color name="md_green_500_75">#c04caf50</color>
<!--light greens-->
<array name="light_greens">
<item>@color/md_light_green_500</item>
<item>@color/md_light_green_50</item>
<item>@color/md_light_green_100</item>
<item>@color/md_light_green_200</item>
<item>@color/md_light_green_300</item>
<item>@color/md_light_green_400</item>
<item>@color/md_light_green_600</item>
<item>@color/md_light_green_700</item>
<item>@color/md_light_green_800</item>
<item>@color/md_light_green_900</item>
<item>@color/md_light_green_A100</item>
<item>@color/md_light_green_A200</item>
<item>@color/md_light_green_A400</item>
<item>@color/md_light_green_A700</item>
</array>
<color name="md_light_green_50">#f1f8e9</color>
<color name="md_light_green_100">#dcedc8</color>
<color name="md_light_green_200">#c5e1a5</color>
<color name="md_light_green_300">#aed581</color>
<color name="md_light_green_400">#9ccc65</color>
<color name="md_light_green_500">#8bc34a</color>
<color name="md_light_green_600">#7cb342</color>
<color name="md_light_green_700">#689f38</color>
<color name="md_light_green_800">#558b2f</color>
<color name="md_light_green_900">#33691e</color>
<color name="md_light_green_A100">#ccff90</color>
<color name="md_light_green_A200">#b2ff59</color>
<color name="md_light_green_A400">#76ff03</color>
<color name="md_light_green_A700">#64dd17</color>
<color name="md_light_green_500_25">#408bc34a</color>
<color name="md_light_green_500_50">#808bc34a</color>
<color name="md_light_green_500_75">#c88bc34a</color>
<!-- limes -->
<array name="limes">
<item>@color/md_lime_500</item>
<item>@color/md_lime_50</item>
<item>@color/md_lime_100</item>
<item>@color/md_lime_200</item>
<item>@color/md_lime_300</item>
<item>@color/md_lime_400</item>
<item>@color/md_lime_600</item>
<item>@color/md_lime_700</item>
<item>@color/md_lime_800</item>
<item>@color/md_lime_900</item>
<item>@color/md_lime_A100</item>
<item>@color/md_lime_A200</item>
<item>@color/md_lime_A400</item>
<item>@color/md_lime_A700</item>
</array>
<color name="md_lime_50">#f9fbe7</color>
<color name="md_lime_100">#f0f4c3</color>
<color name="md_lime_200">#e6ee9c</color>
<color name="md_lime_300">#dce775</color>
<color name="md_lime_400">#d4e157</color>
<color name="md_lime_500">#cddc39</color>
<color name="md_lime_600">#c0ca33</color>
<color name="md_lime_700">#afb42b</color>
<color name="md_lime_800">#9e9d24</color>
<color name="md_lime_900">#827717</color>
<color name="md_lime_A100">#f4ff81</color>
<color name="md_lime_A200">#eeff41</color>
<color name="md_lime_A400">#c6ff00</color>
<color name="md_lime_A700">#aeea00</color>
<color name="md_lime_500_25">#40cddc39</color>
<color name="md_lime_500_50">#80cddc39</color>
<color name="md_lime_500_75">#c0cddc39</color>
<!--yellows -->
<array name="yellows">
<item>@color/md_yellow_500</item>
<item>@color/md_yellow_50</item>
<item>@color/md_yellow_100</item>
<item>@color/md_yellow_200</item>
<item>@color/md_yellow_300</item>
<item>@color/md_yellow_400</item>
<item>@color/md_yellow_600</item>
<item>@color/md_yellow_700</item>
<item>@color/md_yellow_800</item>
<item>@color/md_yellow_900</item>
<item>@color/md_yellow_A100</item>
<item>@color/md_yellow_A200</item>
<item>@color/md_yellow_A400</item>
<item>@color/md_yellow_A700</item>
</array>
<color name="md_yellow_50">#fffde7</color>
<color name="md_yellow_100">#fff9c4</color>
<color name="md_yellow_200">#fff59d</color>
<color name="md_yellow_300">#fff176</color>
<color name="md_yellow_400">#ffee58</color>
<color name="md_yellow_500">#ffeb3b</color>
<color name="md_yellow_600">#fdd835</color>
<color name="md_yellow_700">#fbc02d</color>
<color name="md_yellow_800">#f9a825</color>
<color name="md_yellow_900">#f57f17</color>
<color name="md_yellow_A100">#ffff8d</color>
<color name="md_yellow_A200">#ffff00</color>
<color name="md_yellow_A400">#ffea00</color>
<color name="md_yellow_A700">#ffd600</color>
<color name="md_yellow_500_25">#40ffeb3b</color>
<color name="md_yellow_500_50">#80ffeb3b</color>
<color name="md_yellow_500_75">#c0ffeb3b</color>
<!-- ambers -->
<array name="ambers">
<item>@color/md_amber_500</item>
<item>@color/md_amber_50</item>
<item>@color/md_amber_100</item>
<item>@color/md_amber_200</item>
<item>@color/md_amber_300</item>
<item>@color/md_amber_400</item>
<item>@color/md_amber_600</item>
<item>@color/md_amber_700</item>
<item>@color/md_amber_800</item>
<item>@color/md_amber_900</item>
<item>@color/md_amber_A100</item>
<item>@color/md_amber_A200</item>
<item>@color/md_amber_A400</item>
<item>@color/md_amber_A700</item>
</array>
<color name="md_amber_50">#fff8e1</color>
<color name="md_amber_100">#ffecb3</color>
<color name="md_amber_200">#ffe082</color>
<color name="md_amber_300">#ffd54f</color>
<color name="md_amber_400">#ffca28</color>
<color name="md_amber_500">#ffc107</color>
<color name="md_amber_600">#ffb300</color>
<color name="md_amber_700">#ffa000</color>
<color name="md_amber_800">#ff8f00</color>
<color name="md_amber_900">#ff6f00</color>
<color name="md_amber_A100">#ffe57f</color>
<color name="md_amber_A200">#ffd740</color>
<color name="md_amber_A400">#ffc400</color>
<color name="md_amber_A700">#ffab00</color>
<color name="md_amber_500_25">#40ffc107</color>
<color name="md_amber_500_50">#80ffc107</color>
<color name="md_amber_500_75">#c0ffc107</color>
<!-- oranges -->
<array name="oranges">
<item>@color/md_orange_500</item>
<item>@color/md_orange_50</item>
<item>@color/md_orange_100</item>
<item>@color/md_orange_200</item>
<item>@color/md_orange_300</item>
<item>@color/md_orange_400</item>
<item>@color/md_orange_600</item>
<item>@color/md_orange_700</item>
<item>@color/md_orange_800</item>
<item>@color/md_orange_900</item>
<item>@color/md_orange_A100</item>
<item>@color/md_orange_A200</item>
<item>@color/md_orange_A400</item>
<item>@color/md_orange_A700</item>
</array>
<color name="md_orange_50">#fff3e0</color>
<color name="md_orange_100">#ffe0b2</color>
<color name="md_orange_200">#ffcc80</color>
<color name="md_orange_300">#ffb74d</color>
<color name="md_orange_400">#ffa726</color>
<color name="md_orange_500">#ff9800</color>
<color name="md_orange_600">#fb8c00</color>
<color name="md_orange_700">#f57c00</color>
<color name="md_orange_800">#ef6c00</color>
<color name="md_orange_900">#e65100</color>
<color name="md_orange_A100">#ffd180</color>
<color name="md_orange_A200">#ffab40</color>
<color name="md_orange_A400">#ff9100</color>
<color name="md_orange_A700">#ff6d00</color>
<color name="md_orange_500_25">#40ff9800</color>
<color name="md_orange_500_50">#80ff9800</color>
<color name="md_orange_500_75">#c0ff9800</color>
<!-- deep oranges -->
<array name="deep_oranges">
<item>@color/md_deep_orange_500</item>
<item>@color/md_deep_orange_50</item>
<item>@color/md_deep_orange_100</item>
<item>@color/md_deep_orange_200</item>
<item>@color/md_deep_orange_300</item>
<item>@color/md_deep_orange_400</item>
<item>@color/md_deep_orange_600</item>
<item>@color/md_deep_orange_700</item>
<item>@color/md_deep_orange_800</item>
<item>@color/md_deep_orange_900</item>
<item>@color/md_deep_orange_A100</item>
<item>@color/md_deep_orange_A200</item>
<item>@color/md_deep_orange_A400</item>
<item>@color/md_deep_orange_A700</item>
</array>
<color name="md_deep_orange_50">#fbe9e7</color>
<color name="md_deep_orange_100">#ffccbc</color>
<color name="md_deep_orange_200">#ffab91</color>
<color name="md_deep_orange_300">#ff8a65</color>
<color name="md_deep_orange_400">#ff7043</color>
<color name="md_deep_orange_500">#ff5722</color>
<color name="md_deep_orange_600">#f4511e</color>
<color name="md_deep_orange_700">#e64a19</color>
<color name="md_deep_orange_800">#d84315</color>
<color name="md_deep_orange_900">#bf360c</color>
<color name="md_deep_orange_A100">#ff9e80</color>
<color name="md_deep_orange_A200">#ff6e40</color>
<color name="md_deep_orange_A400">#ff3d00</color>
<color name="md_deep_orange_A700">#dd2c00</color>
<color name="md_deep_orange_500_25">#40ff5722</color>
<color name="md_deep_orange_500_50">#80ff5722</color>
<color name="md_deep_orange_500_75">#c0ff5722</color>
<!-- browns -->
<array name="browns">
<item>@color/md_brown_500</item>
<item>@color/md_brown_50</item>
<item>@color/md_brown_100</item>
<item>@color/md_brown_200</item>
<item>@color/md_brown_300</item>
<item>@color/md_brown_400</item>
<item>@color/md_brown_600</item>
<item>@color/md_brown_700</item>
<item>@color/md_brown_800</item>
<item>@color/md_brown_900</item>
</array>
<color name="md_brown_50">#efebe9</color>
<color name="md_brown_100">#d7ccc8</color>
<color name="md_brown_200">#bcaaa4</color>
<color name="md_brown_300">#a1887f</color>
<color name="md_brown_400">#8d6e63</color>
<color name="md_brown_500">#795548</color>
<color name="md_brown_600">#6d4c41</color>
<color name="md_brown_700">#5d4037</color>
<color name="md_brown_800">#4e342e</color>
<color name="md_brown_900">#3e2723</color>
<color name="md_brown_500_25">#40795548</color>
<color name="md_brown_500_50">#80795548</color>
<color name="md_brown_500_75">#c0795548</color>
<!--grey-->
<array name="greys">
<item>@color/md_grey_500</item>
<item>@color/md_grey_50</item>
<item>@color/md_grey_100</item>
<item>@color/md_grey_200</item>
<item>@color/md_grey_300</item>
<item>@color/md_grey_400</item>
<item>@color/md_grey_600</item>
<item>@color/md_grey_700</item>
<item>@color/md_grey_800</item>
<item>@color/md_grey_900</item>
<item>@color/md_black_1000</item>
<item>@color/md_white_1000</item>
</array>
<color name="md_grey_50">#fafafa</color>
<color name="md_grey_100">#f5f5f5</color>
<color name="md_grey_200">#eeeeee</color>
<color name="md_grey_300">#e0e0e0</color>
<color name="md_grey_400">#bdbdbd</color>
<color name="md_grey_500">#9e9e9e</color>
<color name="md_grey_600">#757575</color>
<color name="md_grey_700">#616161</color>
<!-- Material Design Colors -->
<color name="md_black_1000">#000000</color>
<color name="md_grey_50">#FAFAFA</color>
<color name="md_grey_100">#F5F5F5</color>
<color name="md_grey_300">#E0E0E0</color>
<color name="md_grey_800">#424242</color>
<color name="md_grey_900">#212121</color>
<color name="md_grey_500_25">#409e9e9e</color>
<color name="md_grey_500_50">#809e9e9e</color>
<color name="md_grey_500_75">#c09e9e9e</color>
<color name="md_white_1000">#ffffff</color>
<color name="md_white_1000_10">#1affffff</color>
<color name="md_white_1000_15">#22ffffff</color>
<color name="md_white_1000_20">#33ffffff</color>
<color name="md_white_1000_25">#40ffffff</color>
<color name="md_white_1000_50">#80ffffff</color>
<color name="md_white_1000_60">#99ffffff</color>
<color name="md_white_1000_75">#c0ffffff</color>
<color name="md_black_1000_10">#1a000000</color>
<color name="md_black_1000_15">#26000000</color>
<color name="md_black_1000_20">#33000000</color>
<color name="md_black_1000_25">#40000000</color>
<color name="md_black_1000_50">#80000000</color>
<color name="md_black_1000_75">#c0000000</color>
<color name="md_black_1000">#000000</color>
<!--blue grey-->
<array name="blue_greys">
<item>@color/md_blue_grey_500</item>
<item>@color/md_blue_grey_50</item>
<item>@color/md_blue_grey_100</item>
<item>@color/md_blue_grey_200</item>
<item>@color/md_blue_grey_300</item>
<item>@color/md_blue_grey_400</item>
<item>@color/md_blue_grey_600</item>
<item>@color/md_blue_grey_700</item>
<item>@color/md_blue_grey_800</item>
<item>@color/md_blue_grey_900</item>
</array>
<color name="md_blue_grey_50">#eceff1</color>
<color name="md_blue_grey_100">#cfd8dc</color>
<color name="md_blue_grey_200">#b0bec5</color>
<color name="md_blue_grey_300">#90a4ae</color>
<color name="md_blue_grey_400">#78909c</color>
<color name="md_blue_grey_500">#607d8b</color>
<color name="md_blue_grey_600">#546e7a</color>
<color name="md_blue_grey_700">#455a64</color>
<color name="md_blue_grey_800">#37474f</color>
<color name="md_blue_grey_900">#263238</color>
<color name="md_blue_grey_500_25">#40607d8b</color>
<color name="md_blue_grey_500_50">#80607d8b</color>
<color name="md_blue_grey_500_75">#c0607d8b</color>
<!--Texts-->
<array name="texts_white">
<item>@color/md_text_white</item>
<item>@color/md_text_white_87</item>
<item>@color/md_secondary_text_icons_white</item>
<item>@color/md_disabled_hint_text_white</item>
<item>@color/md_divider_white</item>
</array>
<color name="md_text_white">#ffffffff</color>
<color name="md_text_white_87">#dfffffff</color>
<color name="md_secondary_text_icons_white">#b3ffffff</color>
<color name="md_disabled_hint_text_white">#4dffffff</color>
<color name="md_divider_white">#1fffffff</color>
<array name="texts_black">
<item>@color/md_text</item>
<item>@color/md_secondary_text_icons</item>
<item>@color/md_disabled_hint_text</item>
<item>@color/md_divider</item>
</array>
<color name="md_text">#df000000</color>
<color name="md_secondary_text_icons">#8a000000</color>
<color name="md_disabled_hint_text">#4c000000</color>
<color name="md_divider">#1f000000</color>
<!--Falcon-->
<array name="falcon">
<item>@color/md_falcon_400</item>
<item>@color/md_falcon_500</item>
<item>@color/md_falcon_700</item>
</array>
<color name="md_falcon_400">#ff38628b</color>
<color name="md_falcon_500">#384e77</color>
<color name="md_falcon_700">#2b3e5f</color>
<color name="md_falcon_A400">#598bae</color>
<color name="md_falcon_500_25">#40384e77</color>
<color name="md_falcon_500_50">#80384e77</color>
<color name="md_falcon_500_75">#c0384e77</color>
<color name="md_falcon_A400_25">#40598bae</color>
<color name="md_blue_400">#42A5F5</color>
<color name="md_blue_400_50">#8042A5F5</color>
<color name="md_blue_400_38">#6142A5F5</color>
<color name="md_red_100">#FFCDD2</color>
<color name="md_statusbar_translucent">#4000</color>

View File

@ -1,16 +1,16 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">0dp</dimen>
<dimen name="activity_vertical_margin">0dp</dimen>
<dimen name="margin_top">0dp</dimen>
<dimen name="margin_bottom">0dp</dimen>
<dimen name="margin_left">0dp</dimen>
<dimen name="margin_right">0dp</dimen>
<dimen name="fab_margin">0dp</dimen>
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="margin_top">16dp</dimen>
<dimen name="margin_bottom">16dp</dimen>
<dimen name="margin_left">16dp</dimen>
<dimen name="margin_right">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="fab_size">56dp</dimen>
<dimen name="dialog_content_padding">0dp</dimen>
<dimen name="dialog_margin_top_content">0dp</dimen>
<dimen name="dialog_content_padding">24dp</dimen>
<dimen name="dialog_margin_top_content">20dp</dimen>
<dimen name="text_headline">24sp</dimen>
<dimen name="text_large_title">22sp</dimen>

View File

@ -15,6 +15,7 @@
<string name="pref_update_only_non_completed_key">pref_update_only_non_completed_key</string>
<string name="pref_auto_update_manga_sync_key">pref_auto_update_manga_sync_key</string>
<string name="pref_ask_update_manga_sync_key">pref_ask_update_manga_sync_key</string>
<string name="pref_theme_key">pref_theme_key</string>
<string name="pref_default_viewer_key">pref_default_viewer_key</string>
<string name="pref_image_scale_type_key">pref_image_scale_type_key</string>

Some files were not shown because too many files have changed in this diff Show More