Improve explore fragment

This commit is contained in:
Koitharu
2022-07-07 14:28:06 +03:00
parent b7442fe445
commit 49634a2f52
13 changed files with 232 additions and 175 deletions

View File

@@ -11,16 +11,19 @@ import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.databinding.FragmentExploreBinding import org.koitharu.kotatsu.databinding.FragmentExploreBinding
import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter
import org.koitharu.kotatsu.explore.ui.adapter.SourcesHeaderEventListener import org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener
import org.koitharu.kotatsu.library.ui.adapter.LibraryAdapter import org.koitharu.kotatsu.explore.ui.model.ExploreItem
import org.koitharu.kotatsu.history.ui.HistoryActivity
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.SettingsActivity
class ExploreFragment : BaseFragment<FragmentExploreBinding>(), class ExploreFragment : BaseFragment<FragmentExploreBinding>(),
RecyclerViewOwner, RecyclerViewOwner,
SourcesHeaderEventListener { ExploreListEventListener, OnListItemClickListener<ExploreItem.Source> {
private val viewModel by viewModel<ExploreViewModel>() private val viewModel by viewModel<ExploreViewModel>()
private var exploreAdapter: ExploreAdapter? = null private var exploreAdapter: ExploreAdapter? = null
@@ -35,15 +38,15 @@ class ExploreFragment : BaseFragment<FragmentExploreBinding>(),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
exploreAdapter = ExploreAdapter(get(), viewLifecycleOwner, this) exploreAdapter = ExploreAdapter(get(), viewLifecycleOwner, this, this)
with(binding.recyclerView) { with(binding.recyclerView) {
adapter = exploreAdapter adapter = exploreAdapter
setHasFixedSize(true) setHasFixedSize(true)
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing) val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
paddingHorizontal = spacing paddingHorizontal = spacing
} }
viewModel.items.observe(viewLifecycleOwner) { viewModel.content.observe(viewLifecycleOwner) {
exploreAdapter!!.items = it exploreAdapter?.items = it
} }
} }
@@ -68,6 +71,19 @@ class ExploreFragment : BaseFragment<FragmentExploreBinding>(),
startActivity(SettingsActivity.newManageSourcesIntent(view.context)) startActivity(SettingsActivity.newManageSourcesIntent(view.context))
} }
override fun onClick(v: View) {
val intent = when (v.id) {
R.id.button_history -> HistoryActivity.newIntent(v.context)
else -> return
}
startActivity(intent)
}
override fun onItemClick(item: ExploreItem.Source, view: View) {
val intent = MangaListActivity.newIntent(view.context, item.source)
startActivity(intent)
}
override fun onRetryClick(error: Throwable) = Unit override fun onRetryClick(error: Throwable) = Unit
override fun onEmptyActionClick() = Unit override fun onEmptyActionClick() = Unit

View File

@@ -1,71 +1,40 @@
package org.koitharu.kotatsu.explore.ui package org.koitharu.kotatsu.explore.ui
import androidx.core.os.LocaleListCompat import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.getLocaleTitle
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.explore.ui.model.ExploreItem import org.koitharu.kotatsu.explore.ui.model.ExploreItem
import org.koitharu.kotatsu.utils.ext.map import org.koitharu.kotatsu.parsers.model.MangaSource
import java.util.* import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
private const val KEY_ENABLED = "!"
class ExploreViewModel( class ExploreViewModel(
private val settings: AppSettings, private val settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
val items = MutableLiveData<List<ExploreItem>>(emptyList()) val content: LiveData<List<ExploreItem>> = settings.observe()
.filter { it == AppSettings.KEY_SOURCES_HIDDEN || it == AppSettings.KEY_SOURCES_ORDER }
.onStart { emit("") }
.map { settings.getMangaSources(includeHidden = false) }
.distinctUntilChanged()
.map { buildList(it) }
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
init { private fun buildList(sources: List<MangaSource>): List<ExploreItem> {
buildList() val result = ArrayList<ExploreItem>(sources.size + 2)
}
private fun buildList() {
val sources = settings.getMangaSources(includeHidden = true)
val hiddenSources = settings.hiddenSources
val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) {
if (it.name !in hiddenSources) {
KEY_ENABLED
} else {
it.locale
}
}
val result = ArrayList<ExploreItem>(sources.size + map.size + 1)
result += ExploreItem.Buttons result += ExploreItem.Buttons
val enabledSources = map.remove(KEY_ENABLED) if (sources.isNotEmpty()) {
if (!enabledSources.isNullOrEmpty()) {
result += ExploreItem.Header(R.string.enabled_sources) result += ExploreItem.Header(R.string.enabled_sources)
enabledSources.mapTo(result) { sources.mapTo(result) { ExploreItem.Source(it) }
ExploreItem.Source( } else {
source = it, // TODO
summary = it.getLocaleTitle(),
)
}
}
items.value = result
}
private class LocaleKeyComparator : Comparator<String?> {
private val deviceLocales = LocaleListCompat.getAdjustedDefault()
.map { it.language }
override fun compare(a: String?, b: String?): Int {
when {
a == b -> return 0
a == null -> return 1
b == null -> return -1
}
val ai = deviceLocales.indexOf(a!!)
val bi = deviceLocales.indexOf(b!!)
return when {
ai < 0 && bi < 0 -> a.compareTo(b)
ai < 0 -> 1
bi < 0 -> -1
else -> ai.compareTo(bi)
}
} }
return result
} }
} }

View File

@@ -3,25 +3,17 @@ package org.koitharu.kotatsu.explore.ui.adapter
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.explore.ui.model.ExploreItem import org.koitharu.kotatsu.explore.ui.model.ExploreItem
class ExploreAdapter( class ExploreAdapter(
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
listener: SourcesHeaderEventListener, listener: ExploreListEventListener,
clickListener: OnListItemClickListener<ExploreItem.Source>,
) : AsyncListDifferDelegationAdapter<ExploreItem>( ) : AsyncListDifferDelegationAdapter<ExploreItem>(
ExploreDiffCallback(), ExploreDiffCallback(),
exploreButtonsDelegate(), exploreButtonsAD(listener),
sourceHeaderDelegate(listener), exploreSourcesHeaderAD(listener),
sourceItemDelegate(coil, lifecycleOwner), exploreSourceItemAD(coil, clickListener, lifecycleOwner),
) { )
init {
delegatesManager
.addDelegate(exploreButtonsDelegate())
.addDelegate(sourceHeaderDelegate(listener = listener))
.addDelegate(sourceItemDelegate(coil, lifecycleOwner))
}
}

View File

@@ -7,22 +7,29 @@ import coil.request.Disposable
import coil.request.ImageRequest import coil.request.ImageRequest
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
import org.koitharu.kotatsu.databinding.ItemExploreHeaderBinding import org.koitharu.kotatsu.databinding.ItemExploreHeaderBinding
import org.koitharu.kotatsu.databinding.ItemExploreSourceBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceBinding
import org.koitharu.kotatsu.explore.ui.model.ExploreItem import org.koitharu.kotatsu.explore.ui.model.ExploreItem
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.image.FaviconFallbackDrawable
fun exploreButtonsDelegate() = adapterDelegateViewBinding<ExploreItem.Buttons, ExploreItem, ItemExploreButtonsBinding>( fun exploreButtonsAD(
clickListener: View.OnClickListener,
) = adapterDelegateViewBinding<ExploreItem.Buttons, ExploreItem, ItemExploreButtonsBinding>(
{ layoutInflater, parent -> ItemExploreButtonsBinding.inflate(layoutInflater, parent, false) } { layoutInflater, parent -> ItemExploreButtonsBinding.inflate(layoutInflater, parent, false) }
) { ) {
binding.localStorage.requestFocus() // stub binding.buttonBookmarks.setOnClickListener(clickListener)
binding.buttonHistory.setOnClickListener(clickListener)
binding.buttonLocal.setOnClickListener(clickListener)
binding.buttonSuggestions.setOnClickListener(clickListener)
}
} // TODO fun exploreSourcesHeaderAD(
listener: ExploreListEventListener,
fun sourceHeaderDelegate(
listener: SourcesHeaderEventListener,
) = adapterDelegateViewBinding<ExploreItem.Header, ExploreItem, ItemExploreHeaderBinding>( ) = adapterDelegateViewBinding<ExploreItem.Header, ExploreItem, ItemExploreHeaderBinding>(
{ layoutInflater, parent -> ItemExploreHeaderBinding.inflate(layoutInflater, parent, false) } { layoutInflater, parent -> ItemExploreHeaderBinding.inflate(layoutInflater, parent, false) }
) { ) {
@@ -38,8 +45,9 @@ fun sourceHeaderDelegate(
} }
} }
fun sourceItemDelegate( fun exploreSourceItemAD(
coil: ImageLoader, coil: ImageLoader,
listener: OnListItemClickListener<ExploreItem.Source>,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<ExploreItem.Source, ExploreItem, ItemExploreSourceBinding>( ) = adapterDelegateViewBinding<ExploreItem.Source, ExploreItem, ItemExploreSourceBinding>(
{ layoutInflater, parent -> ItemExploreSourceBinding.inflate(layoutInflater, parent, false) }, { layoutInflater, parent -> ItemExploreSourceBinding.inflate(layoutInflater, parent, false) },
@@ -47,12 +55,19 @@ fun sourceItemDelegate(
) { ) {
var imageRequest: Disposable? = null var imageRequest: Disposable? = null
val eventListener = AdapterDelegateClickListenerAdapter(this, listener)
binding.root.setOnClickListener(eventListener)
binding.root.setOnLongClickListener(eventListener)
bind { bind {
binding.textViewTitle.text = item.source.title binding.textViewTitle.text = item.source.title
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
imageRequest = ImageRequest.Builder(context) imageRequest = ImageRequest.Builder(context)
.data(item.faviconUrl) .data(item.faviconUrl)
.error(R.drawable.ic_favicon_fallback) .fallback(fallbackIcon)
.placeholder(fallbackIcon)
.error(fallbackIcon)
.target(binding.imageViewCover) .target(binding.imageViewCover)
.lifecycle(lifecycleOwner) .lifecycle(lifecycleOwner)
.enqueueWith(coil) .enqueueWith(coil)

View File

@@ -8,9 +8,7 @@ class ExploreDiffCallback : DiffUtil.ItemCallback<ExploreItem>() {
override fun areItemsTheSame(oldItem: ExploreItem, newItem: ExploreItem): Boolean { override fun areItemsTheSame(oldItem: ExploreItem, newItem: ExploreItem): Boolean {
return when { return when {
oldItem.javaClass != newItem.javaClass -> false oldItem.javaClass != newItem.javaClass -> false
oldItem is ExploreItem.Buttons && newItem is ExploreItem.Buttons -> { oldItem is ExploreItem.Buttons && newItem is ExploreItem.Buttons -> true
oldItem == newItem
}
oldItem is ExploreItem.Source && newItem is ExploreItem.Source -> { oldItem is ExploreItem.Source && newItem is ExploreItem.Source -> {
oldItem.source == newItem.source oldItem.source == newItem.source
} }

View File

@@ -3,8 +3,7 @@ package org.koitharu.kotatsu.explore.ui.adapter
import android.view.View import android.view.View
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
interface SourcesHeaderEventListener : ListStateHolderListener { interface ExploreListEventListener : ListStateHolderListener, View.OnClickListener {
fun onManageClick(view: View) fun onManageClick(view: View)
} }

View File

@@ -2,9 +2,10 @@ package org.koitharu.kotatsu.explore.ui.model
import android.net.Uri import android.net.Uri
import androidx.annotation.StringRes import androidx.annotation.StringRes
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
sealed interface ExploreItem { sealed interface ExploreItem : ListModel {
object Buttons : ExploreItem object Buttons : ExploreItem
@@ -24,7 +25,6 @@ sealed interface ExploreItem {
class Source( class Source(
val source: MangaSource, val source: MangaSource,
val summary: String?,
) : ExploreItem { ) : ExploreItem {
val faviconUrl: Uri val faviconUrl: Uri
@@ -37,15 +37,12 @@ sealed interface ExploreItem {
other as Source other as Source
if (source != other.source) return false if (source != other.source) return false
if (summary != other.summary) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = source.hashCode() return source.hashCode()
result = 31 * result + summary.hashCode()
return result
} }
} }

View File

@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags
import org.koitharu.kotatsu.databinding.ActivityContainerBinding import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel
@@ -24,17 +25,21 @@ class MangaListActivity : BaseActivity<ActivityContainerBinding>() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityContainerBinding.inflate(layoutInflater)) setContentView(ActivityContainerBinding.inflate(layoutInflater))
val tags = intent.getParcelableExtra<ParcelableMangaTags>(EXTRA_TAGS)?.tags ?: run { val tags = intent.getParcelableExtra<ParcelableMangaTags>(EXTRA_TAGS)?.tags
finishAfterTransition()
return
}
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
val fm = supportFragmentManager val fm = supportFragmentManager
if (fm.findFragmentById(R.id.container) == null) { if (fm.findFragmentById(R.id.container) == null) {
val source = intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: tags?.firstOrNull()?.source
if (source == null) {
finishAfterTransition()
return
}
fm.commit { fm.commit {
val fragment = RemoteListFragment.newInstance(tags.first().source) val fragment = RemoteListFragment.newInstance(source)
replace(R.id.container, fragment) replace(R.id.container, fragment)
runOnCommit(ApplyFilterRunnable(fragment, tags)) if (!tags.isNullOrEmpty()) {
runOnCommit(ApplyFilterRunnable(fragment, tags))
}
} }
} }
} }
@@ -70,9 +75,12 @@ class MangaListActivity : BaseActivity<ActivityContainerBinding>() {
companion object { companion object {
private const val EXTRA_TAGS = "tags" private const val EXTRA_TAGS = "tags"
private const val EXTRA_SOURCE = "source"
fun newIntent(context: Context, tags: Set<MangaTag>) = fun newIntent(context: Context, tags: Set<MangaTag>) = Intent(context, MangaListActivity::class.java)
Intent(context, MangaListActivity::class.java) .putExtra(EXTRA_TAGS, ParcelableMangaTags(tags))
.putExtra(EXTRA_TAGS, ParcelableMangaTags(tags))
fun newIntent(context: Context, source: MangaSource) = Intent(context, MangaListActivity::class.java)
.putExtra(EXTRA_SOURCE, source)
} }
} }

View File

@@ -0,0 +1,66 @@
package org.koitharu.kotatsu.utils.image
import android.content.Context
import android.graphics.*
import android.graphics.drawable.Drawable
import androidx.core.graphics.ColorUtils
import kotlin.math.absoluteValue
class FaviconFallbackDrawable(
context: Context,
name: String,
) : Drawable() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val letter = name.take(1).uppercase()
private val color = colorOfString(name)
private val textBounds = Rect()
private val tempRect = Rect()
init {
paint.style = Paint.Style.FILL
paint.textAlign = Paint.Align.CENTER
paint.isFakeBoldText = true
}
override fun draw(canvas: Canvas) {
val cx = bounds.exactCenterX()
paint.color = color
canvas.drawPaint(paint)
paint.color = Color.WHITE
val ty = bounds.height() / 2f + textBounds.height() / 2f - textBounds.bottom
canvas.drawText(letter, cx, ty, paint)
}
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
val innerWidth = bounds.width() - (paint.strokeWidth * 2f)
paint.textSize = getTextSizeForWidth(innerWidth, "100%")
paint.getTextBounds(letter, 0, letter.length, textBounds)
invalidateSelf()
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Deprecated in Java")
override fun getOpacity() = PixelFormat.TRANSPARENT
private fun getTextSizeForWidth(width: Float, text: String): Float {
val testTextSize = 48f
paint.textSize = testTextSize
paint.getTextBounds(text, 0, text.length, tempRect)
return testTextSize * width / tempRect.width()
}
private fun colorOfString(str: String): Int {
val hue = (str.hashCode() % 360).absoluteValue.toFloat()
return ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))
}
}

View File

@@ -5,45 +5,45 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingHorizontal="8dp" android:padding="4dp"
android:paddingVertical="4dp"> android:weightSum="4">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/favourites" android:id="@+id/button_history"
style="@style/Widget.Kotatsu.ExploreButton" style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginHorizontal="4dp"
android:layout_weight="1" android:layout_weight="1"
android:text="@string/history" android:text="@string/history"
app:icon="@drawable/ic_history" /> app:icon="@drawable/ic_history" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/local_storage" android:id="@+id/button_local"
style="@style/Widget.Kotatsu.ExploreButton" style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginHorizontal="4dp"
android:layout_weight="1" android:layout_weight="1"
android:text="@string/local_storage" android:text="@string/local_storage"
app:icon="@drawable/ic_storage" /> app:icon="@drawable/ic_storage" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/suggestions" android:id="@+id/button_suggestions"
style="@style/Widget.Kotatsu.ExploreButton" style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="4dp"
android:layout_weight="1" android:layout_weight="1"
android:text="@string/suggestions" android:text="@string/suggestions"
app:icon="@drawable/ic_suggestion" /> app:icon="@drawable/ic_suggestion" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/downloads" android:id="@+id/button_bookmarks"
style="@style/Widget.Kotatsu.ExploreButton" style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginHorizontal="4dp"
android:layout_weight="1" android:layout_weight="1"
android:text="@string/bookmarks" android:text="@string/bookmarks"
app:icon="@drawable/ic_bookmark" /> app:icon="@drawable/ic_bookmark" />

View File

@@ -1,62 +1,63 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:columnCount="2"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="8dp" android:rowCount="2">
android:paddingVertical="4dp">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/favourites" android:id="@+id/button_history"
style="@style/Widget.Kotatsu.ExploreButton" style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_columnWeight="0.5"
android:layout_gravity="fill_horizontal"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:text="@string/history" android:text="@string/history"
app:icon="@drawable/ic_history" app:icon="@drawable/ic_history" />
app:layout_constraintEnd_toStartOf="@+id/local_storage"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/local_storage" android:id="@+id/button_suggestions"
style="@style/Widget.Kotatsu.ExploreButton" style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/local_storage" android:layout_columnWeight="0.5"
app:icon="@drawable/ic_storage" android:layout_gravity="fill_horizontal"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginStart="8dp"
app:layout_constraintHorizontal_bias="0.5" android:layout_marginTop="4dp"
app:layout_constraintStart_toEndOf="@+id/favourites" android:layout_marginEnd="8dp"
app:layout_constraintTop_toTopOf="@+id/favourites" /> android:layout_marginBottom="4dp"
<com.google.android.material.button.MaterialButton
android:id="@+id/suggestions"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/suggestions" android:text="@string/suggestions"
app:icon="@drawable/ic_suggestion" app:icon="@drawable/ic_suggestion" />
app:layout_constraintEnd_toStartOf="@+id/downloads"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="@+id/favourites"
app:layout_constraintTop_toBottomOf="@+id/favourites" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/downloads" android:id="@+id/button_local"
style="@style/Widget.Kotatsu.ExploreButton" style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_columnWeight="0.5"
android:text="@string/bookmarks" android:layout_gravity="fill_horizontal"
app:icon="@drawable/ic_bookmark" android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="@+id/local_storage" android:layout_marginTop="4dp"
app:layout_constraintHorizontal_bias="0.5" android:layout_marginEnd="8dp"
app:layout_constraintStart_toEndOf="@+id/suggestions" android:layout_marginBottom="4dp"
app:layout_constraintTop_toBottomOf="@+id/local_storage" /> android:text="@string/local_storage"
app:icon="@drawable/ic_storage" />
</androidx.constraintlayout.widget.ConstraintLayout> <com.google.android.material.button.MaterialButton
android:id="@+id/button_bookmarks"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_height="wrap_content"
android:layout_columnWeight="0.5"
android:layout_gravity="fill_horizontal"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:text="@string/bookmarks"
app:icon="@drawable/ic_bookmark" />
</GridLayout>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@@ -7,32 +7,26 @@
android:layout_height="?attr/listPreferredItemHeightSmall" android:layout_height="?attr/listPreferredItemHeightSmall"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:clipChildren="false" android:clipChildren="false"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/list_spacing"> android:padding="@dimen/list_spacing">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover" android:id="@+id/imageView_cover"
android:layout_width="0dp" android:layout_width="32dp"
android:layout_height="0dp" android:layout_height="32dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent" app:shapeAppearance="?shapeAppearanceCornerSmall"
app:layout_constraintDimensionRatio="h,1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" /> tools:src="@tools:sample/backgrounds/scenic" />
<TextView <TextView
android:id="@+id/textView_title" android:id="@+id/textView_title"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyMedium" android:textAppearance="?attr/textAppearanceBodyMedium"
app:layout_constraintBottom_toBottomOf="parent" tools:text="@tools:sample/lorem[2]" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView_cover"
app:layout_constraintTop_toTopOf="@+id/imageView_cover"
tools:text="Title" />
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>

View File

@@ -128,15 +128,17 @@
</style> </style>
<style name="Widget.Kotatsu.ExploreButton" parent="Widget.Material3.Button.TonalButton.Icon"> <style name="Widget.Kotatsu.ExploreButton" parent="Widget.Material3.Button.TonalButton.Icon">
<item name="android:paddingTop">16dp</item> <item name="android:minHeight">58dp</item>
<item name="android:paddingBottom">16dp</item>
<item name="android:textColor">?attr/colorOnSurface</item> <item name="android:textColor">?attr/colorOnSurface</item>
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Material3.FloatingActionButton</item> <item name="singleLine">true</item>
<item name="shapeAppearance">?shapeAppearanceCornerLarge</item>
<item name="iconPadding">16dp</item> <item name="iconPadding">16dp</item>
<item name="iconGravity">start</item> <item name="iconGravity">start</item>
<item name="android:insetTop">2dp</item>
<item name="android:insetBottom">2dp</item>
<item name="iconTint">?attr/colorPrimary</item> <item name="iconTint">?attr/colorPrimary</item>
<item name="android:gravity">start|center_vertical</item> <item name="android:gravity">start|center_vertical</item>
<item name="android:textAppearance">@style/TextAppearance.Material3.BodyMedium</item> <item name="android:textAppearance">?textAppearanceButton</item>
</style> </style>
<style name="ThemeOverlay.Kotatsu.MainToolbar" parent=""> <style name="ThemeOverlay.Kotatsu.MainToolbar" parent="">