Manage library grid size

This commit is contained in:
Koitharu
2022-07-24 10:42:52 +03:00
parent 5a565a16fe
commit 5edfda6c1a
17 changed files with 301 additions and 43 deletions

View File

@@ -4,7 +4,7 @@ import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
import org.koitharu.kotatsu.library.domain.LibraryRepository
import org.koitharu.kotatsu.library.ui.LibraryViewModel
import org.koitharu.kotatsu.library.ui.config.LibraryCategoriesConfigViewModel
import org.koitharu.kotatsu.library.ui.config.categories.LibraryCategoriesConfigViewModel
val libraryModule
get() = module {

View File

@@ -6,14 +6,15 @@ import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import androidx.fragment.app.FragmentManager
import com.google.android.material.R as materialR
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.RememberSelectionDialogListener
import org.koitharu.kotatsu.library.ui.config.LibraryCategoriesConfigSheet
import org.koitharu.kotatsu.utils.ext.startOfDay
import java.util.*
import java.util.concurrent.TimeUnit
import com.google.android.material.R as materialR
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.RememberSelectionDialogListener
import org.koitharu.kotatsu.library.ui.config.categories.LibraryCategoriesConfigSheet
import org.koitharu.kotatsu.library.ui.config.size.LibrarySizeBottomSheet
import org.koitharu.kotatsu.utils.ext.startOfDay
class LibraryMenuProvider(
private val context: Context,
@@ -31,6 +32,10 @@ class LibraryMenuProvider(
showClearHistoryDialog()
true
}
R.id.action_grid_size -> {
LibrarySizeBottomSheet.show(fragmentManager)
true
}
R.id.action_categories -> {
LibraryCategoriesConfigSheet.show(fragmentManager)
true
@@ -64,4 +69,4 @@ class LibraryMenuProvider(
viewModel.clearHistory(minDate)
}.show()
}
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.config
package org.koitharu.kotatsu.library.ui.config.categories
import androidx.recyclerview.widget.DiffUtil
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
@@ -29,4 +29,4 @@ class LibraryCategoriesConfigAdapter(
} else Unit
}
}
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.config
package org.koitharu.kotatsu.library.ui.config.categories
import android.os.Bundle
import android.view.LayoutInflater
@@ -14,7 +14,9 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.SheetBaseBinding
import org.koitharu.kotatsu.utils.BottomSheetToolbarController
class LibraryCategoriesConfigSheet : BaseBottomSheet<SheetBaseBinding>(), OnListItemClickListener<FavouriteCategory>,
class LibraryCategoriesConfigSheet :
BaseBottomSheet<SheetBaseBinding>(),
OnListItemClickListener<FavouriteCategory>,
View.OnClickListener {
private val viewModel by viewModel<LibraryCategoriesConfigViewModel>()
@@ -53,4 +55,4 @@ class LibraryCategoriesConfigSheet : BaseBottomSheet<SheetBaseBinding>(), OnList
fun show(fm: FragmentManager) = LibraryCategoriesConfigSheet().show(fm, TAG)
}
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.config
package org.koitharu.kotatsu.library.ui.config.categories
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
@@ -24,4 +24,4 @@ class LibraryCategoriesConfigViewModel(
favouritesRepository.updateCategory(category.id, !category.isVisibleInLibrary)
}
}
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.config
package org.koitharu.kotatsu.library.ui.config.categories
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
@@ -9,9 +9,8 @@ import org.koitharu.kotatsu.databinding.ItemCategoryCheckableMultipleBinding
fun libraryCategoryAD(
listener: OnListItemClickListener<FavouriteCategory>,
) = adapterDelegateViewBinding<FavouriteCategory, FavouriteCategory, ItemCategoryCheckableMultipleBinding>(
{ layoutInflater, parent -> ItemCategoryCheckableMultipleBinding.inflate(layoutInflater, parent, false) }
{ layoutInflater, parent -> ItemCategoryCheckableMultipleBinding.inflate(layoutInflater, parent, false) },
) {
val eventListener = AdapterDelegateClickListenerAdapter(this, listener)
itemView.setOnClickListener(eventListener)
@@ -19,4 +18,4 @@ fun libraryCategoryAD(
binding.root.text = item.title
binding.root.isChecked = item.isVisibleInLibrary
}
}
}

View File

@@ -0,0 +1,63 @@
package org.koitharu.kotatsu.library.ui.config.size
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.SheetLibrarySizeBinding
import org.koitharu.kotatsu.utils.ext.setValueRounded
import org.koitharu.kotatsu.utils.progress.IntPercentLabelFormatter
class LibrarySizeBottomSheet :
BaseBottomSheet<SheetLibrarySizeBinding>(),
Slider.OnChangeListener,
View.OnClickListener {
private val settings by inject<AppSettings>(mode = LazyThreadSafetyMode.NONE)
private var labelFormatter: LabelFormatter? = null
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetLibrarySizeBinding {
return SheetLibrarySizeBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
labelFormatter = IntPercentLabelFormatter(view.context)
binding.sliderGrid.addOnChangeListener(this)
binding.buttonSmall.setOnClickListener(this)
binding.buttonLarge.setOnClickListener(this)
binding.sliderGrid.setValueRounded(settings.gridSize.toFloat())
}
override fun onDestroyView() {
labelFormatter = null
super.onDestroyView()
}
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
settings.gridSize = value.toInt()
binding.textViewLabel.text = labelFormatter?.getFormattedValue(value)
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_small -> binding.sliderGrid.value -= binding.sliderGrid.stepSize
R.id.button_large -> binding.sliderGrid.value += binding.sliderGrid.stepSize
}
}
companion object {
private const val TAG = "LibrarySizeBottomSheet"
fun show(fm: FragmentManager) = LibrarySizeBottomSheet().show(fm, TAG)
}
}

View File

@@ -1,15 +1,85 @@
package org.koitharu.kotatsu.list.ui
import android.content.SharedPreferences
import android.content.res.Resources
import android.view.View
import android.widget.TextView
import androidx.annotation.StyleRes
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import kotlin.math.roundToInt
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import kotlin.math.roundToInt
class ItemSizeResolver(resources: Resources, settings: AppSettings) {
class ItemSizeResolver(resources: Resources, private val settings: AppSettings) {
private val scaleFactor = settings.gridSize / 100f
private val gridWidth = resources.getDimension(R.dimen.preferred_grid_width)
private val scaleFactor: Float
get() = settings.gridSize / 100f
val cellWidth: Int
get() = (gridWidth * scaleFactor).roundToInt()
}
fun attachToView(lifecycleOwner: LifecycleOwner, view: View, textView: TextView?) {
val observer = SizeObserver(view, textView)
view.addOnAttachStateChangeListener(observer)
lifecycleOwner.lifecycle.addObserver(observer)
if (view.isAttachedToWindow) {
observer.update()
}
}
private inner class SizeObserver(
private val view: View,
private val textView: TextView?,
) : DefaultLifecycleObserver, SharedPreferences.OnSharedPreferenceChangeListener, View.OnAttachStateChangeListener {
private val widthThreshold = view.resources.getDimensionPixelSize(R.dimen.small_grid_width)
@StyleRes
private var prevTextAppearance = 0
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == AppSettings.KEY_GRID_SIZE) {
update()
}
}
override fun onViewAttachedToWindow(v: View?) {
settings.subscribe(this)
update()
}
override fun onViewDetachedFromWindow(v: View?) {
settings.unsubscribe(this)
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
settings.unsubscribe(this)
view.removeOnAttachStateChangeListener(this)
}
fun update() {
val newWidth = cellWidth
textView?.adjustTextAppearance(newWidth)
view.updateLayoutParams {
width = newWidth
}
}
private fun TextView.adjustTextAppearance(width: Int) {
val textAppearanceResId = if (width < widthThreshold) {
R.style.TextAppearance_Kotatsu_GridTitle_Small
} else {
R.style.TextAppearance_Kotatsu_GridTitle
}
if (textAppearanceResId != prevTextAppearance) {
prevTextAppearance = textAppearanceResId
setTextAppearance(textAppearanceResId)
requestLayout()
}
}
}
}

View File

@@ -18,14 +18,16 @@ import org.koitharu.kotatsu.databinding.DialogListModeBinding
import org.koitharu.kotatsu.utils.ext.setValueRounded
import org.koitharu.kotatsu.utils.progress.IntPercentLabelFormatter
class ListModeSelectDialog : AlertDialogFragment<DialogListModeBinding>(),
CheckableButtonGroup.OnCheckedChangeListener, Slider.OnSliderTouchListener {
class ListModeSelectDialog :
AlertDialogFragment<DialogListModeBinding>(),
CheckableButtonGroup.OnCheckedChangeListener,
Slider.OnChangeListener {
private val settings by inject<AppSettings>(mode = LazyThreadSafetyMode.NONE)
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
container: ViewGroup?,
) = DialogListModeBinding.inflate(inflater, container, false)
override fun onBuildDialog(builder: MaterialAlertDialogBuilder) {
@@ -45,7 +47,7 @@ class ListModeSelectDialog : AlertDialogFragment<DialogListModeBinding>(),
binding.sliderGrid.setLabelFormatter(IntPercentLabelFormatter(view.context))
binding.sliderGrid.setValueRounded(settings.gridSize.toFloat())
binding.sliderGrid.addOnSliderTouchListener(this)
binding.sliderGrid.addOnChangeListener(this)
binding.checkableGroup.onCheckedChangeListener = this
}
@@ -62,10 +64,10 @@ class ListModeSelectDialog : AlertDialogFragment<DialogListModeBinding>(),
settings.listMode = mode
}
override fun onStartTrackingTouch(slider: Slider) = Unit
override fun onStopTrackingTouch(slider: Slider) {
settings.gridSize = slider.value.toInt()
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
if (fromUser) {
settings.gridSize = value.toInt()
}
}
companion object {
@@ -74,4 +76,4 @@ class ListModeSelectDialog : AlertDialogFragment<DialogListModeBinding>(),
fun show(fm: FragmentManager) = ListModeSelectDialog().show(fm, TAG)
}
}
}

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.list.ui.adapter
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.google.android.material.badge.BadgeDrawable
@@ -24,9 +23,8 @@ fun mangaGridItemAD(
clickListener: OnListItemClickListener<Manga>,
sizeResolver: ItemSizeResolver?,
) = adapterDelegateViewBinding<MangaGridModel, ListModel, ItemMangaGridBinding>(
{ inflater, parent -> ItemMangaGridBinding.inflate(inflater, parent, false) }
{ inflater, parent -> ItemMangaGridBinding.inflate(inflater, parent, false) },
) {
var badge: BadgeDrawable? = null
itemView.setOnClickListener {
@@ -35,11 +33,7 @@ fun mangaGridItemAD(
itemView.setOnLongClickListener {
clickListener.onItemLongClick(item.manga, it)
}
if (sizeResolver != null) {
itemView.updateLayoutParams {
width = sizeResolver.cellWidth
}
}
sizeResolver?.attachToView(lifecycleOwner, itemView, binding.textViewTitle)
bind { payloads ->
binding.textViewTitle.text = item.title
@@ -62,4 +56,4 @@ fun mangaGridItemAD(
badge = null
binding.imageViewCover.disposeImageRequest()
}
}
}

View File

@@ -9,4 +9,4 @@ class IntPercentLabelFormatter(context: Context) : LabelFormatter {
private val pattern = context.getString(R.string.percent_string_pattern)
override fun getFormattedValue(value: Float) = pattern.format(value.toInt().toString())
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M21,15H23V17H21V15M21,11H23V13H21V11M23,19H21V21C22,21 23,20 23,19M13,3H15V5H13V3M21,7H23V9H21V7M21,3V5H23C23,4 22,3 21,3M1,7H3V9H1V7M17,3H19V5H17V3M17,19H19V21H17V19M3,3C2,3 1,4 1,5H3V3M9,3H11V5H9V3M5,3H7V5H5V3M1,11V19A2,2 0 0,0 3,21H15V11H1M3,19L5.5,15.79L7.29,17.94L9.79,14.72L13,19H3Z" />
</vector>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M23,15H21V17H23V15M23,11H21V13H23V11M23,19H21V21C22,21 23,20 23,19M15,3H13V5H15V3M23,7H21V9H23V7M21,3V5H23C23,4 22,3 21,3M3,21H11V15H1V19A2,2 0 0,0 3,21M3,7H1V9H3V7M15,19H13V21H15V19M19,3H17V5H19V3M19,19H17V21H19V19M3,3C2,3 1,4 1,5H3V3M3,11H1V13H3V11M11,3H9V5H11V3M7,3H5V5H7V3Z" />
</vector>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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:orientation="vertical"
android:paddingHorizontal="@dimen/margin_small"
android:paddingBottom="@dimen/margin_normal">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/dragHandle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true" />
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignWithParentIfMissing="true"
android:layout_below="@id/dragHandle"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/textView_label"
android:paddingHorizontal="@dimen/margin_small"
android:paddingBottom="@dimen/margin_small"
android:singleLine="true"
android:text="@string/grid_size"
android:textAppearance="?textAppearanceTitleMedium" />
<TextView
android:id="@+id/textView_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/textView_title"
android:layout_alignParentEnd="true"
android:paddingHorizontal="@dimen/margin_small"
android:singleLine="true"
android:textAppearance="?textAppearanceLabelLarge"
tools:text="100%" />
<ImageView
android:id="@+id/button_small"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_alignTop="@id/slider_grid"
android:layout_alignBottom="@id/slider_grid"
android:layout_alignParentStart="true"
android:background="?selectableItemBackgroundBorderless"
android:padding="8dp"
android:src="@drawable/ic_size_small"
android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar" />
<ImageView
android:id="@+id/button_large"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_alignTop="@id/slider_grid"
android:layout_alignBottom="@id/slider_grid"
android:layout_alignParentEnd="true"
android:background="?selectableItemBackgroundBorderless"
android:padding="8dp"
android:src="@drawable/ic_size_large"
android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_grid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/textView_title"
android:layout_toStartOf="@id/button_large"
android:layout_toEndOf="@id/button_small"
android:stepSize="5"
android:valueFrom="50"
android:valueTo="150"
app:labelBehavior="gone"
app:tickVisible="false"
tools:value="100" />
</RelativeLayout>

View File

@@ -10,10 +10,16 @@
android:title="@string/categories_"
app:showAsAction="never" />
<item
android:id="@+id/action_grid_size"
android:orderInCategory="50"
android:title="@string/grid_size"
app:showAsAction="never" />
<item
android:id="@+id/action_clear_history"
android:orderInCategory="50"
android:title="@string/clear_history"
app:showAsAction="never" />
</menu>
</menu>

View File

@@ -17,6 +17,7 @@
<dimen name="bookmark_list_spacing">4dp</dimen>
<dimen name="chapter_list_item_height">48dp</dimen>
<dimen name="preferred_grid_width">120dp</dimen>
<dimen name="small_grid_width">92dp</dimen>
<dimen name="header_height">48dp</dimen>
<dimen name="list_footer_height_inner">36dp</dimen>
<dimen name="list_footer_height_outer">48dp</dimen>
@@ -59,4 +60,4 @@
<dimen name="fastscroll_scrollbar_padding_start">6dp</dimen>
<dimen name="fastscroll_scrollbar_padding_end">6dp</dimen>
</resources>
</resources>

View File

@@ -198,6 +198,15 @@
<item name="android:textColor">?attr/colorPrimary</item>
</style>
<style name="TextAppearance.Kotatsu.GridTitle" parent="TextAppearance.Material3.TitleSmall" />
<style name="TextAppearance.Kotatsu.GridTitle.Small" parent="TextAppearance.Material3.TitleSmall">
<item name="android:textSize">12sp</item>
<item name="android:letterSpacing">0.00714286</item>
<item name="lineHeight">14sp</item>
<item name="android:lineHeight" tools:ignore="NewApi">14sp</item>
</style>
<!-- Shapes -->
<style name="ShapeAppearanceOverlay.Kotatsu.Cover" parent="">