fix: clear the mechanical Android Lint warnings

Lint reports 874 warnings / 0 errors per amethyst variant. 512 of those are
in Crowdin-managed values-*/strings.xml (mostly MissingQuantity — a
translator's plural missing a quantity) and are not ours to hand-edit; this
takes the ones that are mechanical and behaviour-preserving:

- EmptySuperCall (24 in amethyst, plus 2 in commons the amethyst run cannot
  see): ViewModel.onCleared is documented empty, so drop the super calls.
- UseKtx (2): Canvas.withTranslation for the LaTeX drawable, and
  Bitmap.toDrawable for the map pin — the KTX form CLAUDE.md asks for, and
  both compile to the same calls.
- ConstantLocale (1): CalendarEventListCard held its "MMM" formatter in a
  file-level val, which captures Locale.getDefault() once — month
  abbreviations stayed in whatever language was active at class init. The
  formatter is now cached per locale, which keeps the property the original
  comment was protecting (a formatter per recompose was 500 allocations while
  scrolling), and the locale comes from LocalLocale.current.platformLocale so
  the read is observable: Locale.getDefault() inside a composable is not, and
  Compose's own NonObservableLocale check rates that an error.
- UnusedResources (6): the Android Studio new-project wizard's leftover
  colors (purple_200, teal_200, teal_700, black, white, transparent), each
  verified unreferenced from Kotlin and XML. purple_500/700 are in use and
  stay.
- UseTomlInstead (3): the debug-only Compose/Perfetto tracing dependencies
  move into the version catalog. Same coordinates and versions; the catalog
  already carries BOM-managed versionless entries.

playDebug goes from 874 warnings to 838, still 0 errors.

Deliberately left, because each is a decision rather than a cleanup:
AppLinkWarning (autoVerify only works if the domains serve a matching
assetlinks.json), the 126 unused source strings and 10 PluralsCandidate
(both churn the translation surface), GradleDependency /
NewerVersionAvailable (dependency bumps need the license check), VectorRaster
/ VectorPath / IconDensities / IconXmlAndPng (redrawing assets), BatteryLife
(the battery-optimization helper working as designed), and InlinedApi /
ClickableViewAccessibility / DiscouragedApi / InsecureBaseConfiguration (each
needs its surrounding intent read first).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123kXtseu4X18hL3GMDcdER
This commit is contained in:
Claude
2026-09-12 19:17:39 +00:00
parent 6e3af61e6a
commit 1f4d2bf9b5
32 changed files with 25 additions and 45 deletions
+3 -3
View File
@@ -399,9 +399,9 @@ dependencies {
// Usage: runtime-enable, then capture a Perfetto trace with the `track_event` data source:
// adb shell am broadcast -a androidx.tracing.perfetto.action.ENABLE_TRACING \
// -n com.vitorpamplona.amethyst.debug/androidx.tracing.perfetto.TracingReceiver
debugImplementation("androidx.compose.runtime:runtime-tracing")
debugImplementation("androidx.tracing:tracing-perfetto:1.0.1")
debugImplementation("androidx.tracing:tracing-perfetto-binary:1.0.1")
debugImplementation(libs.androidx.compose.runtime.tracing)
debugImplementation(libs.androidx.tracing.perfetto)
debugImplementation(libs.androidx.tracing.perfetto.binary)
implementation(project(":quartz"))
implementation(project(":commons"))
@@ -40,6 +40,7 @@ import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.sp
import androidx.core.graphics.withTranslation
import com.vitorpamplona.amethyst.commons.richtext.MathParser
import ru.noties.jlatexmath.JLatexMathDrawable
@@ -130,12 +131,11 @@ fun LatexEquation(
Canvas(modifier = equationModifier) {
drawIntoCanvas { canvas ->
val native = canvas.nativeCanvas
val checkpoint = native.save()
// Position the icon's baseline on the text baseline within the padded box.
native.translate(0f, drawTopPx)
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
drawable.draw(native)
native.restoreToCount(checkpoint)
native.withTranslation(y = drawTopPx) {
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
drawable.draw(this)
}
}
}
if (trailing.isNotEmpty()) {
@@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.note.creators.location
import android.graphics.ColorFilter
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.drawable.BitmapDrawable
import android.view.MotionEvent
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
@@ -36,6 +35,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.graphics.drawable.toDrawable
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
@@ -159,7 +159,7 @@ fun LocationPreviewMap(
remember(pinColor, pinEmoji) {
if (pinColor != null && pinEmoji != null) {
val bitmap = roadEventPinBitmap(pinEmoji, pinColor.toArgb(), context.resources.displayMetrics.density)
BitmapDrawable(context.resources, bitmap)
bitmap.toDrawable(context.resources)
} else {
null
}
@@ -118,6 +118,5 @@ open class UserFeedViewModel(
override fun onCleared() {
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
bundler.cancel()
super.onCleared()
}
}
@@ -2647,7 +2647,6 @@ class AccountViewModel(
com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestBridge
.clear()
feedStates.destroy()
super.onCleared()
}
fun loadMentions(
@@ -310,7 +310,6 @@ class AgentConsoleViewModel : ViewModel() {
override fun onCleared() {
stopObserving()
stopWatching()
super.onCleared()
}
/** One decrypted observer telemetry frame rendered on the Observer tab. */
@@ -218,7 +218,6 @@ class AgentWorkBoardViewModel : ViewModel() {
override fun onCleared() {
stopWatching()
super.onCleared()
}
companion object {
@@ -360,6 +360,5 @@ class BuzzDmListViewModel : ViewModel() {
override fun onCleared() {
liveJob?.cancel()
liveJob = null
super.onCleared()
}
}
@@ -137,7 +137,6 @@ class JobBoardViewModel : ViewModel() {
override fun onCleared() {
stopWatching()
super.onCleared()
}
companion object {
@@ -258,7 +258,6 @@ class WorkflowRunBoardViewModel : ViewModel() {
override fun onCleared() {
stopWatching()
super.onCleared()
}
companion object {
@@ -41,6 +41,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLocale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -58,11 +59,15 @@ import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.concurrent.ConcurrentHashMap
// Thread-safe and hoisted: previously each CalendarDateBadge recompose allocated a new
// SimpleDateFormat, which (a) is not thread-safe and (b) created 500 allocations while scrolling.
private val MonthShortFormatter: DateTimeFormatter =
DateTimeFormatter.ofPattern("MMM", Locale.getDefault())
// Cached per locale rather than in a single val that captures the locale once: the month
// names have to follow a language the user changes while the app is running.
private val monthShortFormatters = ConcurrentHashMap<Locale, DateTimeFormatter>()
private fun monthShortFormatter(locale: Locale): DateTimeFormatter = monthShortFormatters.getOrPut(locale) { DateTimeFormatter.ofPattern("MMM", locale) }
@Composable
fun CalendarEventListCard(
@@ -214,7 +219,10 @@ private fun CalendarDateBadge(startSeconds: Long?) {
Instant.ofEpochSecond(startSeconds).atZone(ZoneId.systemDefault()).toLocalDate()
}
val day = localDate.dayOfMonth.toString()
val month = remember(localDate) { MonthShortFormatter.format(localDate).uppercase() }
// LocalLocale rather than Locale.getDefault(): the latter is not observable, so a
// locale change while the app runs would leave the month name in the old language.
val locale = LocalLocale.current.platformLocale
val month = remember(localDate, locale) { monthShortFormatter(locale).format(localDate).uppercase() }
Column(
modifier = Modifier.size(width = 52.dp, height = 60.dp),
@@ -109,7 +109,6 @@ class NewCalendarCollectionViewModel : ViewModel() {
override fun onCleared() {
liveScanJob?.cancel()
super.onCleared()
}
fun toggle(address: Address) {
@@ -801,7 +801,6 @@ class ChatNewMessageViewModel :
}
override fun onCleared() {
super.onCleared()
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
}
@@ -1027,7 +1027,6 @@ open class ChannelNewMessageViewModel :
}
override fun onCleared() {
super.onCleared()
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
}
@@ -132,7 +132,6 @@ class ChessViewModelNew(
fun clearFocusedGame() = logic.clearFocusedGame()
override fun onCleared() {
super.onCleared()
logic.stopPolling()
}
@@ -739,7 +739,6 @@ class LongFormPostViewModel :
}
override fun onCleared() {
super.onCleared()
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
}
@@ -612,7 +612,6 @@ open class NewProductViewModel :
}
override fun onCleared() {
super.onCleared()
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
}
@@ -1977,7 +1977,6 @@ open class ShortNotePostViewModel :
}
override fun onCleared() {
super.onCleared()
writingAssistant?.close()
writingAssistant = null
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
@@ -320,7 +320,6 @@ class VoiceReplyViewModel : ViewModel() {
override fun onCleared() {
cancel()
super.onCleared()
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
}
}
@@ -102,7 +102,6 @@ class AddToMusicPlaylistViewModel : ViewModel() {
override fun onCleared() {
liveScanJob?.cancel()
super.onCleared()
}
private suspend fun rescan() {
@@ -597,7 +597,6 @@ open class NestNewMessageViewModel :
}
override fun onCleared() {
super.onCleared()
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
}
@@ -660,7 +660,6 @@ class NewPublicMessageViewModel :
}
override fun onCleared() {
super.onCleared()
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
}
@@ -196,6 +196,5 @@ class RelayFeedViewModel :
override fun onCleared() {
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
super.onCleared()
}
}
@@ -116,6 +116,5 @@ open class StringFeedViewModel(
override fun onCleared() {
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
bundler.cancel()
super.onCleared()
}
}
@@ -180,7 +180,6 @@ open class NewHlsVideoViewModel : ViewModel() {
}
override fun onCleared() {
super.onCleared()
currentJob?.cancel()
}
@@ -459,7 +459,6 @@ class ReloadMintViewModel : ViewModel() {
// The pipeline runs on the AccountViewModel scope, not this VM's, so it would
// outlive the screen — cancel it when the screen goes away.
job?.cancel()
super.onCleared()
}
companion object {
@@ -267,7 +267,6 @@ class TopUpMintViewModel : ViewModel() {
// The pipeline runs on the AccountViewModel scope, not this VM's, so it would
// outlive the screen — cancel it when the screen goes away.
job?.cancel()
super.onCleared()
}
}
@@ -293,7 +293,6 @@ class CashuWalletWizardViewModel : ViewModel() {
}
override fun onCleared() {
super.onCleared()
discovery?.cancel()
}
}
-6
View File
@@ -1,13 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="transparent">#00FFFFFF</color>
<!-- Launch splash / window background. Tracks MaterialTheme's background so the
first composed frame does not step to a different colour. See values-night. -->
@@ -68,6 +68,5 @@ abstract class FeedViewModel(
override fun onCleared() {
Log.d("Init") { "OnCleared: ${this::class.simpleName}" }
super.onCleared()
}
}
@@ -838,7 +838,6 @@ class NestViewModel(
closed = true
teardownBroadcast(BroadcastUiState.Idle, finalCleanup = true)
teardown(targetState = ConnectionUiState.Closed, finalCleanup = true)
super.onCleared()
}
private fun observeSpeakerState(s: NestsSpeaker) {
+4
View File
@@ -28,6 +28,7 @@ uiautomator = "2.4.0"
biometricKtx = "1.4.0-alpha02"
coil = "3.6.2"
composeBom = "2026.09.00"
tracingPerfetto = "1.0.1"
composeRuntimeAnnotation = "1.12.1"
coreKtx = "1.19.0"
datastore = "1.2.1"
@@ -136,6 +137,9 @@ androidx-camera-extensions = { module = "androidx.camera:camera-extensions", ver
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidxCamera" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidxCamera" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-runtime-tracing = { group = "androidx.compose.runtime", name = "runtime-tracing" }
androidx-tracing-perfetto = { group = "androidx.tracing", name = "tracing-perfetto", version.ref = "tracingPerfetto" }
androidx-tracing-perfetto-binary = { group = "androidx.tracing", name = "tracing-perfetto-binary", version.ref = "tracingPerfetto" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
androidx-compose-runtime-annotation = { group = "androidx.compose.runtime", name = "runtime-annotation", version.ref = "composeRuntimeAnnotation" }
androidx-collection = { group = "androidx.collection", name = "collection", version.ref = "androidxCollection" }