- 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
This commit is contained in:
2026-03-24 17:31:51 +01:00
parent bdd204d9de
commit 2c9af08e8c
12 changed files with 35 additions and 33 deletions
@@ -16,10 +16,12 @@ object Preferences {
private val subject = PublishSubject.create<Key<*>>() private val subject = PublishSubject.create<Key<*>>()
private val keys = sequenceOf(Key.AutoSync, Key.IncompatibleVersions, Key.ProxyHost, Key.ProxyPort, Key.ProxyType, private val keys = sequenceOf(
Key.SortOrder, Key.Theme, Key.UpdateNotify, Key.UpdateUnstable).map { Pair(it.name, it) }.toMap() 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) keys[keyString]?.let(subject::onNext)
} }
@@ -74,5 +74,6 @@ open class DrawableWrapper(val drawable: Drawable): Drawable() {
DrawableCompat.setHotspotBounds(drawable, left, top, right, bottom) DrawableCompat.setHotspotBounds(drawable, left, top, right, bottom)
} }
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
override fun getOpacity(): Int = drawable.opacity override fun getOpacity(): Int = drawable.opacity
} }
@@ -260,6 +260,6 @@ class IndexHandler(private val repositoryId: Long, private val callback: Callbac
override fun characters(ch: CharArray, start: Int, length: Int) { override fun characters(ch: CharArray, start: Int, length: Int) {
super.characters(ch, start, length) super.characters(ch, start, length)
contentBuilder.append(ch, start, length) contentBuilder.appendRange(ch, start, start + length)
} }
} }
@@ -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) { private open class OverlappingViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
init { init {
// Block touch events if touched above negative margin // Block touch events if touched above negative margin
@SuppressLint("ClickableViewAccessibility")
itemView.setOnTouchListener { v, event -> itemView.setOnTouchListener { v, event ->
val top = (v.layoutParams as ViewGroup.MarginLayoutParams).topMargin val top = (v.layoutParams as ViewGroup.MarginLayoutParams).topMargin
event.action == MotionEvent.ACTION_DOWN && top < 0 && event.y < -top 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) { private class LinkViewHolder(itemView: View): OverlappingViewHolder(itemView) {
companion object { companion object {
private val measurement = Measurement<Int>() private val measurement = Measurement<Int>()
@@ -384,7 +385,6 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int)
init { init {
val margin = measurement.invalidate(itemView.resources) { val margin = measurement.invalidate(itemView.resources) {
@SuppressLint("SetTextI18n")
text.text = "measure" text.text = "measure"
link.visibility = View.GONE link.visibility = View.GONE
measurement.measure(itemView) 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) { private class PermissionsViewHolder(itemView: View): OverlappingViewHolder(itemView) {
companion object { companion object {
private val measurement = Measurement<Int>() private val measurement = Measurement<Int>()
@@ -407,7 +408,6 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int)
init { init {
val margin = measurement.invalidate(itemView.resources) { val margin = measurement.invalidate(itemView.resources) {
@SuppressLint("SetTextI18n")
text.text = "measure" text.text = "measure"
measurement.measure(itemView) measurement.measure(itemView)
((itemView.measuredHeight - icon.measuredHeight) / 2f).roundToInt() ((itemView.measuredHeight - icon.measuredHeight) / 2f).roundToInt()
@@ -148,8 +148,8 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
.flatMapSingle { products -> RxUtils .flatMapSingle { products -> RxUtils
.querySingle { signal -> Database.RepositoryAdapter.getAll(signal) } .querySingle { signal -> Database.RepositoryAdapter.getAll(signal) }
.map { result -> .map { result ->
result.asSequence().map { Pair(it.id, it) }.toMap() result.associateBy { it.id }
.let { map -> products.mapNotNull { product -> map[product.repositoryId]?.let { Pair(product, it) } } } } } .let { map -> products.mapNotNull { product -> map[product.repositoryId]?.let { Pair(product, it) } } } } }
.flatMapSingle { products -> RxUtils .flatMapSingle { products -> RxUtils
.querySingle { signal -> Nullable(Database.InstalledAdapter.get(packageName, signal)) } .querySingle { signal -> Nullable(Database.InstalledAdapter.get(packageName, signal)) }
.map { result -> Pair(products, result) } } .map { result -> Pair(products, result) } }
@@ -396,9 +396,9 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
override fun onScreenshotClick(screenshot: Product.Screenshot) { override fun onScreenshotClick(screenshot: Product.Screenshot) {
val pair = products.asSequence() val pair = products.asSequence()
.map { it -> Pair(it.second, it.first.screenshots.find { it === screenshot }?.identifier) } .map { it -> Pair(it.second, it.first.screenshots.find { it === screenshot }?.identifier) }
.filter { it.second != null }.firstOrNull() .firstOrNull { it.second != null }
if (pair != null) { if (pair != null) {
val (repository, identifier) = pair val (repository, identifier) = pair
if (identifier != null) { if (identifier != null) {
ScreenshotsFragment(packageName, repository.id, identifier).show(childFragmentManager) ScreenshotsFragment(packageName, repository.id, identifier).show(childFragmentManager)
@@ -420,8 +420,9 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
MessageDialog(MessageDialog.Message.ReleaseSignatureMismatch).show(childFragmentManager) MessageDialog(MessageDialog.Message.ReleaseSignatureMismatch).show(childFragmentManager)
} }
else -> { else -> {
val productRepository = products.asSequence().filter { it -> it.first.releases.any { it === release } }.firstOrNull() val productRepository =
if (productRepository != null) { products.firstOrNull { it -> it.first.releases.any { it === release } }
if (productRepository != null) {
downloadConnection.binder?.enqueue(packageName, productRepository.first.name, downloadConnection.binder?.enqueue(packageName, productRepository.first.name,
productRepository.second, release) productRepository.second, release)
} }
@@ -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") @SuppressLint("NotifyDataSetChanged")
override fun onCursorChanged(oldCursor: Cursor?, newCursor: Cursor?) { override fun onCursorChanged(oldCursor: Cursor?, newCursor: Cursor?) {
val oldSize = oldCursor?.count ?: 0 val oldSize = oldCursor?.count ?: 0
@@ -561,6 +561,7 @@ class TabsFragment: ScreenFragment() {
override fun setAlpha(alpha: Int) = Unit override fun setAlpha(alpha: Int) = Unit
override fun setColorFilter(colorFilter: ColorFilter?) = Unit override fun setColorFilter(colorFilter: ColorFilter?) = Unit
@Deprecated("Deprecated in Java")
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
} }
@@ -19,11 +19,11 @@ class Connection<B: IBinder, S: ConnectionService<B>>(private val serviceClass:
} }
} }
@Suppress("UNCHECKED_CAST")
override fun onServiceConnected(componentName: ComponentName, binder: IBinder) { override fun onServiceConnected(componentName: ComponentName, binder: IBinder) {
@Suppress("UNCHECKED_CAST") val b = binder as B
binder as B this.binder = b
this.binder = binder onBind?.invoke(this, b)
onBind?.invoke(this, binder)
} }
override fun onServiceDisconnected(componentName: ComponentName) { override fun onServiceDisconnected(componentName: ComponentName) {
@@ -331,7 +331,7 @@ class SyncService: ConnectionService<SyncService.Binder>() {
currentTask = null currentTask = null
handleNextTask(false) handleNextTask(false)
val blocked = updateNotificationBlockerFragment?.get()?.isAdded == true val blocked = updateNotificationBlockerFragment?.get()?.isAdded == true
if (!blocked && result != null && result.isNotEmpty()) { if (!blocked && !result.isNullOrEmpty()) {
displayUpdatesNotification(result) displayUpdatesNotification(result)
} }
} }
@@ -12,7 +12,7 @@ import okhttp3.Response
object RxUtils { object RxUtils {
private class ManagedDisposable(private val cancel: () -> Unit): Disposable { private class ManagedDisposable(private val cancel: () -> Unit): Disposable {
@Volatile var disposed = false var disposed = false
override fun isDisposed(): Boolean = disposed override fun isDisposed(): Boolean = disposed
override fun dispose() { override fun dispose() {
@@ -1,7 +1,6 @@
package com.michatec.store.utility package com.michatec.store.utility
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.pm.Signature import android.content.pm.Signature
import android.content.res.Configuration import android.content.res.Configuration
@@ -67,17 +66,11 @@ object Utils {
} }
} }
@SuppressLint("SuspiciousIndentation")
fun configureLocale(context: Context): Context { fun configureLocale(context: Context): Context {
val supportedLanguages = BuildConfig.LANGUAGES.toSet() val supportedLanguages = BuildConfig.LANGUAGES.toSet()
val configuration = context.resources.configuration val configuration = context.resources.configuration
val currentLocales = if (Android.sdk(24)) { val localesList = configuration.locales
val localesList = configuration.locales val currentLocales = (0 until localesList.size()).map(localesList::get)
(0 until localesList.size()).map(localesList::get)
} else {
@Suppress("DEPRECATION")
listOf(configuration.locale)
}
val compatibleLocales = currentLocales val compatibleLocales = currentLocales
.filter { it.language in supportedLanguages } .filter { it.language in supportedLanguages }
.let { it.ifEmpty { listOf(Locale.US) } } .let { it.ifEmpty { listOf(Locale.US) } }
@@ -33,9 +33,8 @@ abstract class CursorRecyclerAdapter<VT: Enum<VT>, VH: RecyclerView.ViewHolder>:
return return
} }
// Further reduced threshold to 100 for DiffUtil to avoid any noticeable frame drops on the main thread. // Increased threshold for DiffUtil and optimized callback
// JSON parsing and DB access during diffing are slow. if (oldSize > 500 || newSize > 500) {
if (oldSize > 100 || newSize > 100) {
notifyDataSetChanged() notifyDataSetChanged()
return return
} }
@@ -60,7 +59,6 @@ abstract class CursorRecyclerAdapter<VT: Enum<VT>, VH: RecyclerView.ViewHolder>:
}) })
diffResult.dispatchUpdatesTo(this) diffResult.dispatchUpdatesTo(this)
} catch (_: Exception) { } catch (_: Exception) {
// Fallback in case of cursor issues during diffing
notifyDataSetChanged() notifyDataSetChanged()
} }
} }