UI improvements

This commit is contained in:
Koitharu
2024-04-07 18:47:04 +03:00
parent 7c4b254f08
commit 265fbc9f63
17 changed files with 252 additions and 111 deletions

View File

@@ -78,6 +78,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getInt(KEY_GRID_SIZE, 100)
set(value) = prefs.edit { putInt(KEY_GRID_SIZE, value) }
var gridSizePages: Int
get() = prefs.getInt(KEY_GRID_SIZE_PAGES, 100)
set(value) = prefs.edit { putInt(KEY_GRID_SIZE_PAGES, value) }
var historyListMode: ListMode
get() = prefs.getEnumValue(KEY_LIST_MODE_HISTORY, listMode)
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE_HISTORY, value) }
@@ -527,6 +531,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_SEARCH_HISTORY_CLEAR = "search_history_clear"
const val KEY_UPDATES_FEED_CLEAR = "updates_feed_clear"
const val KEY_GRID_SIZE = "grid_size"
const val KEY_GRID_SIZE_PAGES = "grid_size_pages"
const val KEY_REMOTE_SOURCES = "remote_sources"
const val KEY_LOCAL_STORAGE = "local_storage"
const val KEY_READER_DOUBLE_PAGES = "reader_double_pages"

View File

@@ -0,0 +1,126 @@
package org.koitharu.kotatsu.details.ui
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet.Companion.TAB_CHAPTERS
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet.Companion.TAB_PAGES
import java.lang.ref.WeakReference
class ChapterPagesMenuProvider(
private val viewModel: DetailsViewModel,
private val sheet: BaseAdaptiveSheet<*>,
private val pager: ViewPager2,
private val settings: AppSettings,
) : OnBackPressedCallback(false), MenuProvider, SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener,
Slider.OnChangeListener {
private var expandedItemRef: WeakReference<MenuItem>? = null
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
val tab = getCurrentTab()
when (tab) {
TAB_CHAPTERS -> {
menuInflater.inflate(R.menu.opt_chapters, menu)
menu.findItem(R.id.action_search)?.run {
setOnActionExpandListener(this@ChapterPagesMenuProvider)
(actionView as? SearchView)?.setupChaptersSearchView()
}
menu.findItem(R.id.action_search)?.isVisible = viewModel.isChaptersEmpty.value == false
menu.findItem(R.id.action_reversed)?.isChecked = viewModel.isChaptersReversed.value == true
menu.findItem(R.id.action_grid_view)?.isChecked = viewModel.isChaptersInGridView.value == true
}
TAB_PAGES -> {
menuInflater.inflate(R.menu.opt_pages, menu)
menu.findItem(R.id.action_grid_size)?.run {
setOnActionExpandListener(this@ChapterPagesMenuProvider)
(actionView as? Slider)?.setupPagesSizeSlider()
}
}
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_reversed -> {
viewModel.setChaptersReversed(!menuItem.isChecked)
true
}
R.id.action_grid_view -> {
viewModel.setChaptersInGridView(!menuItem.isChecked)
true
}
else -> false
}
override fun handleOnBackPressed() {
expandedItemRef?.get()?.collapseActionView()
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
expandedItemRef = WeakReference(item)
sheet.expandAndLock()
isEnabled = true
return true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
expandedItemRef = null
isEnabled = false
(item.actionView as? SearchView)?.setQuery("", false)
viewModel.performChapterSearch(null)
sheet.unlock()
return true
}
override fun onQueryTextSubmit(query: String?): Boolean = false
override fun onQueryTextChange(newText: String?): Boolean {
viewModel.performChapterSearch(newText)
return true
}
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
if (fromUser) {
settings.gridSizePages = value.toInt()
}
}
private fun SearchView.setupChaptersSearchView() {
setOnQueryTextListener(this@ChapterPagesMenuProvider)
setIconifiedByDefault(false)
queryHint = context.getString(R.string.search_chapters)
}
private fun Slider.setupPagesSizeSlider() {
valueFrom = 50f
valueTo = 150f
stepSize = 5f
isTickVisible = false
labelBehavior = LabelFormatter.LABEL_FLOATING
setLabelFormatter(IntPercentLabelFormatter(context))
setValueRounded(settings.gridSizePages.toFloat())
addOnChangeListener(this@ChapterPagesMenuProvider)
}
private fun getCurrentTab(): Int {
var page = pager.currentItem
if (page > 0 && pager.adapter?.itemCount == 2) { // no Pages page
page++ // shift
}
return page
}
}

View File

@@ -1,75 +0,0 @@
package org.koitharu.kotatsu.details.ui
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import java.lang.ref.WeakReference
class ChaptersMenuProvider2(
private val viewModel: DetailsViewModel,
private val sheet: BaseAdaptiveSheet<*>,
) : OnBackPressedCallback(false), MenuProvider, SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener {
private var searchItemRef: WeakReference<MenuItem>? = null
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_chapters, menu)
val searchMenuItem = menu.findItem(R.id.action_search)
searchMenuItem.setOnActionExpandListener(this)
val searchView = searchMenuItem.actionView as SearchView
searchView.setOnQueryTextListener(this)
searchView.setIconifiedByDefault(false)
searchView.queryHint = searchMenuItem.title
searchItemRef = WeakReference(searchMenuItem)
}
override fun onPrepareMenu(menu: Menu) {
menu.findItem(R.id.action_search)?.isVisible = viewModel.isChaptersEmpty.value == false
menu.findItem(R.id.action_reversed)?.isChecked = viewModel.isChaptersReversed.value == true
menu.findItem(R.id.action_grid_view)?.isChecked = viewModel.isChaptersInGridView.value == true
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_reversed -> {
viewModel.setChaptersReversed(!menuItem.isChecked)
true
}
R.id.action_grid_view -> {
viewModel.setChaptersInGridView(!menuItem.isChecked)
true
}
else -> false
}
override fun handleOnBackPressed() {
searchItemRef?.get()?.collapseActionView()
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
sheet.expandAndLock()
isEnabled = true
return true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
isEnabled = false
(item.actionView as? SearchView)?.setQuery("", false)
viewModel.performChapterSearch(null)
sheet.unlock()
return true
}
override fun onQueryTextSubmit(query: String?): Boolean = false
override fun onQueryTextChange(newText: String?): Boolean {
viewModel.performChapterSearch(newText)
return true
}
}

View File

@@ -92,7 +92,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.thumbnails.PagesThumbnailsSheet
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
@@ -316,19 +315,6 @@ class DetailsActivity2 :
true
}
R.id.action_pages_thumbs -> {
val history = viewModel.historyInfo.value.history
PagesThumbnailsSheet.show(
fm = supportFragmentManager,
manga = viewModel.manga.value ?: return false,
chapterId = history?.chapterId
?: viewModel.chapters.value.firstOrNull()?.chapter?.id
?: return false,
currentPage = history?.page ?: 0,
)
true
}
else -> false
}
}
@@ -606,8 +592,11 @@ class DetailsActivity2 :
append(branch.count.toString())
}
}
menu.menu.add(Menu.NONE, Menu.NONE, i, title)
val item = menu.menu.add(R.id.group_branches, Menu.NONE, i, title)
item.isCheckable = true
item.isChecked = branch.isSelected
}
menu.menu.setGroupCheckable(R.id.group_branches, true, true)
menu.setOnMenuItemClickListener {
viewModel.setSelectedBranch(branches.getOrNull(it.order)?.name)
true

View File

@@ -19,7 +19,7 @@ import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetChaptersPagesBinding
import org.koitharu.kotatsu.details.ui.ChaptersMenuProvider2
import org.koitharu.kotatsu.details.ui.ChapterPagesMenuProvider
import org.koitharu.kotatsu.details.ui.DetailsViewModel
import javax.inject.Inject
@@ -49,7 +49,7 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
binding.pager.setCurrentItem(args.getInt(ARG_TAB, settings.defaultDetailsTab), false)
binding.tabs.isVisible = adapter.itemCount > 1
val menuProvider = ChaptersMenuProvider2(viewModel, this)
val menuProvider = ChapterPagesMenuProvider(viewModel, this, binding.pager, settings)
onBackPressedDispatcher.addCallback(viewLifecycleOwner, menuProvider)
binding.toolbar.addMenuProvider(menuProvider)
@@ -84,7 +84,7 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
}
private fun onPageChanged(position: Int) {
viewBinding?.toolbar?.menuView?.isVisible = position == 0
viewBinding?.toolbar?.invalidateMenu()
}
companion object {

View File

@@ -30,14 +30,12 @@ import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.showOrHide
import org.koitharu.kotatsu.databinding.FragmentPagesBinding
import org.koitharu.kotatsu.details.ui.DetailsViewModel
import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
import javax.inject.Inject
@@ -58,7 +56,7 @@ class PagesFragment :
lateinit var settings: AppSettings
private var thumbnailsAdapter: PageThumbnailAdapter? = null
private var spanResolver: MangaListSpanResolver? = null
private var spanResolver: PagesGridSpanResolver? = null
private var scrollListener: ScrollListener? = null
private val spanSizeLookup = SpanSizeLookup()
@@ -85,19 +83,19 @@ class PagesFragment :
override fun onViewBindingCreated(binding: FragmentPagesBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
spanResolver = MangaListSpanResolver(binding.root.resources)
spanResolver = PagesGridSpanResolver(binding.root.resources)
thumbnailsAdapter = PageThumbnailAdapter(
coil = coil,
lifecycleOwner = viewLifecycleOwner,
clickListener = this@PagesFragment,
)
viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) // before rv initialization
with(binding.recyclerView) {
addItemDecoration(TypedListSpacingDecoration(context, false))
adapter = thumbnailsAdapter
setHasFixedSize(true)
isNestedScrollingEnabled = false
addOnLayoutChangeListener(spanResolver)
spanResolver?.setGridSize(settings.gridSize / 100f, this)
addOnScrollListener(ScrollListener().also { scrollListener = it })
(layoutManager as GridLayoutManager).let {
it.spanSizeLookup = spanSizeLookup
@@ -174,6 +172,11 @@ class PagesFragment :
}
}
private fun onGridScaleChanged(scale: Float) {
spanSizeLookup.invalidateCache()
spanResolver?.setGridSize(scale, requireViewBinding().recyclerView)
}
private fun onNoChaptersChanged(isNoChapters: Boolean) {
with(viewBinding ?: return) {
textViewHolder.isVisible = isNoChapters

View File

@@ -0,0 +1,59 @@
package org.koitharu.kotatsu.details.ui.pager.pages
import android.content.res.Resources
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.R
import kotlin.math.abs
import kotlin.math.roundToInt
class PagesGridSpanResolver(
resources: Resources,
) : View.OnLayoutChangeListener {
var spanCount = 3
private set
private val gridWidth = resources.getDimension(R.dimen.preferred_grid_width)
private val spacing = resources.getDimension(R.dimen.grid_spacing)
private var cellWidth = -1f
override fun onLayoutChange(
v: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int,
) {
if (cellWidth <= 0f) {
return
}
val rv = v as? RecyclerView ?: return
val width = abs(right - left)
if (width == 0) {
return
}
resolveGridSpanCount(width)
(rv.layoutManager as? GridLayoutManager)?.spanCount = spanCount
}
fun setGridSize(scaleFactor: Float, rv: RecyclerView) {
cellWidth = (gridWidth * scaleFactor) + spacing
val lm = rv.layoutManager as? GridLayoutManager ?: return
val innerWidth = lm.width - lm.paddingEnd - lm.paddingStart
if (innerWidth > 0) {
resolveGridSpanCount(innerWidth)
lm.spanCount = spanCount
}
}
private fun resolveGridSpanCount(width: Int) {
val estimatedCount = (width / cellWidth).roundToInt()
spanCount = estimatedCount.coerceAtLeast(2)
}
}

View File

@@ -1,11 +1,15 @@
package org.koitharu.kotatsu.details.ui.pager.pages
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.firstNotNull
import org.koitharu.kotatsu.details.data.MangaDetails
@@ -18,6 +22,7 @@ import javax.inject.Inject
@HiltViewModel
class PagesViewModel @Inject constructor(
private val chaptersLoader: ChaptersLoader,
private val settings: AppSettings,
) : BaseViewModel() {
private var loadingJob: Job? = null
@@ -29,6 +34,12 @@ class PagesViewModel @Inject constructor(
val isLoadingUp = MutableStateFlow(false)
val isLoadingDown = MutableStateFlow(false)
val gridScale = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default,
key = AppSettings.KEY_GRID_SIZE_PAGES,
valueProducer = { gridSizePages / 100f },
)
init {
loadingJob = launchLoadingJob(Dispatchers.Default) {
val firstState = state.firstNotNull()

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.favourites.ui.categories.select.adapter
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
@@ -23,6 +24,6 @@ fun mangaCategoryAD(
binding.checkableImageView.setChecked(item.isChecked, ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED in payloads)
binding.textViewTitle.text = item.category.title
binding.imageViewTracker.isVisible = item.category.isTrackingEnabled && item.isTrackerEnabled
binding.imageViewVisible.isVisible = item.category.isVisibleInLibrary
binding.imageViewHidden.isGone = item.category.isVisibleInLibrary
}
}

View File

@@ -2,7 +2,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:tint="?attr/colorPrimary"
android:tint="@color/common_green"
android:viewportWidth="24"
android:viewportHeight="24">
<path

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="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M12 18.88A1 1 0 0 1 11.71 19.71A1 1 0 0 1 10.3 19.71L6.3 15.71A1 1 0 0 1 6 14.87V9.75L1.21 3.62A1 1 0 0 1 1.38 2.22A1 1 0 0 1 2 2H16A1 1 0 0 1 16.62 2.22A1 1 0 0 1 16.79 3.62L12 9.75V18.88M4 4L8 9.06V14.58L10 16.58V9.05L14 4M13 16L18 21L23 16Z" />
</vector>

View File

@@ -20,7 +20,7 @@
android:layout_margin="@dimen/screen_padding"
android:background="@drawable/bg_circle_button"
android:contentDescription="@string/back"
android:elevation="4dp"
android:elevation="@dimen/m3_sys_elevation_level1"
android:scaleType="center"
android:src="?homeAsUpIndicator" />

View File

@@ -35,7 +35,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:icon="@drawable/ic_reorder"
app:icon="@drawable/ic_filter_menu"
tools:text="@string/newest"
tools:visibility="visible" />

View File

@@ -17,12 +17,13 @@
android:text="@string/location"
android:textAppearance="?textAppearanceLabelMedium" />
<TextView
<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
android:id="@+id/textView_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAppearance="?textAppearanceBodyMedium"
android:textIsSelectable="true"
tools:text="/storage/emulated/0/Manga/lorem.cbz" />
<org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView

View File

@@ -46,13 +46,13 @@
android:layout_marginStart="4dp"
android:contentDescription="@string/check_for_new_chapters"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/imageView_visible"
app:layout_constraintEnd_toStartOf="@id/imageView_hidden"
app:layout_constraintStart_toEndOf="@id/textView_title"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_notification" />
<ImageView
android:id="@+id/imageView_visible"
android:id="@+id/imageView_hidden"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="4dp"
@@ -61,6 +61,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_tracker"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_eye" />
app:srcCompat="@drawable/ic_eye_off" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_grid_size"
android:icon="@drawable/ic_size_large"
android:orderInCategory="10"
android:title="@string/grid_size"
app:actionViewClass="com.google.android.material.slider.Slider"
app:showAsAction="ifRoom|collapseActionView" />
</menu>

View File

@@ -7,11 +7,6 @@
android:icon="@drawable/ic_incognito"
android:title="@string/incognito_mode" />
<item
android:id="@+id/action_pages_thumbs"
android:icon="@drawable/ic_grid"
android:title="@string/pages" />
<item
android:id="@+id/action_forget"
android:icon="@drawable/ic_delete"