diff --git a/build.gradle b/build.gradle index 38c8722a..aec691ca 100644 --- a/build.gradle +++ b/build.gradle @@ -151,6 +151,7 @@ dependencies { implementation("com.mikepenz:fastadapter:5.6.0") implementation("com.mikepenz:fastadapter-extensions-diff:5.6.0") implementation("com.mikepenz:fastadapter-extensions-binding:5.6.0") + implementation("com.mikepenz:fastadapter-extensions-paged:5.6.0") // Coil implementation 'io.coil-kt:coil:1.4.0' diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 62769f58..9ed20d0d 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -2,6 +2,7 @@ package com.looker.droidify.database import android.database.Cursor import android.os.CancellationSignal +import androidx.paging.DataSource import androidx.room.* import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteQuery @@ -175,14 +176,13 @@ interface ProductDao : BaseDao { @RawQuery(observedEntities = [Product::class]) fun queryList( query: SupportSQLiteQuery - ): List + ): DataSource.Factory // TODO optimize and simplify - @Transaction fun queryList( installed: Boolean, updates: Boolean, searchQuery: String, section: ProductItem.Section, order: ProductItem.Order, numberOfItems: Int = 0 - ): List { + ): DataSource.Factory { val builder = QueryBuilder() val signatureMatches = """installed.${ROW_SIGNATURE} IS NOT NULL AND diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt index 679f8a8b..e517aadf 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/ExploreFragment.kt @@ -11,9 +11,13 @@ import com.looker.droidify.database.Product import com.looker.droidify.databinding.FragmentExploreXBinding import com.looker.droidify.entity.Repository import com.looker.droidify.ui.adapters.AppListAdapter +import com.looker.droidify.ui.items.VAppItem import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX +import com.looker.droidify.utility.PRODUCT_ASYNC_DIFFER_CONFIG import com.looker.droidify.utility.RxUtils import com.looker.droidify.utility.extension.resources.getDrawableCompat +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.paged.PagedModelAdapter import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import me.zhanghai.android.fastscroll.FastScrollerBuilder @@ -23,6 +27,8 @@ class ExploreFragment : MainNavFragmentX() { override lateinit var viewModel: MainNavFragmentViewModelX private lateinit var binding: FragmentExploreXBinding + private lateinit var appsItemAdapter: PagedModelAdapter + private var appsFastAdapter: FastAdapter? = null override val source = Source.AVAILABLE @@ -40,13 +46,19 @@ class ExploreFragment : MainNavFragmentX() { viewModel = ViewModelProvider(this, viewModelFactory) .get(MainNavFragmentViewModelX::class.java) + appsItemAdapter = PagedModelAdapter(PRODUCT_ASYNC_DIFFER_CONFIG) { + it.data_item?.let { item -> + VAppItem(item, repositories[it.repository_id]) + } + } + + appsFastAdapter = FastAdapter.with(appsItemAdapter) + appsFastAdapter?.setHasStableIds(true) binding.recyclerView.apply { - layoutManager = LinearLayoutManager(context) - isMotionEventSplittingEnabled = false - isVerticalScrollBarEnabled = false + layoutManager = LinearLayoutManager(requireContext()) setHasFixedSize(true) recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30) - adapter = AppListAdapter { mainActivityX.navigateProduct(it.packageName) } + adapter = appsFastAdapter FastScrollerBuilder(this) .useMd2Style() .setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb)) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt index 1a5e6832..05078b03 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/InstalledFragment.kt @@ -8,16 +8,18 @@ import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.looker.droidify.R +import com.looker.droidify.database.Product import com.looker.droidify.databinding.FragmentInstalledXBinding -import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository +import com.looker.droidify.ui.adapters.AppListAdapter import com.looker.droidify.ui.items.HAppItem import com.looker.droidify.ui.items.VAppItem import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX +import com.looker.droidify.utility.PRODUCT_ASYNC_DIFFER_CONFIG import com.looker.droidify.utility.RxUtils import com.looker.droidify.utility.extension.resources.getDrawableCompat import com.mikepenz.fastadapter.FastAdapter -import com.mikepenz.fastadapter.adapters.ItemAdapter +import com.mikepenz.fastadapter.paged.PagedModelAdapter import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import me.zhanghai.android.fastscroll.FastScrollerBuilder @@ -27,9 +29,9 @@ class InstalledFragment : MainNavFragmentX() { override lateinit var viewModel: MainNavFragmentViewModelX private lateinit var binding: FragmentInstalledXBinding - private val installedItemAdapter = ItemAdapter() + private lateinit var installedItemAdapter: PagedModelAdapter private var installedFastAdapter: FastAdapter? = null - private val updatedItemAdapter = ItemAdapter() + private lateinit var updatedItemAdapter: PagedModelAdapter private var updatedFastAdapter: FastAdapter? = null override val source = Source.INSTALLED @@ -48,12 +50,23 @@ class InstalledFragment : MainNavFragmentX() { viewModel = ViewModelProvider(this, viewModelFactory) .get(MainNavFragmentViewModelX::class.java) + installedItemAdapter = PagedModelAdapter(PRODUCT_ASYNC_DIFFER_CONFIG) { + it.data_item?.let { item -> + VAppItem(item, repositories[it.repository_id]) + } + } + updatedItemAdapter = PagedModelAdapter(PRODUCT_ASYNC_DIFFER_CONFIG) { + it.data_item?.let { item -> + // TODO filter for only updated apps and add placeholder + HAppItem(item, repositories[it.repository_id]) + } + } + installedFastAdapter = FastAdapter.with(installedItemAdapter) installedFastAdapter?.setHasStableIds(true) binding.installedRecycler.apply { layoutManager = LinearLayoutManager(requireContext()) - isMotionEventSplittingEnabled = false - isVerticalScrollBarEnabled = false + recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30) adapter = installedFastAdapter FastScrollerBuilder(this) .useMd2Style() @@ -64,13 +77,8 @@ class InstalledFragment : MainNavFragmentX() { updatedFastAdapter?.setHasStableIds(true) binding.updatedRecycler.apply { layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) - isMotionEventSplittingEnabled = false - isVerticalScrollBarEnabled = false + recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30) adapter = updatedFastAdapter - FastScrollerBuilder(this) - .useMd2Style() - .setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb)) - .build() } return binding.root } diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt index 8c932278..284b3a49 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/LatestFragment.kt @@ -8,16 +8,18 @@ import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.looker.droidify.R +import com.looker.droidify.database.Product import com.looker.droidify.databinding.FragmentLatestXBinding -import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository +import com.looker.droidify.ui.adapters.AppListAdapter import com.looker.droidify.ui.items.HAppItem import com.looker.droidify.ui.items.VAppItem import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX +import com.looker.droidify.utility.PRODUCT_ASYNC_DIFFER_CONFIG import com.looker.droidify.utility.RxUtils import com.looker.droidify.utility.extension.resources.getDrawableCompat import com.mikepenz.fastadapter.FastAdapter -import com.mikepenz.fastadapter.adapters.ItemAdapter +import com.mikepenz.fastadapter.paged.PagedModelAdapter import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import me.zhanghai.android.fastscroll.FastScrollerBuilder @@ -27,11 +29,12 @@ class LatestFragment : MainNavFragmentX() { override lateinit var viewModel: MainNavFragmentViewModelX private lateinit var binding: FragmentLatestXBinding - private val updatedItemAdapter = ItemAdapter() + private lateinit var updatedItemAdapter: PagedModelAdapter private var updatedFastAdapter: FastAdapter? = null - private val newItemAdapter = ItemAdapter() + private lateinit var newItemAdapter: PagedModelAdapter private var newFastAdapter: FastAdapter? = null + // TODO replace the source with one that get a certain amount of updated apps override val source = Source.AVAILABLE private var repositories: Map = mapOf() @@ -48,12 +51,23 @@ class LatestFragment : MainNavFragmentX() { viewModel = ViewModelProvider(this, viewModelFactory) .get(MainNavFragmentViewModelX::class.java) + updatedItemAdapter = PagedModelAdapter(PRODUCT_ASYNC_DIFFER_CONFIG) { + it.data_item?.let { item -> + VAppItem(item, repositories[it.repository_id]) + } + } + newItemAdapter = PagedModelAdapter(PRODUCT_ASYNC_DIFFER_CONFIG) { + it.data_item?.let { item -> + // TODO filter for only new apps and add placeholder + HAppItem(item, repositories[it.repository_id]) + } + } + updatedFastAdapter = FastAdapter.with(updatedItemAdapter) updatedFastAdapter?.setHasStableIds(true) binding.updatedRecycler.apply { layoutManager = LinearLayoutManager(requireContext()) - isMotionEventSplittingEnabled = false - isVerticalScrollBarEnabled = false + recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30) adapter = updatedFastAdapter FastScrollerBuilder(this) .useMd2Style() @@ -64,13 +78,8 @@ class LatestFragment : MainNavFragmentX() { newFastAdapter?.setHasStableIds(true) binding.newRecycler.apply { layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) - isMotionEventSplittingEnabled = false - isVerticalScrollBarEnabled = false + recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30) adapter = newFastAdapter - FastScrollerBuilder(this) - .useMd2Style() - .setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb)) - .build() } return binding.root } diff --git a/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt b/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt index b27ff37f..bee80c1f 100644 --- a/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/viewmodels/MainNavFragmentViewModelX.kt @@ -1,14 +1,18 @@ package com.looker.droidify.ui.viewmodels -import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import androidx.paging.DataSource +import androidx.paging.LivePagedListBuilder +import androidx.paging.PagedList import com.looker.droidify.database.DatabaseX import com.looker.droidify.database.Product import com.looker.droidify.entity.ProductItem import com.looker.droidify.ui.fragments.Request import com.looker.droidify.ui.fragments.Source +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -68,15 +72,36 @@ class MainNavFragmentViewModelX(val db: DatabaseX, source: Source) : ViewModel() } } - var productsList = MediatorLiveData>() + var productsList: LiveData> + + init { + + val pagedListConfig = PagedList.Config.Builder() + .setPageSize(30) + .setPrefetchDistance(30) + .setEnablePlaceholders(false) + .build() + val request = request(source) + + productsList = LivePagedListBuilder( + db.productDao.queryList( + installed = request.installed, + updates = request.updates, + searchQuery = request.searchQuery, + section = request.section, + order = request.order, + numberOfItems = request.numberOfItems + ), pagedListConfig + ).build() + } fun fillList(source: Source) { - viewModelScope.launch { - productsList.value = query(request(source))?.toMutableList() + CoroutineScope(Dispatchers.Default).launch { + // productsList = query(request(source)) } } - private suspend fun query(request: Request): List? { + private suspend fun query(request: Request): DataSource.Factory { return withContext(Dispatchers.Default) { db.productDao .queryList( diff --git a/src/main/kotlin/com/looker/droidify/utility/Utils.kt b/src/main/kotlin/com/looker/droidify/utility/Utils.kt index 0a4798a9..1216913d 100644 --- a/src/main/kotlin/com/looker/droidify/utility/Utils.kt +++ b/src/main/kotlin/com/looker/droidify/utility/Utils.kt @@ -10,6 +10,8 @@ import android.content.res.Configuration import android.database.Cursor import android.graphics.drawable.Drawable import android.os.Build +import androidx.recyclerview.widget.AsyncDifferConfig +import androidx.recyclerview.widget.DiffUtil import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonParser import com.looker.droidify.* @@ -234,4 +236,23 @@ fun jsonGenerate(callback: (JsonGenerator) -> Unit): ByteArray { val outputStream = ByteArrayOutputStream() Json.factory.createGenerator(outputStream).use { it.writeDictionary(callback) } return outputStream.toByteArray() -} \ No newline at end of file +} + +val PRODUCT_ASYNC_DIFFER_CONFIG + get() = AsyncDifferConfig.Builder(object : + DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: com.looker.droidify.database.Product, + newItem: com.looker.droidify.database.Product + ): Boolean { + return oldItem.repository_id == newItem.repository_id + && oldItem.package_name == newItem.package_name + } + + override fun areContentsTheSame( + oldItem: com.looker.droidify.database.Product, + newItem: com.looker.droidify.database.Product + ): Boolean { + return oldItem.data_item == newItem.data_item + } + }).build() \ No newline at end of file