diff --git a/.idea/codeStyles/continuationIndentation.xml b/.idea/codeStyles/continuationIndentation.xml new file mode 100644 index 0000000000000000000000000000000000000000..d61c43fa6a80ceffda4183c1226430bc82786892 --- /dev/null +++ b/.idea/codeStyles/continuationIndentation.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt index 55c4c0fd20eb956c9c3d25801b3cdaae0f879466..31994463bce5c7de2634743cb94e7543dbe8e9ab 100644 --- a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt @@ -40,17 +40,18 @@ import com.facebook.shimmer.ShimmerDrawable import com.google.android.material.button.MaterialButton import com.google.android.material.snackbar.Snackbar import foundation.e.apps.R -import foundation.e.apps.data.cleanapk.CleanApkRetrofit -import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.enums.User import foundation.e.apps.data.application.ApplicationInstaller import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.cleanapk.CleanApkRetrofit import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.enums.User import foundation.e.apps.databinding.ApplicationListItemBinding import foundation.e.apps.install.pkg.InstallerService import foundation.e.apps.ui.AppInfoFetchViewModel import foundation.e.apps.ui.MainActivityViewModel import foundation.e.apps.ui.PrivacyInfoViewModel +import foundation.e.apps.ui.applicationlist.diffUtils.ConciseAppDiffUtils import foundation.e.apps.ui.search.SearchFragmentDirections import foundation.e.apps.ui.updates.UpdatesFragmentDirections import foundation.e.apps.utils.disableInstallButton @@ -67,7 +68,7 @@ class ApplicationListRVAdapter( private val currentDestinationId: Int, private var lifecycleOwner: LifecycleOwner?, private var paidAppHandler: ((Application) -> Unit)? = null -) : ListAdapter(ApplicationDiffUtil()) { +) : ListAdapter(ConciseAppDiffUtils()) { private var optionalCategory = "" diff --git a/app/src/main/java/foundation/e/apps/ui/applicationlist/diffUtils/ConciseAppDiffUtils.kt b/app/src/main/java/foundation/e/apps/ui/applicationlist/diffUtils/ConciseAppDiffUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..48bc3341960f65284061f7b26079499518743c94 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/ui/applicationlist/diffUtils/ConciseAppDiffUtils.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2025 e Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.ui.applicationlist.diffUtils + +import androidx.recyclerview.widget.DiffUtil +import foundation.e.apps.BuildConfig +import foundation.e.apps.data.application.data.Application +import timber.log.Timber + +class ConciseAppDiffUtils : DiffUtil.ItemCallback() { + + companion object { + private val PERFORMANCE_DEBUGGING = BuildConfig.DEBUG + private const val TAG = "ConciseAppDiffUtils" + } + + override fun areItemsTheSame(oldItem: Application, newItem: Application): Boolean { + /* + * #Application._id is based on App.id which is (unimplemented/default) hashCode of the App + * class we care about package name and it's source to define if the item is same. + */ + val isSame = oldItem.package_name == newItem.package_name && + oldItem.source.ordinal == newItem.source.ordinal + + if (PERFORMANCE_DEBUGGING && !isSame) { + val oldInfo = "Old(pkg=${oldItem.package_name}, src=${oldItem.source})" + val newInfo = "New(pkg=${newItem.package_name}, src=${newItem.source})" + Timber.tag(TAG).d("item aren't same : $oldInfo vs $newInfo") + } + return isSame + } + + override fun areContentsTheSame(oldItem: Application, newItem: Application): Boolean { + /* + * most of the properties in the Application is val + * so they won't change the same way var would + * and in search screen we don't care about most fields + * like a version changes should not be considered as a + * significant changes to re render the item + */ + + val areAllPropsSame = oldItem.name == newItem.name && + oldItem.author == newItem.author && oldItem.package_name == newItem.package_name && + oldItem.category == newItem.category && oldItem.licence == newItem.licence && + oldItem.icon_image_path == newItem.icon_image_path && + oldItem.description == newItem.description && + oldItem.isPurchased == newItem.isPurchased && + oldItem.isPlaceHolder == newItem.isPlaceHolder && + oldItem.ratings.usageQualityScore == newItem.ratings.usageQualityScore && + oldItem.status.ordinal == newItem.status.ordinal + + if (PERFORMANCE_DEBUGGING && !areAllPropsSame) { + logContentChanged(oldItem, newItem) + } + return areAllPropsSame + } + + private fun logContentChanged(oldItem: Application, newItem: Application) { + val diff = StringBuilder("contents are not the same for ${oldItem.package_name}:\n") + + if (oldItem.name != newItem.name) { + diff.append(" name: '${oldItem.name}' vs '${newItem.name}'\n") + } + if (oldItem.author != newItem.author) { + diff.append(" author: '${oldItem.author}' vs '${newItem.author}'\n") + } + if (oldItem.category != newItem.category) { + diff.append(" category: '${oldItem.category}' vs '${newItem.category}'\n") + } + if (oldItem.description != newItem.description) { + diff.append(" description: (changed)\n") // descriptions might be very long + } + if (oldItem.icon_image_path != newItem.icon_image_path) { + val oldIcon = oldItem.icon_image_path + val newIcon = newItem.icon_image_path + diff.append(" icon_image_path: '$oldIcon' vs '$newIcon'\n") + } + if (oldItem.licence != newItem.licence) { + diff.append(" licence: '${oldItem.licence}' vs '${newItem.licence}'\n") + } + if (oldItem.isPurchased != newItem.isPurchased) { + diff.append(" isPurchased: ${oldItem.isPurchased} vs ${newItem.isPurchased}\n") + } + if (oldItem.isPlaceHolder != newItem.isPlaceHolder) { + diff.append(" isPlaceHolder: ${oldItem.isPlaceHolder} vs ${newItem.isPlaceHolder}\n") + } + if (oldItem.ratings.usageQualityScore != newItem.ratings.usageQualityScore) { + val oldRating = oldItem.ratings.usageQualityScore + val newRating = newItem.ratings.usageQualityScore + diff.append(" usageQualityScore: $oldRating vs $newRating\n") + } + if (oldItem.package_name != newItem.package_name) { + diff.append(" package_name: '${oldItem.package_name}' vs '${newItem.package_name}'\n") + } + if (oldItem.status.ordinal != newItem.status.ordinal) { + diff.append(" status: '${oldItem.status.name}' vs '${newItem.status.name}'\n") + } + + Timber.tag(TAG).d(diff.toString()) + } +}