Temporarily replace ViewPager2 within ViewPager in favourites

pull/440/head
Koitharu 3 years ago
parent e7bd74429e
commit 83cf6aa997
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -152,7 +152,7 @@ class KotatsuApp : Application(), Configuration.Provider {
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder() FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
.penaltyDeath() .penaltyDeath()
.detectFragmentReuse() .detectFragmentReuse()
.detectWrongFragmentContainer() // .detectWrongFragmentContainer() FIXME: migrate to ViewPager2
.detectRetainInstanceUsage() .detectRetainInstanceUsage()
.detectSetUserVisibleHint() .detectSetUserVisibleHint()
.detectFragmentTagUsage() .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 package org.koitharu.kotatsu.favourites.ui.container
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.AdapterListUpdateCallback import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.fragment.app.FragmentStatePagerAdapter
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 kotlinx.coroutines.flow.FlowCollector
import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.parsers.util.replaceWith
import kotlin.coroutines.suspendCoroutine
class FavouritesContainerAdapter(fragment: Fragment) : @Suppress("DEPRECATION")
FragmentStateAdapter(fragment.childFragmentManager, fragment.viewLifecycleOwner.lifecycle), class FavouritesContainerAdapter(
TabConfigurationStrategy, fm: FragmentManager
) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT),
FlowCollector<List<FavouriteTabModel>> { FlowCollector<List<FavouriteTabModel>> {
private val differ = AsyncListDiffer( private val dataSet = ArrayList<FavouriteTabModel>()
AdapterListUpdateCallback(this),
AsyncDifferConfig.Builder(ListModelDiffCallback<FavouriteTabModel>())
.setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor())
.build(),
)
override fun getItemCount(): Int = differ.currentList.size override fun getCount(): Int = dataSet.size
override fun getItemId(position: Int): Long { override fun getItem(position: Int): Fragment {
return differ.currentList[position].id val item = dataSet[position]
}
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) return FavouritesListFragment.newInstance(item.id)
} }
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) { override fun getPageTitle(position: Int): CharSequence {
val item = differ.currentList[position] return dataSet[position].title
tab.text = item.title
tab.tag = item
} }
override suspend fun emit(value: List<FavouriteTabModel>) = suspendCoroutine { cont -> override suspend fun emit(value: List<FavouriteTabModel>) {
differ.submitList(value, ContinuationResumeRunnable(cont)) 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 package org.koitharu.kotatsu.favourites.ui.container
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
@ -7,10 +8,10 @@ import androidx.appcompat.view.ActionMode
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.util.ActionModeListener 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.core.util.ext.observe
import org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding import org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding
@ -26,16 +27,13 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
override fun onViewBindingCreated(binding: FragmentFavouritesContainerBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentFavouritesContainerBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
val adapter = FavouritesContainerAdapter(this) val adapter = FavouritesContainerAdapter(childFragmentManager)
binding.pager.adapter = adapter binding.pager.adapter = adapter
TabLayoutMediator( binding.tabs.setupWithViewPager(binding.pager)
binding.tabs,
binding.pager,
adapter,
).attach()
binding.pager.offscreenPageLimit = 1 binding.pager.offscreenPageLimit = 1
actionModeDelegate.addListener(this) actionModeDelegate.addListener(this)
viewModel.categories.observe(viewLifecycleOwner, adapter) viewModel.categories.observe(viewLifecycleOwner, adapter)
addMenuProvider(FavouritesContainerMenuProvider(binding.root.context))
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -50,6 +48,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
) )
} }
@SuppressLint("ClickableViewAccessibility")
override fun onActionModeStarted(mode: ActionMode) { override fun onActionModeStarted(mode: ActionMode) {
viewBinding?.run { viewBinding?.run {
pager.isUserInputEnabled = false pager.isUserInputEnabled = false
@ -57,6 +56,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
} }
} }
@SuppressLint("ClickableViewAccessibility")
override fun onActionModeFinished(mode: ActionMode) { override fun onActionModeFinished(mode: ActionMode) {
viewBinding?.run { viewBinding?.run {
pager.isUserInputEnabled = true 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.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.SparseIntArray
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@ -14,7 +13,6 @@ import androidx.appcompat.view.ActionMode
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.util.size
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.EditorInfoCompat
@ -276,11 +274,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
startActivity(IntentBuilder(this).manga(manga).build(), options) startActivity(IntentBuilder(this).manga(manga).build(), options)
} }
private fun onCountersChanged(counters: SparseIntArray) { private fun onCountersChanged(counters: IntArray) {
repeat(counters.size) { i -> repeat(counters.size) { i ->
val id = counters.keyAt(i) val counter = counters[i]
val counter = counters.valueAt(i) navigationDelegate.setCounterAt(i, counter)
navigationDelegate.setCounter(id, counter)
} }
} }

@ -66,6 +66,11 @@ class MainNavigationDelegate(
} ?: onNavigationItemSelected(navBar.selectedItemId) } ?: 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) { fun setCounter(@IdRes id: Int, counter: Int) {
if (counter == 0) { if (counter == 0) {
navBar.getBadge(id)?.isVisible = false navBar.getBadge(id)?.isVisible = false

@ -1,16 +1,16 @@
package org.koitharu.kotatsu.main.ui package org.koitharu.kotatsu.main.ui
import android.util.SparseIntArray
import androidx.core.util.set
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine 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.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.github.AppUpdateRepository import org.koitharu.kotatsu.core.github.AppUpdateRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@ -51,16 +51,13 @@ class MainViewModel @Inject constructor(
val counters = combine( val counters = combine(
trackingRepository.observeUpdatedMangaCount(), trackingRepository.observeUpdatedMangaCount(),
flow { emit(settings.newSources.size) }, observeNewSourcesCount(),
) { tracks, newSources -> ) { tracks, newSources ->
val a = SparseIntArray(2) intArrayOf(0, 0, newSources, tracks)
a[R.id.nav_explore] = newSources
a[R.id.nav_feed] = tracks
a
}.stateIn( }.stateIn(
scope = viewModelScope + Dispatchers.Default, scope = viewModelScope + Dispatchers.Default,
started = SharingStarted.WhileSubscribed(5000), started = SharingStarted.WhileSubscribed(5000),
initialValue = SparseIntArray(0), initialValue = IntArray(4),
) )
init { init {
@ -79,4 +76,11 @@ class MainViewModel @Inject constructor(
fun setIncognitoMode(isEnabled: Boolean) { fun setIncognitoMode(isEnabled: Boolean) {
settings.isIncognitoModeEnabled = isEnabled 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:tabGravity="start"
app:tabMode="scrollable" /> app:tabMode="scrollable" />
<androidx.viewpager2.widget.ViewPager2 <org.koitharu.kotatsu.core.ui.widgets.EnhancedViewPager
android:id="@+id/pager" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

@ -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_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_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_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_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_shelf_holder_secondary">Find what to read in the «Explore» section</string>
<string name="text_local_holder_primary">Save something first</string> <string name="text_local_holder_primary">Save something first</string>
@ -460,4 +460,5 @@
<string name="background">Background</string> <string name="background">Background</string>
<string name="data_not_restored">Data was not restored</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="data_not_restored_text">Make sure you have selected the correct backup file</string>
<string name="manage_favourites">Manage favourites</string>
</resources> </resources>

Loading…
Cancel
Save