Filter
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.core.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class MangaFilter(
|
||||
val sortOrder: SortOrder,
|
||||
val tag: MangaTag?
|
||||
) : Parcelable
|
||||
@@ -1,5 +1,12 @@
|
||||
package org.koitharu.kotatsu.core.model
|
||||
|
||||
enum class SortOrder {
|
||||
ALPHABETICAL, POPULARITY, UPDATED, NEWEST, RATING
|
||||
import androidx.annotation.StringRes
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
enum class SortOrder(@StringRes val titleRes: Int) {
|
||||
UPDATED(R.string.updated),
|
||||
POPULARITY(R.string.popular),
|
||||
RATING(R.string.by_rating),
|
||||
NEWEST(R.string.newest),
|
||||
ALPHABETICAL(R.string.by_name)
|
||||
}
|
||||
@@ -15,8 +15,9 @@ abstract class GroupleRepository(
|
||||
protected abstract val domain: String
|
||||
|
||||
override val sortOrders = setOf(
|
||||
SortOrder.ALPHABETICAL, SortOrder.POPULARITY,
|
||||
SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.RATING
|
||||
SortOrder.UPDATED, SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST, SortOrder.RATING
|
||||
//FIXME SortOrder.ALPHABETICAL
|
||||
)
|
||||
|
||||
override suspend fun getList(
|
||||
@@ -26,9 +27,16 @@ abstract class GroupleRepository(
|
||||
tag: MangaTag?
|
||||
): List<Manga> {
|
||||
val doc = when {
|
||||
!query.isNullOrEmpty() -> loaderContext.post("https://$domain/search", mapOf("q" to query))
|
||||
!query.isNullOrEmpty() -> loaderContext.post(
|
||||
"https://$domain/search",
|
||||
mapOf("q" to query)
|
||||
)
|
||||
tag == null -> loaderContext.get("https://$domain/list?sortType=${getSortKey(sortOrder)}&offset=$offset")
|
||||
else -> loaderContext.get( "https://$domain/list/genre/${tag.key}?sortType=${getSortKey(sortOrder)}&offset=$offset")
|
||||
else -> loaderContext.get(
|
||||
"https://$domain/list/genre/${tag.key}?sortType=${getSortKey(
|
||||
sortOrder
|
||||
)}&offset=$offset"
|
||||
)
|
||||
}.parseHtml()
|
||||
val root = doc.body().getElementById("mangaBox")
|
||||
?.selectFirst("div.tiles.row") ?: throw ParseException("Cannot find root")
|
||||
@@ -128,19 +136,20 @@ abstract class GroupleRepository(
|
||||
.selectFirst("table.table")
|
||||
return root.select("a.element-link").map { a ->
|
||||
MangaTag(
|
||||
title = a.text(),
|
||||
title = a.text().capitalize(),
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
source = source
|
||||
)
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private fun getSortKey(sortOrder: SortOrder?) = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "name"
|
||||
SortOrder.POPULARITY -> "rate"
|
||||
SortOrder.UPDATED -> "updated"
|
||||
SortOrder.NEWEST -> "created"
|
||||
SortOrder.RATING -> "votes"
|
||||
null -> "updated"
|
||||
}
|
||||
private fun getSortKey(sortOrder: SortOrder?) =
|
||||
when (sortOrder ?: sortOrders.minBy { it.ordinal }) {
|
||||
SortOrder.ALPHABETICAL -> "name"
|
||||
SortOrder.POPULARITY -> "rate"
|
||||
SortOrder.UPDATED -> "updated"
|
||||
SortOrder.NEWEST -> "created"
|
||||
SortOrder.RATING -> "votes"
|
||||
null -> "updated"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.koitharu.kotatsu.ui.common
|
||||
|
||||
import android.view.KeyEvent
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import moxy.MvpAppCompatActivity
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
abstract class BaseActivity : MvpAppCompatActivity(), KoinComponent {
|
||||
@@ -27,4 +29,13 @@ abstract class BaseActivity : MvpAppCompatActivity(), KoinComponent {
|
||||
onBackPressed()
|
||||
true
|
||||
} else super.onOptionsItemSelected(item)
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
//TODO remove. Just for testing
|
||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||
recreate()
|
||||
return true
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.koitharu.kotatsu.ui.common
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import moxy.MvpBottomSheetDialogFragment
|
||||
|
||||
abstract class BaseBottomSheet(@LayoutRes private val layoutResId: Int) : MvpBottomSheetDialogFragment() {
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? = inflater.inflate(layoutResId, container, false)
|
||||
}
|
||||
@@ -1,26 +1,19 @@
|
||||
package org.koitharu.kotatsu.ui.common
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.LayoutRes
|
||||
import moxy.MvpAppCompatFragment
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.utils.delegates.ParcelableArgumentDelegate
|
||||
import org.koitharu.kotatsu.utils.delegates.StringArgumentDelegate
|
||||
|
||||
abstract class BaseFragment(@LayoutRes contentLayoutId: Int) :
|
||||
MvpAppCompatFragment(contentLayoutId), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
protected val settings by inject<AppSettings>()
|
||||
MvpAppCompatFragment(contentLayoutId) {
|
||||
|
||||
fun stringArg(name: String) = StringArgumentDelegate(name)
|
||||
|
||||
fun <T : Parcelable> arg(name: String) = ParcelableArgumentDelegate<T>(name)
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = Unit
|
||||
|
||||
open fun getTitle(): CharSequence? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
|
||||
@@ -16,7 +16,7 @@ abstract class BaseViewHolder<T, E> protected constructor(view: View) :
|
||||
override val containerView: View?
|
||||
get() = itemView
|
||||
|
||||
protected var boundData: T? = null
|
||||
var boundData: T? = null
|
||||
private set
|
||||
|
||||
val context get() = itemView.context!!
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.koitharu.kotatsu.ui.common.list.decor
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeDrawable
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ItemTypeDividerDecoration(context: Context) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private val divider = context.getThemeDrawable(android.R.attr.listDivider)
|
||||
private val bounds = Rect()
|
||||
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect, view: View,
|
||||
parent: RecyclerView, state: RecyclerView.State
|
||||
) {
|
||||
outRect.set(0, divider?.intrinsicHeight ?: 0, 0, 0)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas, parent: RecyclerView, s: RecyclerView.State) {
|
||||
if (parent.layoutManager == null || divider == null) {
|
||||
return
|
||||
}
|
||||
val adapter = parent.adapter ?: return
|
||||
canvas.save()
|
||||
val left: Int
|
||||
val right: Int
|
||||
if (parent.clipToPadding) {
|
||||
left = parent.paddingLeft
|
||||
right = parent.width - parent.paddingRight
|
||||
canvas.clipRect(
|
||||
left, parent.paddingTop, right,
|
||||
parent.height - parent.paddingBottom
|
||||
)
|
||||
} else {
|
||||
left = 0
|
||||
right = parent.width
|
||||
}
|
||||
|
||||
var lastItemType = -1
|
||||
for (child in parent.children) {
|
||||
val itemType = adapter.getItemViewType(parent.getChildAdapterPosition(child))
|
||||
if (lastItemType != -1 && itemType != lastItemType) {
|
||||
parent.getDecoratedBoundsWithMargins(child, bounds)
|
||||
val top: Int = bounds.top + child.translationY.roundToInt()
|
||||
val bottom: Int = top + divider.intrinsicHeight
|
||||
divider.setBounds(left, top, right, bottom)
|
||||
divider.draw(canvas)
|
||||
}
|
||||
lastItemType = itemType
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.koitharu.kotatsu.ui.common.list.decor
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.utils.ext.inflate
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* https://github.com/paetztm/recycler_view_headers
|
||||
*/
|
||||
class SectionItemDecoration(
|
||||
private val isSticky: Boolean,
|
||||
private val callback: Callback
|
||||
) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private var headerView: TextView? = null
|
||||
private var headerOffset: Int = 0
|
||||
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
if (headerOffset == 0) {
|
||||
headerOffset = parent.resources.getDimensionPixelSize(R.dimen.header_height)
|
||||
}
|
||||
val pos = parent.getChildAdapterPosition(view)
|
||||
outRect.set(0, if (callback.isSection(pos)) headerOffset else 0, 0, 0)
|
||||
}
|
||||
|
||||
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
super.onDrawOver(c, parent, state)
|
||||
val textView = headerView ?: parent.inflate<TextView>(R.layout.item_header).also {
|
||||
headerView = it
|
||||
}
|
||||
fixLayoutSize(textView, parent)
|
||||
|
||||
for (child in parent.children) {
|
||||
val pos = parent.getChildAdapterPosition(child)
|
||||
if (callback.isSection(pos)) {
|
||||
textView.text = callback.getSectionTitle(pos) ?: continue
|
||||
c.save()
|
||||
if (isSticky) {
|
||||
c.translate(
|
||||
0f,
|
||||
max(0f, (child.top - textView.height).toFloat())
|
||||
)
|
||||
} else {
|
||||
c.translate(
|
||||
0f,
|
||||
(child.top - textView.height).toFloat()
|
||||
)
|
||||
}
|
||||
textView.draw(c)
|
||||
c.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures the header view to make sure its size is greater than 0 and will be drawn
|
||||
* https://yoda.entelect.co.za/view/9627/how-to-android-recyclerview-item-decorations
|
||||
*/
|
||||
private fun fixLayoutSize(view: View, parent: ViewGroup) {
|
||||
val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
|
||||
val heightSpec =
|
||||
View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)
|
||||
|
||||
val childWidth = ViewGroup.getChildMeasureSpec(
|
||||
widthSpec,
|
||||
parent.paddingLeft + parent.paddingRight,
|
||||
view.layoutParams.width
|
||||
)
|
||||
val childHeight = ViewGroup.getChildMeasureSpec(
|
||||
heightSpec,
|
||||
parent.paddingTop + parent.paddingBottom,
|
||||
view.layoutParams.height
|
||||
)
|
||||
view.measure(childWidth, childHeight)
|
||||
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
|
||||
fun isSection(position: Int): Boolean
|
||||
|
||||
fun getSectionTitle(position: Int): CharSequence?
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.common.list
|
||||
package org.koitharu.kotatsu.ui.common.list.decor
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
@@ -33,7 +33,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
|
||||
navigationView.setNavigationItemSelectedListener(this)
|
||||
|
||||
if (!supportFragmentManager.isStateSaved) {
|
||||
if (supportFragmentManager.findFragmentById(R.id.container) == null) {
|
||||
navigationView.setCheckedItem(R.id.nav_local_storage)
|
||||
setPrimaryFragment(LocalListFragment.newInstance())
|
||||
}
|
||||
|
||||
@@ -6,28 +6,41 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.fragment_list.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.core.model.SortOrder
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.ui.common.BaseFragment
|
||||
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
|
||||
import org.koitharu.kotatsu.ui.common.list.PaginationScrollListener
|
||||
import org.koitharu.kotatsu.ui.common.list.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.ui.common.list.decor.ItemTypeDividerDecoration
|
||||
import org.koitharu.kotatsu.ui.common.list.decor.SectionItemDecoration
|
||||
import org.koitharu.kotatsu.ui.common.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
|
||||
import org.koitharu.kotatsu.utils.ext.clearItemDecorations
|
||||
import org.koitharu.kotatsu.utils.ext.firstItem
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.hasItems
|
||||
import org.koitharu.kotatsu.ui.main.list.filter.FilterAdapter
|
||||
import org.koitharu.kotatsu.ui.main.list.filter.OnFilterChangedListener
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
|
||||
abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), MangaListView<E>,
|
||||
PaginationScrollListener.Callback, OnRecyclerItemClickListener<Manga> {
|
||||
abstract class MangaListFragment<E> : BaseFragment(R.layout.fragment_list), MangaListView<E>,
|
||||
PaginationScrollListener.Callback, OnRecyclerItemClickListener<Manga>,
|
||||
SharedPreferences.OnSharedPreferenceChangeListener, OnFilterChangedListener,
|
||||
SectionItemDecoration.Callback {
|
||||
|
||||
private val settings by inject<AppSettings>()
|
||||
|
||||
private lateinit var adapter: MangaListAdapter
|
||||
|
||||
@@ -38,6 +51,7 @@ abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), Man
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
adapter = MangaListAdapter(this)
|
||||
initListMode(settings.listMode)
|
||||
recyclerView.adapter = adapter
|
||||
@@ -45,6 +59,8 @@ abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), Man
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
onRequestMoreItems(0)
|
||||
}
|
||||
recyclerView_filter.addItemDecoration(ItemTypeDividerDecoration(view.context))
|
||||
recyclerView_filter.addItemDecoration(SectionItemDecoration(false, this))
|
||||
settings.subscribe(this)
|
||||
}
|
||||
|
||||
@@ -55,7 +71,7 @@ abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), Man
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
if (!recyclerView.hasItems) {
|
||||
if (savedInstanceState?.containsKey("MoxyDelegateBundle") != true) {
|
||||
onRequestMoreItems(0)
|
||||
}
|
||||
}
|
||||
@@ -65,14 +81,24 @@ abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), Man
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) {
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.action_list_mode -> {
|
||||
ListModeSelectDialog.show(childFragmentManager)
|
||||
true
|
||||
}
|
||||
R.id.action_filter -> {
|
||||
drawer.toggleDrawer(GravityCompat.END)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
menu.findItem(R.id.action_filter).isVisible =
|
||||
drawer.getDrawerLockMode(GravityCompat.END) != DrawerLayout.LOCK_MODE_LOCKED_CLOSED
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onItemClick(item: Manga, position: Int, view: View) {
|
||||
startActivity(MangaDetailsActivity.newIntent(context ?: return, item))
|
||||
}
|
||||
@@ -93,10 +119,16 @@ abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), Man
|
||||
|
||||
override fun onError(e: Exception) {
|
||||
if (recyclerView.hasItems) {
|
||||
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show()
|
||||
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
} else {
|
||||
textView_holder.text = e.getDisplayMessage(resources)
|
||||
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(0, R.drawable.ic_error_large, 0, 0)
|
||||
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
0,
|
||||
R.drawable.ic_error_large,
|
||||
0,
|
||||
0
|
||||
)
|
||||
layout_holder.isVisible = true
|
||||
}
|
||||
}
|
||||
@@ -112,11 +144,32 @@ abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), Man
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
when(key) {
|
||||
when (key) {
|
||||
getString(R.string.key_list_mode) -> initListMode(settings.listMode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInitFilter(
|
||||
sortOrders: List<SortOrder>,
|
||||
tags: List<MangaTag>,
|
||||
currentFilter: MangaFilter?
|
||||
) {
|
||||
recyclerView_filter.adapter = FilterAdapter(sortOrders, tags, currentFilter, this)
|
||||
drawer.setDrawerLockMode(
|
||||
if (sortOrders.isEmpty() && tags.isEmpty()) {
|
||||
DrawerLayout.LOCK_MODE_LOCKED_CLOSED
|
||||
} else {
|
||||
DrawerLayout.LOCK_MODE_UNLOCKED
|
||||
}
|
||||
)
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onFilterChanged(filter: MangaFilter) {
|
||||
drawer.closeDrawers()
|
||||
}
|
||||
|
||||
protected open fun setUpEmptyListHolder() {
|
||||
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null)
|
||||
textView_holder.setText(R.string.nothing_found)
|
||||
@@ -129,17 +182,35 @@ abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), Man
|
||||
recyclerView.layoutManager = null
|
||||
recyclerView.clearItemDecorations()
|
||||
adapter.listMode = mode
|
||||
recyclerView.layoutManager = when(mode) {
|
||||
recyclerView.layoutManager = when (mode) {
|
||||
ListMode.GRID -> GridLayoutManager(ctx, 3)
|
||||
else -> LinearLayoutManager(ctx)
|
||||
}
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.addItemDecoration(when(mode) {
|
||||
ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL)
|
||||
ListMode.DETAILED_LIST,
|
||||
ListMode.GRID -> SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing))
|
||||
})
|
||||
recyclerView.addItemDecoration(
|
||||
when (mode) {
|
||||
ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL)
|
||||
ListMode.DETAILED_LIST,
|
||||
ListMode.GRID -> SpacingItemDecoration(
|
||||
resources.getDimensionPixelOffset(R.dimen.grid_spacing)
|
||||
)
|
||||
}
|
||||
)
|
||||
adapter.notifyDataSetChanged()
|
||||
recyclerView.firstItem = position
|
||||
}
|
||||
|
||||
override fun isSection(position: Int): Boolean {
|
||||
return position == 0 || recyclerView_filter.adapter?.run {
|
||||
getItemViewType(position) != getItemViewType(position - 1)
|
||||
} ?: false
|
||||
}
|
||||
|
||||
override fun getSectionTitle(position: Int): CharSequence? {
|
||||
return when (recyclerView_filter.adapter?.getItemViewType(position)) {
|
||||
FilterAdapter.VIEW_TYPE_SORT -> getString(R.string.sort_order)
|
||||
FilterAdapter.VIEW_TYPE_TAG -> getString(R.string.genre)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,15 @@ import org.koitharu.kotatsu.core.model.MangaHistory
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||
|
||||
class MangaListHolder(parent: ViewGroup) : BaseViewHolder<Manga, MangaHistory?>(parent, R.layout.item_manga_list) {
|
||||
class MangaListHolder(parent: ViewGroup) :
|
||||
BaseViewHolder<Manga, MangaHistory?>(parent, R.layout.item_manga_list) {
|
||||
|
||||
private var coverRequest: RequestDisposable? = null
|
||||
|
||||
override fun onBind(data: Manga, extra: MangaHistory?) {
|
||||
coverRequest?.dispose()
|
||||
textView_title.text = data.title
|
||||
textView_subtitle.textAndVisible = data.altTitle
|
||||
textView_subtitle.textAndVisible = data.tags.joinToString(", ") { it.title }
|
||||
coverRequest = imageView_cover.load(data.coverUrl) {
|
||||
crossfade(true)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package org.koitharu.kotatsu.ui.main.list
|
||||
import moxy.MvpView
|
||||
import moxy.viewstate.strategy.*
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.core.model.SortOrder
|
||||
|
||||
interface MangaListView<E> : MvpView {
|
||||
|
||||
@@ -17,4 +20,7 @@ interface MangaListView<E> : MvpView {
|
||||
|
||||
@StateStrategyType(OneExecutionStateStrategy::class)
|
||||
fun onError(e: Exception)
|
||||
|
||||
@StateStrategyType(AddToEndSingleStrategy::class)
|
||||
fun onInitFilter(sortOrders: List<SortOrder>, tags: List<MangaTag>, currentFilter: MangaFilter?)
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import androidx.core.util.set
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
import org.koitharu.kotatsu.utils.ext.disableFor
|
||||
|
||||
class CategoriesAdapter(private val listener: OnCategoryCheckListener) :
|
||||
BaseRecyclerAdapter<FavouriteCategory, Boolean>() {
|
||||
@@ -37,7 +36,6 @@ class CategoriesAdapter(private val listener: OnCategoryCheckListener) :
|
||||
holder.itemView.setOnClickListener {
|
||||
if (it !is Checkable) return@setOnClickListener
|
||||
it.toggle()
|
||||
it.disableFor(200)
|
||||
if (it.isChecked) {
|
||||
listener.onCategoryChecked(holder.requireData())
|
||||
} else {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.koitharu.kotatsu.ui.main.list.favourites.categories
|
||||
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.item_caegory_checkable.*
|
||||
import kotlinx.android.synthetic.main.item_category_checkable.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
|
||||
class CategoryHolder(parent: ViewGroup) :
|
||||
BaseViewHolder<FavouriteCategory, Boolean>(parent, R.layout.item_caegory_checkable) {
|
||||
BaseViewHolder<FavouriteCategory, Boolean>(parent, R.layout.item_category_checkable) {
|
||||
|
||||
override fun onBind(data: FavouriteCategory, extra: Boolean) {
|
||||
checkedTextView.text = data.title
|
||||
|
||||
@@ -4,19 +4,18 @@ import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import kotlinx.android.synthetic.main.dialog_favorite_categories.*
|
||||
import moxy.ktx.moxyPresenter
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.ui.common.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.ui.common.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.ui.common.TextInputDialog
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
class FavouriteCategoriesDialog() : AlertDialogFragment(R.layout.dialog_favorite_categories),
|
||||
class FavouriteCategoriesDialog() : BaseBottomSheet(R.layout.dialog_favorite_categories),
|
||||
FavouriteCategoriesView,
|
||||
OnCategoryCheckListener {
|
||||
|
||||
@@ -26,10 +25,6 @@ class FavouriteCategoriesDialog() : AlertDialogFragment(R.layout.dialog_favorite
|
||||
|
||||
private var adapter: CategoriesAdapter? = null
|
||||
|
||||
override fun onBuildDialog(builder: AlertDialog.Builder) {
|
||||
builder.setTitle(R.string.add_to_favourites)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
adapter = CategoriesAdapter(this)
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.koitharu.kotatsu.ui.main.list.filter
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.core.model.SortOrder
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FilterAdapter(
|
||||
sortOrders: List<SortOrder> = emptyList(),
|
||||
tags: List<MangaTag> = emptyList(),
|
||||
state: MangaFilter?,
|
||||
private val listener: OnFilterChangedListener
|
||||
) : RecyclerView.Adapter<BaseViewHolder<*, Boolean>>() {
|
||||
|
||||
private val sortOrders = ArrayList<SortOrder>(sortOrders)
|
||||
private val tags = ArrayList(Collections.singletonList(null) + tags)
|
||||
|
||||
private var currentState = state ?: MangaFilter(sortOrders.first(), null)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
|
||||
VIEW_TYPE_SORT -> FilterSortHolder(parent).apply {
|
||||
itemView.setOnClickListener {
|
||||
setCheckedSort(requireData())
|
||||
}
|
||||
}
|
||||
VIEW_TYPE_TAG -> FilterTagHolder(parent).apply {
|
||||
itemView.setOnClickListener {
|
||||
setCheckedTag(boundData)
|
||||
}
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unknown viewType $viewType")
|
||||
}
|
||||
|
||||
override fun getItemCount() = sortOrders.size + tags.size
|
||||
|
||||
override fun onBindViewHolder(holder: BaseViewHolder<*, Boolean>, position: Int) {
|
||||
when (holder) {
|
||||
is FilterSortHolder -> {
|
||||
val item = sortOrders[position]
|
||||
holder.bind(item, item == currentState.sortOrder)
|
||||
}
|
||||
is FilterTagHolder -> {
|
||||
val item = tags[position - sortOrders.size]
|
||||
holder.bind(item, item == currentState.tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int) = when (position) {
|
||||
in sortOrders.indices -> VIEW_TYPE_SORT
|
||||
else -> VIEW_TYPE_TAG
|
||||
}
|
||||
|
||||
fun setCheckedTag(tag: MangaTag?) {
|
||||
if (tag != currentState.tag) {
|
||||
val oldItemPos = tags.indexOf(currentState.tag)
|
||||
val newItemPos = tags.indexOf(tag)
|
||||
currentState = currentState.copy(tag = tag)
|
||||
if (oldItemPos in tags.indices) {
|
||||
notifyItemChanged(sortOrders.size + oldItemPos)
|
||||
}
|
||||
if (newItemPos in tags.indices) {
|
||||
notifyItemChanged(sortOrders.size + newItemPos)
|
||||
}
|
||||
listener.onFilterChanged(currentState)
|
||||
}
|
||||
}
|
||||
|
||||
fun setCheckedSort(sort: SortOrder) {
|
||||
if (sort != currentState.sortOrder) {
|
||||
val oldItemPos = sortOrders.indexOf(currentState.sortOrder)
|
||||
val newItemPos = sortOrders.indexOf(sort)
|
||||
currentState = currentState.copy(sortOrder = sort)
|
||||
if (oldItemPos in sortOrders.indices) {
|
||||
notifyItemChanged(oldItemPos)
|
||||
}
|
||||
if (newItemPos in sortOrders.indices) {
|
||||
notifyItemChanged(newItemPos)
|
||||
}
|
||||
listener.onFilterChanged(currentState)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val VIEW_TYPE_SORT = 0
|
||||
const val VIEW_TYPE_TAG = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.koitharu.kotatsu.ui.main.list.filter
|
||||
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.item_checkable_single.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.SortOrder
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
|
||||
class FilterSortHolder(parent: ViewGroup) :
|
||||
BaseViewHolder<SortOrder, Boolean>(parent, R.layout.item_checkable_single) {
|
||||
|
||||
override fun onBind(data: SortOrder, extra: Boolean) {
|
||||
radio.setText(data.titleRes)
|
||||
radio.isChecked = extra
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.koitharu.kotatsu.ui.main.list.filter
|
||||
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.item_checkable_single.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
|
||||
class FilterTagHolder(parent: ViewGroup) :
|
||||
BaseViewHolder<MangaTag?, Boolean>(parent, R.layout.item_checkable_single) {
|
||||
|
||||
override fun onBind(data: MangaTag?, extra: Boolean) {
|
||||
radio.text = data?.title ?: context.getString(R.string.all)
|
||||
radio.isChecked = extra
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.koitharu.kotatsu.ui.main.list.filter
|
||||
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
|
||||
interface OnFilterChangedListener {
|
||||
|
||||
fun onFilterChanged(filter: MangaFilter)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.ui.main.list.remote
|
||||
|
||||
import moxy.ktx.moxyPresenter
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.ui.main.list.MangaListFragment
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
@@ -19,6 +20,11 @@ class RemoteListFragment : MangaListFragment<Unit>() {
|
||||
return source.title
|
||||
}
|
||||
|
||||
override fun onFilterChanged(filter: MangaFilter) {
|
||||
presenter.applyFilter(source, filter)
|
||||
super.onFilterChanged(filter)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARG_SOURCE = "provider"
|
||||
|
||||
@@ -5,6 +5,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import moxy.InjectViewState
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.domain.MangaProviderFactory
|
||||
import org.koitharu.kotatsu.ui.common.BasePresenter
|
||||
@@ -13,13 +14,19 @@ import org.koitharu.kotatsu.ui.main.list.MangaListView
|
||||
@InjectViewState
|
||||
class RemoteListPresenter : BasePresenter<MangaListView<Unit>>() {
|
||||
|
||||
private var isFilterInitialized = false
|
||||
private var filter: MangaFilter? = null
|
||||
|
||||
fun loadList(source: MangaSource, offset: Int) {
|
||||
launch {
|
||||
viewState.onLoadingChanged(true)
|
||||
try {
|
||||
val list = withContext(Dispatchers.IO) {
|
||||
MangaProviderFactory.create(source)
|
||||
.getList(offset)
|
||||
MangaProviderFactory.create(source).getList(
|
||||
offset = offset,
|
||||
sortOrder = filter?.sortOrder,
|
||||
tag = filter?.tag
|
||||
)
|
||||
}
|
||||
if (offset == 0) {
|
||||
viewState.onListChanged(list)
|
||||
@@ -35,5 +42,32 @@ class RemoteListPresenter : BasePresenter<MangaListView<Unit>>() {
|
||||
viewState.onLoadingChanged(false)
|
||||
}
|
||||
}
|
||||
if (!isFilterInitialized) {
|
||||
loadFilter(source)
|
||||
}
|
||||
}
|
||||
|
||||
fun applyFilter(source: MangaSource, filter: MangaFilter) {
|
||||
this.filter = filter
|
||||
viewState.onListChanged(emptyList())
|
||||
loadList(source, 0)
|
||||
}
|
||||
|
||||
private fun loadFilter(source: MangaSource) {
|
||||
isFilterInitialized = true
|
||||
launch {
|
||||
try {
|
||||
val (sorts, tags) = withContext(Dispatchers.IO) {
|
||||
val repo = MangaProviderFactory.create(source)
|
||||
repo.sortOrders.sortedBy { it.ordinal } to repo.getTags().sortedBy { it.title }
|
||||
}
|
||||
viewState.onInitFilter(sorts, tags, filter)
|
||||
} catch (e: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
isFilterInitialized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -131,4 +132,12 @@ fun View.hasGlobalPoint(x: Int, y: Int): Boolean {
|
||||
val rect = Rect()
|
||||
getGlobalVisibleRect(rect)
|
||||
return rect.contains(x, y)
|
||||
}
|
||||
|
||||
fun DrawerLayout.toggleDrawer(gravity: Int) {
|
||||
if (isDrawerOpen(gravity)) {
|
||||
closeDrawer(gravity)
|
||||
} else {
|
||||
openDrawer(gravity)
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,15 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="12dp">
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
|
||||
android:padding="16dp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/add_to_favourites" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
@@ -20,7 +27,7 @@
|
||||
android:orientation="vertical"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_caegory_checkable" />
|
||||
tools:listitem="@layout/item_category_checkable" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,53 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:openDrawer="end">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_manga_list" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:orientation="vertical">
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_holder"
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="?android:textColorSecondary"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
tools:text="@tools:sample/lorem[3]" />
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/textView_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="?android:textColorSecondary"
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
tools:text="@tools:sample/lorem[3]" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView_filter"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:background="?android:windowBackground"
|
||||
android:orientation="vertical"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_category_checkable" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
14
app/src/main/res/layout/item_checkable_single.xml
Normal file
14
app/src/main/res/layout/item_checkable_single.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CheckedTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/radio"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:drawableStart="?android:listChoiceIndicatorSingle"
|
||||
android:drawablePadding="12dp"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
15
app/src/main/res/layout/item_header.xml
Normal file
15
app/src/main/res/layout/item_header.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/header_height"
|
||||
android:background="?android:windowBackground"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textStyle="bold"
|
||||
tools:text="@tools:sample/lorem[2]" />
|
||||
@@ -1,9 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?android:windowBackground"
|
||||
app:cardElevation="0dp"
|
||||
app:cardMaxElevation="0dp"
|
||||
app:strokeColor="?android:textColorPrimary"
|
||||
app:strokeWidth="1px">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/manga_list_item_height"
|
||||
android:background="?selectableItemBackground"
|
||||
@@ -8,8 +9,8 @@
|
||||
<org.koitharu.kotatsu.ui.common.widgets.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:cardElevation="0dp"
|
||||
app:strokeColor="?android:textColorPrimary"
|
||||
app:strokeWidth="1px"
|
||||
android:layout_height="@dimen/manga_list_details_item_height"
|
||||
app:cardBackgroundColor="?android:windowBackground"
|
||||
android:layout_height="@dimen/manga_list_details_item_height">
|
||||
app:cardElevation="0dp"
|
||||
app:cardMaxElevation="0dp"
|
||||
app:strokeColor="?android:textColorPrimary"
|
||||
app:strokeWidth="1px">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
|
||||
<item
|
||||
android:id="@+id/action_list_mode"
|
||||
android:title="@string/list_mode"
|
||||
android:orderInCategory="20"
|
||||
android:title="@string/list_mode"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_filter"
|
||||
android:orderInCategory="30"
|
||||
android:title="@string/filter"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
@@ -4,4 +4,6 @@
|
||||
<dimen name="manga_list_item_height">84dp</dimen>
|
||||
<dimen name="manga_list_details_item_height">120dp</dimen>
|
||||
<dimen name="chapter_list_item_height">46dp</dimen>
|
||||
<dimen name="preferred_grid_width">120dp</dimen>
|
||||
<dimen name="header_height">42dp</dimen>
|
||||
</resources>
|
||||
@@ -46,4 +46,13 @@
|
||||
<string name="save_this_chapter_and_next">Save this chapter and next</string>
|
||||
<string name="save_this_chapter">Save this chapter</string>
|
||||
<string name="no_saved_manga">No saved manga</string>
|
||||
<string name="by_name">By name</string>
|
||||
<string name="popular">Popular</string>
|
||||
<string name="updated">Updated</string>
|
||||
<string name="newest">Newest</string>
|
||||
<string name="by_rating">By rating</string>
|
||||
<string name="all">All</string>
|
||||
<string name="sort_order">Sort order</string>
|
||||
<string name="genre">Genre</string>
|
||||
<string name="filter">Filter</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user