From 2c9af08e8cd80ac80e6eee35a33af4134b49c965 Mon Sep 17 00:00:00 2001 From: Michatec Date: Tue, 24 Mar 2026 17:31:51 +0100 Subject: [PATCH] - Refactor code to use idiomatic Kotlin functions like `isNullOrEmpty()`, `associateBy`, and `appendRange` - Increase `DiffUtil` threshold in `CursorRecyclerAdapter` to 500 and optimize equality checks - Simplify locale configuration by removing legacy Android SDK version checks - Remove unnecessary `@SuppressLint` annotations and `@Volatile` modifiers - Improve performance of collection operations in `ProductFragment` and `Preferences` using `associateBy` and `firstOrNull` - Suppress deprecation warnings for `getOpacity` in `DrawableWrapper` and `TabsFragment` - Clean up service binding logic in `Connection.kt` with better type casting --- .../com/michatec/store/content/Preferences.kt | 8 +++++--- .../michatec/store/graphics/DrawableWrapper.kt | 1 + .../com/michatec/store/index/IndexHandler.kt | 2 +- .../com/michatec/store/screen/ProductAdapter.kt | 6 +++--- .../com/michatec/store/screen/ProductFragment.kt | 15 ++++++++------- .../com/michatec/store/screen/ProductsAdapter.kt | 6 ++++++ .../com/michatec/store/screen/TabsFragment.kt | 1 + .../com/michatec/store/service/Connection.kt | 8 ++++---- .../com/michatec/store/service/SyncService.kt | 2 +- .../kotlin/com/michatec/store/utility/RxUtils.kt | 2 +- .../kotlin/com/michatec/store/utility/Utils.kt | 11 ++--------- .../store/widget/CursorRecyclerAdapter.kt | 6 ++---- 12 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/com/michatec/store/content/Preferences.kt b/src/main/kotlin/com/michatec/store/content/Preferences.kt index c0c65bf..2c11881 100644 --- a/src/main/kotlin/com/michatec/store/content/Preferences.kt +++ b/src/main/kotlin/com/michatec/store/content/Preferences.kt @@ -16,10 +16,12 @@ object Preferences { private val subject = PublishSubject.create>() - private val keys = sequenceOf(Key.AutoSync, Key.IncompatibleVersions, Key.ProxyHost, Key.ProxyPort, Key.ProxyType, - Key.SortOrder, Key.Theme, Key.UpdateNotify, Key.UpdateUnstable).map { Pair(it.name, it) }.toMap() + private val keys = sequenceOf( + Key.AutoSync, Key.IncompatibleVersions, Key.ProxyHost, Key.ProxyPort, Key.ProxyType, + Key.SortOrder, Key.Theme, Key.UpdateNotify, Key.UpdateUnstable + ).associateBy { it.name } - private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, keyString -> + private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, keyString -> keys[keyString]?.let(subject::onNext) } diff --git a/src/main/kotlin/com/michatec/store/graphics/DrawableWrapper.kt b/src/main/kotlin/com/michatec/store/graphics/DrawableWrapper.kt index 9a7a91f..9a9e212 100644 --- a/src/main/kotlin/com/michatec/store/graphics/DrawableWrapper.kt +++ b/src/main/kotlin/com/michatec/store/graphics/DrawableWrapper.kt @@ -74,5 +74,6 @@ open class DrawableWrapper(val drawable: Drawable): Drawable() { DrawableCompat.setHotspotBounds(drawable, left, top, right, bottom) } + @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") override fun getOpacity(): Int = drawable.opacity } diff --git a/src/main/kotlin/com/michatec/store/index/IndexHandler.kt b/src/main/kotlin/com/michatec/store/index/IndexHandler.kt index 87248a7..2fb3457 100644 --- a/src/main/kotlin/com/michatec/store/index/IndexHandler.kt +++ b/src/main/kotlin/com/michatec/store/index/IndexHandler.kt @@ -260,6 +260,6 @@ class IndexHandler(private val repositoryId: Long, private val callback: Callbac override fun characters(ch: CharArray, start: Int, length: Int) { super.characters(ch, start, length) - contentBuilder.append(ch, start, length) + contentBuilder.appendRange(ch, start, start + length) } } diff --git a/src/main/kotlin/com/michatec/store/screen/ProductAdapter.kt b/src/main/kotlin/com/michatec/store/screen/ProductAdapter.kt index 3986e9e..533d9b6 100644 --- a/src/main/kotlin/com/michatec/store/screen/ProductAdapter.kt +++ b/src/main/kotlin/com/michatec/store/screen/ProductAdapter.kt @@ -362,10 +362,10 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } } + @SuppressLint("ClickableViewAccessibility") private open class OverlappingViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { init { // Block touch events if touched above negative margin - @SuppressLint("ClickableViewAccessibility") itemView.setOnTouchListener { v, event -> val top = (v.layoutParams as ViewGroup.MarginLayoutParams).topMargin event.action == MotionEvent.ACTION_DOWN && top < 0 && event.y < -top @@ -373,6 +373,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } } + @SuppressLint("SetTextI18n") private class LinkViewHolder(itemView: View): OverlappingViewHolder(itemView) { companion object { private val measurement = Measurement() @@ -384,7 +385,6 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) init { val margin = measurement.invalidate(itemView.resources) { - @SuppressLint("SetTextI18n") text.text = "measure" link.visibility = View.GONE measurement.measure(itemView) @@ -397,6 +397,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } } + @SuppressLint("SetTextI18n") private class PermissionsViewHolder(itemView: View): OverlappingViewHolder(itemView) { companion object { private val measurement = Measurement() @@ -407,7 +408,6 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) init { val margin = measurement.invalidate(itemView.resources) { - @SuppressLint("SetTextI18n") text.text = "measure" measurement.measure(itemView) ((itemView.measuredHeight - icon.measuredHeight) / 2f).roundToInt() diff --git a/src/main/kotlin/com/michatec/store/screen/ProductFragment.kt b/src/main/kotlin/com/michatec/store/screen/ProductFragment.kt index e2526ce..311220b 100644 --- a/src/main/kotlin/com/michatec/store/screen/ProductFragment.kt +++ b/src/main/kotlin/com/michatec/store/screen/ProductFragment.kt @@ -148,8 +148,8 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { .flatMapSingle { products -> RxUtils .querySingle { signal -> Database.RepositoryAdapter.getAll(signal) } .map { result -> - result.asSequence().map { Pair(it.id, it) }.toMap() - .let { map -> products.mapNotNull { product -> map[product.repositoryId]?.let { Pair(product, it) } } } } } + result.associateBy { it.id } + .let { map -> products.mapNotNull { product -> map[product.repositoryId]?.let { Pair(product, it) } } } } } .flatMapSingle { products -> RxUtils .querySingle { signal -> Nullable(Database.InstalledAdapter.get(packageName, signal)) } .map { result -> Pair(products, result) } } @@ -396,9 +396,9 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { override fun onScreenshotClick(screenshot: Product.Screenshot) { val pair = products.asSequence() - .map { it -> Pair(it.second, it.first.screenshots.find { it === screenshot }?.identifier) } - .filter { it.second != null }.firstOrNull() - if (pair != null) { + .map { it -> Pair(it.second, it.first.screenshots.find { it === screenshot }?.identifier) } + .firstOrNull { it.second != null } + if (pair != null) { val (repository, identifier) = pair if (identifier != null) { ScreenshotsFragment(packageName, repository.id, identifier).show(childFragmentManager) @@ -420,8 +420,9 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { MessageDialog(MessageDialog.Message.ReleaseSignatureMismatch).show(childFragmentManager) } else -> { - val productRepository = products.asSequence().filter { it -> it.first.releases.any { it === release } }.firstOrNull() - if (productRepository != null) { + val productRepository = + products.firstOrNull { it -> it.first.releases.any { it === release } } + if (productRepository != null) { downloadConnection.binder?.enqueue(packageName, productRepository.first.name, productRepository.second, release) } diff --git a/src/main/kotlin/com/michatec/store/screen/ProductsAdapter.kt b/src/main/kotlin/com/michatec/store/screen/ProductsAdapter.kt index 8b2616e..cbefa19 100644 --- a/src/main/kotlin/com/michatec/store/screen/ProductsAdapter.kt +++ b/src/main/kotlin/com/michatec/store/screen/ProductsAdapter.kt @@ -196,6 +196,12 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): } } + override fun areContentsTheSame(oldCursor: Cursor, newCursor: Cursor): Boolean { + val oldItem = Database.ProductAdapter.transformItem(oldCursor) + val newItem = Database.ProductAdapter.transformItem(newCursor) + return oldItem == newItem + } + @SuppressLint("NotifyDataSetChanged") override fun onCursorChanged(oldCursor: Cursor?, newCursor: Cursor?) { val oldSize = oldCursor?.count ?: 0 diff --git a/src/main/kotlin/com/michatec/store/screen/TabsFragment.kt b/src/main/kotlin/com/michatec/store/screen/TabsFragment.kt index 85782fe..c9c640d 100644 --- a/src/main/kotlin/com/michatec/store/screen/TabsFragment.kt +++ b/src/main/kotlin/com/michatec/store/screen/TabsFragment.kt @@ -561,6 +561,7 @@ class TabsFragment: ScreenFragment() { override fun setAlpha(alpha: Int) = Unit override fun setColorFilter(colorFilter: ColorFilter?) = Unit + @Deprecated("Deprecated in Java") override fun getOpacity(): Int = PixelFormat.TRANSLUCENT } diff --git a/src/main/kotlin/com/michatec/store/service/Connection.kt b/src/main/kotlin/com/michatec/store/service/Connection.kt index 3bf79dd..9fbf6e0 100644 --- a/src/main/kotlin/com/michatec/store/service/Connection.kt +++ b/src/main/kotlin/com/michatec/store/service/Connection.kt @@ -19,11 +19,11 @@ class Connection>(private val serviceClass: } } + @Suppress("UNCHECKED_CAST") override fun onServiceConnected(componentName: ComponentName, binder: IBinder) { - @Suppress("UNCHECKED_CAST") - binder as B - this.binder = binder - onBind?.invoke(this, binder) + val b = binder as B + this.binder = b + onBind?.invoke(this, b) } override fun onServiceDisconnected(componentName: ComponentName) { diff --git a/src/main/kotlin/com/michatec/store/service/SyncService.kt b/src/main/kotlin/com/michatec/store/service/SyncService.kt index f65a513..3463956 100644 --- a/src/main/kotlin/com/michatec/store/service/SyncService.kt +++ b/src/main/kotlin/com/michatec/store/service/SyncService.kt @@ -331,7 +331,7 @@ class SyncService: ConnectionService() { currentTask = null handleNextTask(false) val blocked = updateNotificationBlockerFragment?.get()?.isAdded == true - if (!blocked && result != null && result.isNotEmpty()) { + if (!blocked && !result.isNullOrEmpty()) { displayUpdatesNotification(result) } } diff --git a/src/main/kotlin/com/michatec/store/utility/RxUtils.kt b/src/main/kotlin/com/michatec/store/utility/RxUtils.kt index 17d52a1..027d10f 100644 --- a/src/main/kotlin/com/michatec/store/utility/RxUtils.kt +++ b/src/main/kotlin/com/michatec/store/utility/RxUtils.kt @@ -12,7 +12,7 @@ import okhttp3.Response object RxUtils { private class ManagedDisposable(private val cancel: () -> Unit): Disposable { - @Volatile var disposed = false + var disposed = false override fun isDisposed(): Boolean = disposed override fun dispose() { diff --git a/src/main/kotlin/com/michatec/store/utility/Utils.kt b/src/main/kotlin/com/michatec/store/utility/Utils.kt index 5831350..9146604 100644 --- a/src/main/kotlin/com/michatec/store/utility/Utils.kt +++ b/src/main/kotlin/com/michatec/store/utility/Utils.kt @@ -1,7 +1,6 @@ package com.michatec.store.utility import android.animation.ValueAnimator -import android.annotation.SuppressLint import android.content.Context import android.content.pm.Signature import android.content.res.Configuration @@ -67,17 +66,11 @@ object Utils { } } - @SuppressLint("SuspiciousIndentation") fun configureLocale(context: Context): Context { val supportedLanguages = BuildConfig.LANGUAGES.toSet() val configuration = context.resources.configuration - val currentLocales = if (Android.sdk(24)) { - val localesList = configuration.locales - (0 until localesList.size()).map(localesList::get) - } else { - @Suppress("DEPRECATION") - listOf(configuration.locale) - } + val localesList = configuration.locales + val currentLocales = (0 until localesList.size()).map(localesList::get) val compatibleLocales = currentLocales .filter { it.language in supportedLanguages } .let { it.ifEmpty { listOf(Locale.US) } } diff --git a/src/main/kotlin/com/michatec/store/widget/CursorRecyclerAdapter.kt b/src/main/kotlin/com/michatec/store/widget/CursorRecyclerAdapter.kt index 8766177..bb16491 100644 --- a/src/main/kotlin/com/michatec/store/widget/CursorRecyclerAdapter.kt +++ b/src/main/kotlin/com/michatec/store/widget/CursorRecyclerAdapter.kt @@ -33,9 +33,8 @@ abstract class CursorRecyclerAdapter, VH: RecyclerView.ViewHolder>: return } - // Further reduced threshold to 100 for DiffUtil to avoid any noticeable frame drops on the main thread. - // JSON parsing and DB access during diffing are slow. - if (oldSize > 100 || newSize > 100) { + // Increased threshold for DiffUtil and optimized callback + if (oldSize > 500 || newSize > 500) { notifyDataSetChanged() return } @@ -60,7 +59,6 @@ abstract class CursorRecyclerAdapter, VH: RecyclerView.ViewHolder>: }) diffResult.dispatchUpdatesTo(this) } catch (_: Exception) { - // Fallback in case of cursor issues during diffing notifyDataSetChanged() } }