diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/kotlin/org/koitharu/kotatsu/KotatsuApp.kt index ae4926462..59fbbc62c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/KotatsuApp.kt @@ -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() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/EnhancedViewPager.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/EnhancedViewPager.kt new file mode 100644 index 000000000..08d280070 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/EnhancedViewPager.kt @@ -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) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter.kt index 8e9f6549d..c9daa207d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter.kt @@ -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> { - private val differ = AsyncListDiffer( - AdapterListUpdateCallback(this), - AsyncDifferConfig.Builder(ListModelDiffCallback()) - .setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor()) - .build(), - ) + private val dataSet = ArrayList() - 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) = suspendCoroutine { cont -> - differ.submitList(value, ContinuationResumeRunnable(cont)) + override suspend fun emit(value: List) { + dataSet.replaceWith(value) + notifyDataSetChanged() } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter2.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter2.kt new file mode 100644 index 000000000..4afab1c9f --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter2.kt @@ -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> { + + private val differ = AsyncListDiffer( + AdapterListUpdateCallback(this), + AsyncDifferConfig.Builder(ListModelDiffCallback()) + .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) = suspendCoroutine { cont -> + differ.submitList(value, ContinuationResumeRunnable(cont)) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt index b8d5ca256..a2c43c70d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt @@ -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 { + context.startActivity(FavouriteCategoriesActivity.newIntent(context)) + } + + else -> return false + } + return true + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt index 0a3a2d35c..69ea8d6f8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -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(), 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) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt index d461d49e3..9397bfee8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt @@ -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 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt index 6473bbc96..4ae301c11 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt @@ -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() + } diff --git a/app/src/main/res/layout/fragment_favourites_container.xml b/app/src/main/res/layout/fragment_favourites_container.xml index 8d1ff7c9c..6f99e5e19 100644 --- a/app/src/main/res/layout/fragment_favourites_container.xml +++ b/app/src/main/res/layout/fragment_favourites_container.xml @@ -13,7 +13,7 @@ app:tabGravity="start" app:tabMode="scrollable" /> - diff --git a/app/src/main/res/menu/opt_favourites_container.xml b/app/src/main/res/menu/opt_favourites_container.xml new file mode 100644 index 000000000..703c47fe8 --- /dev/null +++ b/app/src/main/res/menu/opt_favourites_container.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c7cc01bb8..e27cdda2f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -101,7 +101,7 @@ It\'s kind of empty here… Try to reformulate the query. What you read will be displayed here - Find what to read in side menu. + Find what to read in the «Explore» section Your manga will be displayed here Find what to read in the «Explore» section Save something first @@ -460,4 +460,5 @@ Background Data was not restored Make sure you have selected the correct backup file + Manage favourites