Added the ability to view the library as a list (#394)

* Added in the ability to view the library as a list

* reverted LibraryAdapter and renamed libraryToggleViewEvent to LibraryToggleViewEvent for consistency

* removed LibraryToggleViewEvent and directly subscribed to option change

* fixed the toggleView subscription

* Made the library list item layout more compliant with material design

* Changed unread text style and removed background
This commit is contained in:
Josh 2016-07-27 10:37:36 -05:00 committed by Bram van de Kerkhof
parent 74e3d387eb
commit f21a030cf8
16 changed files with 313 additions and 44 deletions

View file

@ -164,6 +164,7 @@ dependencies {
compile 'net.xpece.android:support-preference:0.8.1'
compile 'me.zhanghai.android.systemuihelper:library:1.0.0'
compile 'org.adw.library:discrete-seekbar:1.0.1'
compile 'de.hdodenhof:circleimageview:2.1.0'
// Tests
testCompile 'junit:junit:4.12'

View file

@ -92,4 +92,6 @@ class PreferenceKeys(context: Context) {
fun syncPassword(syncId: Int) = "pref_mangasync_password_$syncId"
val libraryAsList = context.getString(R.string.pref_display_library_as_list)
}

View file

@ -126,6 +126,8 @@ class PreferencesHelper(context: Context) {
fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet())
fun libraryAsList() = rxPrefs.getBoolean(keys.libraryAsList, false)
fun filterDownloaded() = rxPrefs.getBoolean(keys.filterDownloaded, false)
fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false)

View file

@ -84,11 +84,19 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) :
* @return a new view holder for a manga.
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LibraryHolder {
val view = parent.inflate(R.layout.item_catalogue_grid).apply {
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
gradient.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM)
//depending on preferences, display a list or display a grid
if(parent.id == R.id.library_list) {
val view = parent.inflate(R.layout.item_library_list)
return LibraryListHolder(view, this, fragment)
} else {
val view = parent.inflate(R.layout.item_catalogue_grid).apply {
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
gradient.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM)
}
return LibraryGridHolder(view, this, fragment)
}
return LibraryHolder(view, this, fragment)
}
/**

View file

@ -7,17 +7,22 @@ import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import com.f2prateek.rx.preferences.Preference
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
import eu.kanade.tachiyomi.ui.manga.MangaActivity
import eu.kanade.tachiyomi.util.toast
import kotlinx.android.synthetic.main.fragment_catalogue.*
import kotlinx.android.synthetic.main.fragment_library.*
import kotlinx.android.synthetic.main.fragment_library_category.*
import rx.Subscription
import uy.kohesive.injekt.injectLazy
/**
* Fragment containing the library manga for a certain category.
@ -25,6 +30,11 @@ import rx.Subscription
*/
class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemClickListener {
/**
* Preferences.
*/
val preferences: PreferencesHelper by injectLazy()
/**
* Adapter to hold the manga in this category.
*/
@ -46,11 +56,21 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
*/
private var numColumnsSubscription: Subscription? = null
/**
* subscription to view toggle
*/
private var toggleViewSubscription: Subscription? = null
/**
* Subscription of the library search.
*/
private var searchSubscription: Subscription? = null
/**
* display mode
*/
private var displayAsList: Boolean = false;
companion object {
/**
* Key to save and restore [position] from a [Bundle].
@ -66,19 +86,29 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
fun newInstance(position: Int): LibraryCategoryFragment {
val fragment = LibraryCategoryFragment()
fragment.position = position
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_library_category, container, false)
}
override fun onViewCreated(view: View, savedState: Bundle?) {
adapter = LibraryCategoryAdapter(this)
//set up grid
recycler.setHasFixedSize(true)
recycler.adapter = adapter
//set up list
library_list.setHasFixedSize(true)
library_list.adapter = adapter
library_list.layoutManager = LinearLayoutManager(activity)
if (libraryFragment.actionMode != null) {
setSelectionMode(FlexibleAdapter.MODE_MULTI)
}
@ -94,6 +124,17 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
adapter.updateDataSet()
}
toggleViewSubscription = preferences.libraryAsList().asObservable().subscribe {onViewModeChange(it)}
if(libraryPresenter.displayAsList != displayAsList) {
library_switcher.showNext()
displayAsList = libraryPresenter.displayAsList
}
library_switcher.inAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_in)
library_switcher.outAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_out)
if (savedState != null) {
position = savedState.getInt(POSITION_KEY)
adapter.onRestoreInstanceState(savedState)
@ -129,13 +170,17 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
override fun onDestroyView() {
numColumnsSubscription?.unsubscribe()
searchSubscription?.unsubscribe()
toggleViewSubscription?.unsubscribe()
super.onDestroyView()
}
override fun onResume() {
super.onResume()
libraryMangaSubscription = libraryPresenter.libraryMangaSubject
.subscribe { onNextLibraryManga(it) }
}
override fun onPause() {
@ -211,6 +256,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
startActivity(intent)
}
/**
* Toggles the selection for a manga.
*
@ -262,6 +308,15 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
}
}
fun onViewModeChange(isList: Boolean) {
//do nothing if the display does not need to change
if(isList == displayAsList) return
//else change view and display mode
library_switcher.showNext()
displayAsList = isList
}
/**
* Property to get the library fragment.
*/

View file

@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.category.CategoryActivity
@ -22,6 +23,7 @@ import eu.kanade.tachiyomi.util.toast
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_library.*
import nucleus.factory.RequiresPresenter
import uy.kohesive.injekt.injectLazy
import java.io.IOException
/**
@ -37,6 +39,11 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
lateinit var adapter: LibraryAdapter
private set
/**
* Preferences.
*/
val preferences: PreferencesHelper by injectLazy()
/**
* TabLayout of the categories.
*/
@ -53,6 +60,11 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
*/
private var query: String? = null
/**
* Display mode of the library (list or grid mode).
*/
private var displayMode: MenuItem? = null
/**
* Action mode for manga selection.
*/
@ -178,6 +190,18 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
return true
}
})
//set the icon for the display mode button
displayMode = menu.findItem(R.id.action_library_display_mode).apply {
val icon = if (preferences.libraryAsList().getOrDefault())
R.drawable.ic_view_module_white_24dp
else
R.drawable.ic_view_list_white_24dp
setIcon(icon)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -208,6 +232,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
// Apply filter
onFilterCheckboxChanged()
}
R.id.action_library_display_mode -> swapDisplayMode()
R.id.action_update_library -> {
LibraryUpdateService.start(activity, true)
}
@ -231,6 +256,23 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
activity.supportInvalidateOptionsMenu()
}
/**
* swap display mode
*/
private fun swapDisplayMode() {
presenter.swapDisplayMode()
val isListMode = presenter.displayAsList
val icon = if (isListMode)
R.drawable.ic_view_module_white_24dp
else
R.drawable.ic_view_list_white_24dp
displayMode?.setIcon(icon)
}
/**
* Updates the query.
*

View file

@ -0,0 +1,49 @@
package eu.kanade.tachiyomi.ui.library
import android.view.View
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
/**
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
* All the elements from the layout file "item_catalogue_grid" are available in this class.
*
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
* @param listener a listener to react to single tap and long tap events.
* @constructor creates a new library holder.
*/
class LibraryGridHolder(private val view: View,
private val adapter: LibraryCategoryAdapter,
listener: FlexibleViewHolder.OnListItemClickListener)
: LibraryHolder(view, adapter, listener) {
/**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga.
*
* @param manga the manga to bind.
*/
override fun onSetValues(manga: Manga) {
// Update the title of the manga.
view.title.text = manga.title
// Update the unread count and its visibility.
with(view.unread_text) {
visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString()
}
// Update the cover.
Glide.clear(view.thumbnail)
Glide.with(view.context)
.load(manga)
.diskCacheStrategy(DiskCacheStrategy.RESULT)
.centerCrop()
.into(view.thumbnail)
}
}

View file

@ -8,18 +8,14 @@ import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
/**
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
* All the elements from the layout file "item_catalogue_grid" are available in this class.
*
* Generic class used to hold the displayed data of a manga in the library.
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
* @param listener a listener to react to single tap and long tap events.
* @constructor creates a new library holder.
* @param listener a listener to react to the single tap and long tap events.
*/
class LibraryHolder(private val view: View,
private val adapter: LibraryCategoryAdapter,
listener: FlexibleViewHolder.OnListItemClickListener)
: FlexibleViewHolder(view, adapter, listener) {
abstract class LibraryHolder(private val view: View, adapter: LibraryCategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener)
: FlexibleViewHolder(view, adapter, listener) {
/**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
@ -27,23 +23,6 @@ class LibraryHolder(private val view: View,
*
* @param manga the manga to bind.
*/
fun onSetValues(manga: Manga) {
// Update the title of the manga.
view.title.text = manga.title
// Update the unread count and its visibility.
with(view.unread_text) {
visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString()
}
// Update the cover.
Glide.clear(view.thumbnail)
Glide.with(view.context)
.load(manga)
.diskCacheStrategy(DiskCacheStrategy.RESULT)
.centerCrop()
.into(view.thumbnail)
}
abstract fun onSetValues(manga: Manga)
}

View file

@ -0,0 +1,53 @@
package eu.kanade.tachiyomi.ui.library
import android.view.View
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import kotlinx.android.synthetic.main.item_library_list.view.*
/**
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
* All the elements from the layout file "item_library_list" are available in this class.
*
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
* @param listener a listener to react to single tap and long tap events.
* @constructor creates a new library holder.
*/
class LibraryListHolder(private val view: View,
private val adapter: LibraryCategoryAdapter,
listener: FlexibleViewHolder.OnListItemClickListener)
: LibraryHolder(view, adapter, listener) {
/**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga.
*
* @param manga the manga to bind.
*/
override fun onSetValues(manga: Manga) {
// Update the title of the manga.
view.title.text = manga.title
// Update the unread count and its visibility.
with(view.unread_text) {
visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString()
}
// Update the cover.
Glide.clear(view.thumbnail)
Glide.with(view.context)
.load(manga)
.diskCacheStrategy(DiskCacheStrategy.RESULT)
.centerCrop()
.dontAnimate()
.into(view.thumbnail)
}
}

View file

@ -71,6 +71,12 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
*/
val downloadManager: DownloadManager by injectLazy()
/**
* display the library as a list?
*/
var displayAsList: Boolean = false
private set
companion object {
/**
* Id of the restartable that listens for library updates.
@ -89,6 +95,18 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
start(GET_LIBRARY)
}
add(preferences.libraryAsList().asObservable().subscribe{setDisplayMode(it)})
}
/**
* Sets the display mode
*
* @param asList display as list or not
*/
fun setDisplayMode(asList: Boolean) {
displayAsList = asList
}
/**
@ -285,4 +303,12 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
return false
}
/**
* Changes the active display mode.
*/
fun swapDisplayMode() {
var currentMode: Boolean = displayAsList
preferences.libraryAsList().set(!displayAsList)
}
}

View file

@ -3,11 +3,9 @@
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View file

@ -9,14 +9,27 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
android:id="@+id/recycler"
style="@style/Theme.Widget.GridView"
<ViewSwitcher
android:id="@+id/library_switcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnWidth="140dp"
tools:listitem="@layout/item_catalogue_grid"/>
android:layout_height="0dp"
android:layout_weight="1">
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
android:id="@+id/recycler"
style="@style/Theme.Widget.GridView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnWidth="140dp"
tools:listitem="@layout/item_catalogue_grid"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/library_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_library_list"/>
</ViewSwitcher>
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:background="?attr/selectable_list_drawable">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/thumbnail"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/icon"
android:layout_gravity="center_vertical|left"
android:paddingLeft="6dp"/>
<TextView
android:id="@+id/title"
style="@style/TextAppearance.Regular.Body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="38dp"
android:text="Sample name"/>
<TextView
android:id="@+id/unread_text"
style="@style/TextAppearance.Regular.Caption"
android:textColor="@color/material_grey_500"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:paddingRight="9dp"
android:visibility="gone"/>
</FrameLayout>

View file

@ -29,6 +29,11 @@
app:showAsAction="collapseActionView|ifRoom"
app:actionViewClass="android.support.v7.widget.SearchView" />
<item
android:id="@+id/action_library_display_mode"
android:title="Display Mode"
app:showAsAction="always"/>
<item
android:id="@+id/action_update_library"
android:title="@string/action_update_library"

View file

@ -9,6 +9,7 @@
<string name="pref_category_about_key">pref_category_about_key</string>
<string name="pref_category_sources_key">pref_category_sources_key</string>
<string name="pref_display_library_as_list">pref_display_library_as_list</string>
<string name="pref_library_columns_dialog_key">pref_library_columns_dialog_key</string>
<string name="pref_library_columns_portrait_key">pref_library_columns_portrait_key</string>
<string name="pref_library_columns_landscape_key">pref_library_columns_landscape_key</string>