Temporarily replace ViewPager2 within ViewPager in favourites
This commit is contained in:
@@ -152,7 +152,7 @@ class KotatsuApp : Application(), Configuration.Provider {
|
||||
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
|
||||
.penaltyDeath()
|
||||
.detectFragmentReuse()
|
||||
.detectWrongFragmentContainer()
|
||||
// .detectWrongFragmentContainer() FIXME: migrate to ViewPager2
|
||||
.detectRetainInstanceUsage()
|
||||
.detectSetUserVisibleHint()
|
||||
.detectFragmentTagUsage()
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.koitharu.kotatsu.core.ui.widgets
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
class EnhancedViewPager @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
) : ViewPager(context, attrs) {
|
||||
|
||||
var isUserInputEnabled: Boolean = true
|
||||
set(value) {
|
||||
field = value
|
||||
if (!value) {
|
||||
cancelPendingInputEvents()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
return isUserInputEnabled && super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
||||
return isUserInputEnabled && super.onInterceptTouchEvent(event)
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,33 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.container
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||
|
||||
class FavouritesContainerAdapter(fragment: Fragment) :
|
||||
FragmentStateAdapter(fragment.childFragmentManager, fragment.viewLifecycleOwner.lifecycle),
|
||||
TabConfigurationStrategy,
|
||||
@Suppress("DEPRECATION")
|
||||
class FavouritesContainerAdapter(
|
||||
fm: FragmentManager
|
||||
) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT),
|
||||
FlowCollector<List<FavouriteTabModel>> {
|
||||
|
||||
private val differ = AsyncListDiffer(
|
||||
AdapterListUpdateCallback(this),
|
||||
AsyncDifferConfig.Builder(ListModelDiffCallback<FavouriteTabModel>())
|
||||
.setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor())
|
||||
.build(),
|
||||
)
|
||||
private val dataSet = ArrayList<FavouriteTabModel>()
|
||||
|
||||
override fun getItemCount(): Int = differ.currentList.size
|
||||
override fun getCount(): Int = dataSet.size
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return differ.currentList[position].id
|
||||
}
|
||||
|
||||
override fun containsItem(itemId: Long): Boolean {
|
||||
return differ.currentList.any { x -> x.id == itemId }
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
val item = differ.currentList[position]
|
||||
override fun getItem(position: Int): Fragment {
|
||||
val item = dataSet[position]
|
||||
return FavouritesListFragment.newInstance(item.id)
|
||||
}
|
||||
|
||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||
val item = differ.currentList[position]
|
||||
tab.text = item.title
|
||||
tab.tag = item
|
||||
override fun getPageTitle(position: Int): CharSequence {
|
||||
return dataSet[position].title
|
||||
}
|
||||
|
||||
override suspend fun emit(value: List<FavouriteTabModel>) = suspendCoroutine { cont ->
|
||||
differ.submitList(value, ContinuationResumeRunnable(cont))
|
||||
override suspend fun emit(value: List<FavouriteTabModel>) {
|
||||
dataSet.replaceWith(value)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.container
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
// FIXME migrate to ViewPager2 in FavouritesContainerFragment
|
||||
class FavouritesContainerAdapter2(fragment: Fragment) :
|
||||
FragmentStateAdapter(fragment.childFragmentManager, fragment.viewLifecycleOwner.lifecycle),
|
||||
TabConfigurationStrategy,
|
||||
FlowCollector<List<FavouriteTabModel>> {
|
||||
|
||||
private val differ = AsyncListDiffer(
|
||||
AdapterListUpdateCallback(this),
|
||||
AsyncDifferConfig.Builder(ListModelDiffCallback<FavouriteTabModel>())
|
||||
.setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor())
|
||||
.build(),
|
||||
)
|
||||
|
||||
override fun getItemCount(): Int = differ.currentList.size
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return differ.currentList[position].id
|
||||
}
|
||||
|
||||
override fun containsItem(itemId: Long): Boolean {
|
||||
return differ.currentList.any { x -> x.id == itemId }
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
val item = differ.currentList[position]
|
||||
return FavouritesListFragment.newInstance(item.id)
|
||||
}
|
||||
|
||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||
val item = differ.currentList[position]
|
||||
tab.text = item.title
|
||||
tab.tag = item
|
||||
}
|
||||
|
||||
override suspend fun emit(value: List<FavouriteTabModel>) = suspendCoroutine { cont ->
|
||||
differ.submitList(value, ContinuationResumeRunnable(cont))
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.container
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
@@ -7,10 +8,10 @@ import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.core.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.core.ui.util.ActionModeListener
|
||||
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding
|
||||
|
||||
@@ -26,16 +27,13 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
|
||||
|
||||
override fun onViewBindingCreated(binding: FragmentFavouritesContainerBinding, savedInstanceState: Bundle?) {
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
val adapter = FavouritesContainerAdapter(this)
|
||||
val adapter = FavouritesContainerAdapter(childFragmentManager)
|
||||
binding.pager.adapter = adapter
|
||||
TabLayoutMediator(
|
||||
binding.tabs,
|
||||
binding.pager,
|
||||
adapter,
|
||||
).attach()
|
||||
binding.tabs.setupWithViewPager(binding.pager)
|
||||
binding.pager.offscreenPageLimit = 1
|
||||
actionModeDelegate.addListener(this)
|
||||
viewModel.categories.observe(viewLifecycleOwner, adapter)
|
||||
addMenuProvider(FavouritesContainerMenuProvider(binding.root.context))
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -50,6 +48,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onActionModeStarted(mode: ActionMode) {
|
||||
viewBinding?.run {
|
||||
pager.isUserInputEnabled = false
|
||||
@@ -57,6 +56,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onActionModeFinished(mode: ActionMode) {
|
||||
viewBinding?.run {
|
||||
pager.isUserInputEnabled = true
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.container
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
|
||||
|
||||
class FavouritesContainerMenuProvider(
|
||||
private val context: Context,
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_favourites_container, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_manage -> {
|
||||
context.startActivity(FavouriteCategoriesActivity.newIntent(context))
|
||||
}
|
||||
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import android.Manifest
|
||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.SparseIntArray
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
@@ -14,7 +13,6 @@ import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.util.size
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.inputmethod.EditorInfoCompat
|
||||
@@ -276,11 +274,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
startActivity(IntentBuilder(this).manga(manga).build(), options)
|
||||
}
|
||||
|
||||
private fun onCountersChanged(counters: SparseIntArray) {
|
||||
private fun onCountersChanged(counters: IntArray) {
|
||||
repeat(counters.size) { i ->
|
||||
val id = counters.keyAt(i)
|
||||
val counter = counters.valueAt(i)
|
||||
navigationDelegate.setCounter(id, counter)
|
||||
val counter = counters[i]
|
||||
navigationDelegate.setCounterAt(i, counter)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,11 @@ class MainNavigationDelegate(
|
||||
} ?: onNavigationItemSelected(navBar.selectedItemId)
|
||||
}
|
||||
|
||||
fun setCounterAt(position: Int, counter: Int) {
|
||||
val id = navBar.menu.getItem(position).itemId
|
||||
setCounter(id, counter)
|
||||
}
|
||||
|
||||
fun setCounter(@IdRes id: Int, counter: Int) {
|
||||
if (counter == 0) {
|
||||
navBar.getBadge(id)?.isVisible = false
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package org.koitharu.kotatsu.main.ui
|
||||
|
||||
import android.util.SparseIntArray
|
||||
import androidx.core.util.set
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||
import org.koitharu.kotatsu.core.github.AppUpdateRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
@@ -51,16 +51,13 @@ class MainViewModel @Inject constructor(
|
||||
|
||||
val counters = combine(
|
||||
trackingRepository.observeUpdatedMangaCount(),
|
||||
flow { emit(settings.newSources.size) },
|
||||
observeNewSourcesCount(),
|
||||
) { tracks, newSources ->
|
||||
val a = SparseIntArray(2)
|
||||
a[R.id.nav_explore] = newSources
|
||||
a[R.id.nav_feed] = tracks
|
||||
a
|
||||
intArrayOf(0, 0, newSources, tracks)
|
||||
}.stateIn(
|
||||
scope = viewModelScope + Dispatchers.Default,
|
||||
started = SharingStarted.WhileSubscribed(5000),
|
||||
initialValue = SparseIntArray(0),
|
||||
initialValue = IntArray(4),
|
||||
)
|
||||
|
||||
init {
|
||||
@@ -79,4 +76,11 @@ class MainViewModel @Inject constructor(
|
||||
fun setIncognitoMode(isEnabled: Boolean) {
|
||||
settings.isIncognitoModeEnabled = isEnabled
|
||||
}
|
||||
|
||||
private fun observeNewSourcesCount() = settings.observe()
|
||||
.filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN }
|
||||
.onStart { emit("") }
|
||||
.map { settings.newSources.size }
|
||||
.distinctUntilChanged()
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
app:tabGravity="start"
|
||||
app:tabMode="scrollable" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
<org.koitharu.kotatsu.core.ui.widgets.EnhancedViewPager
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
11
app/src/main/res/menu/opt_favourites_container.xml
Normal file
11
app/src/main/res/menu/opt_favourites_container.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_manage"
|
||||
android:orderInCategory="48"
|
||||
android:title="@string/manage_favourites"
|
||||
android:titleCondensed="@string/manage" />
|
||||
|
||||
</menu>
|
||||
@@ -101,7 +101,7 @@
|
||||
<string name="text_empty_holder_primary">It\'s kind of empty here…</string>
|
||||
<string name="text_search_holder_secondary">Try to reformulate the query.</string>
|
||||
<string name="text_history_holder_primary">What you read will be displayed here</string>
|
||||
<string name="text_history_holder_secondary">Find what to read in side menu.</string>
|
||||
<string name="text_history_holder_secondary">Find what to read in the «Explore» section</string>
|
||||
<string name="text_shelf_holder_primary">Your manga will be displayed here</string>
|
||||
<string name="text_shelf_holder_secondary">Find what to read in the «Explore» section</string>
|
||||
<string name="text_local_holder_primary">Save something first</string>
|
||||
@@ -460,4 +460,5 @@
|
||||
<string name="background">Background</string>
|
||||
<string name="data_not_restored">Data was not restored</string>
|
||||
<string name="data_not_restored_text">Make sure you have selected the correct backup file</string>
|
||||
<string name="manage_favourites">Manage favourites</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user