Merge pull request #4250 from vitorpamplona/claude/wonderful-einstein-0lg4k8

Fix Material 3 API deprecations and test GC issues
This commit is contained in:
Vitor Pamplona
2026-09-28 11:13:12 -04:00
committed by GitHub
6 changed files with 35 additions and 15 deletions
@@ -30,8 +30,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetValue
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -82,7 +83,11 @@ fun QrImageCodeChooser(
onPick: (ScannedPayload) -> Unit, onPick: (ScannedPayload) -> Unit,
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val sheetState =
rememberBottomSheetState(
initialValue = SheetValue.Hidden,
enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded),
)
ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState) { ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState) {
Column( Column(
@@ -30,9 +30,10 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetValue
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -73,7 +74,11 @@ fun ScanOutcomeSheet(
onOpenLink: (String) -> Unit, onOpenLink: (String) -> Unit,
onCopy: (String) -> Unit, onCopy: (String) -> Unit,
) { ) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val sheetState =
rememberBottomSheetState(
initialValue = SheetValue.Hidden,
enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded),
)
ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState) { ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState) {
Column( Column(
@@ -88,7 +88,7 @@ fun OutlinedThinPaddingTextField(
shape: Shape = OutlinedTextFieldDefaults.shape, shape: Shape = OutlinedTextFieldDefaults.shape,
colors: TextFieldColors = OutlinedTextFieldDefaults.colors(), colors: TextFieldColors = OutlinedTextFieldDefaults.colors(),
contentPadding: PaddingValues = contentPadding: PaddingValues =
OutlinedTextFieldDefaults.contentPadding( OutlinedTextFieldDefaults.contentPaddingWithoutLabel(
start = 10.dp, start = 10.dp,
top = 12.dp, top = 12.dp,
end = 10.dp, end = 10.dp,
@@ -78,7 +78,7 @@ fun SlimListItem(
) { ) {
val decoratedHeadlineContent: @Composable () -> Unit = { val decoratedHeadlineContent: @Composable () -> Unit = {
ProvideTextStyleFromToken( ProvideTextStyleFromToken(
colors.headlineColor, colors.contentColor,
MaterialTheme.typography.bodyLarge, MaterialTheme.typography.bodyLarge,
headlineContent, headlineContent,
) )
@@ -88,7 +88,7 @@ fun SlimListItem(
{ {
Box(SupportingContentTopPadding) { Box(SupportingContentTopPadding) {
ProvideTextStyleFromToken( ProvideTextStyleFromToken(
colors.supportingTextColor, colors.supportingContentColor,
MaterialTheme.typography.bodyMedium, MaterialTheme.typography.bodyMedium,
it, it,
) )
@@ -99,7 +99,7 @@ fun SlimListItem(
overlineContent?.let { overlineContent?.let {
{ {
ProvideTextStyleFromToken( ProvideTextStyleFromToken(
colors.overlineColor, colors.overlineContentColor,
MaterialTheme.typography.labelSmall, MaterialTheme.typography.labelSmall,
it, it,
) )
@@ -110,7 +110,7 @@ fun SlimListItem(
{ {
Box(LeadingContentEndPadding) { Box(LeadingContentEndPadding) {
CompositionLocalProvider( CompositionLocalProvider(
LocalContentColor provides colors.leadingIconColor, LocalContentColor provides colors.leadingContentColor,
content = it, content = it,
) )
} }
@@ -121,7 +121,7 @@ fun SlimListItem(
{ {
Box(TrailingContentStartPadding) { Box(TrailingContentStartPadding) {
ProvideTextStyleFromToken( ProvideTextStyleFromToken(
colors.trailingIconColor, colors.trailingContentColor,
MaterialTheme.typography.labelSmall, MaterialTheme.typography.labelSmall,
content = it, content = it,
) )
@@ -80,7 +80,7 @@ class SnoObjectCardRenderTest {
MaterialTheme(colorScheme = darkColorScheme()) { MaterialTheme(colorScheme = darkColorScheme()) {
Surface(color = MaterialTheme.colorScheme.surface) { Surface(color = MaterialTheme.colorScheme.surface) {
Column(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
SnoObjectCard(payload = payload!!, eventId = "render-test", onClick = {}) SnoObjectCard(payload = payload, eventId = "render-test", onClick = {})
} }
} }
} }
@@ -122,7 +122,7 @@ class SnoObjectCardRenderTest {
MaterialTheme(colorScheme = darkColorScheme()) { MaterialTheme(colorScheme = darkColorScheme()) {
Surface(color = MaterialTheme.colorScheme.surface) { Surface(color = MaterialTheme.colorScheme.surface) {
SnoObjectViewer( SnoObjectViewer(
payload = payload!!, payload = payload,
eventId = "viewer-test", eventId = "viewer-test",
modifier = Modifier.size(200.dp), modifier = Modifier.size(200.dp),
) )
@@ -36,6 +36,14 @@ import kotlin.test.assertNull
* kind:10050 routing + `UserContext` wiring, `route` dropped kind 10050 and the * kind:10050 routing + `UserContext` wiring, `route` dropped kind 10050 and the
* User model was backed by a map nothing populated, so every recipient looked * User model was backed by a map nothing populated, so every recipient looked
* "unreachable via NIP-17" until an indexer fan-out ran. * "unreachable via NIP-17" until an indexer fan-out ran.
*
* Each test takes the [com.vitorpamplona.amethyst.commons.model.User] *before*
* consuming and keeps it. The cache's users and addressable notes live in a
* weakly-valued `LargeSoftCache`, so a note nothing references can be collected
* between two `consume` calls — taking the stored kind 10050 with it and letting
* a stale list land on a fresh, empty note. The app keeps a DM recipient's `User`
* alive while it is in use, and a `User` strongly holds its `dmRelayListNote`;
* the tests do the same, otherwise a GC mid-test made them fail on CI.
*/ */
class DesktopDmRelayListConsumeTest { class DesktopDmRelayListConsumeTest {
private val relayUrl = NormalizedRelayUrl("wss://relay.test/") private val relayUrl = NormalizedRelayUrl("wss://relay.test/")
@@ -56,11 +64,11 @@ class DesktopDmRelayListConsumeTest {
fun `consume routes kind 10050 into the User model`() { fun `consume routes kind 10050 into the User model`() {
val cache = DesktopLocalCache() val cache = DesktopLocalCache()
val signer = NostrSignerSync(KeyPair()) val signer = NostrSignerSync(KeyPair())
val user = cache.getOrCreateUser(signer.pubKey)
val event = signedDmRelayList(signer) val event = signedDmRelayList(signer)
cache.consume(event, relayUrl) cache.consume(event, relayUrl)
val user = cache.getOrCreateUser(signer.pubKey)
assertEquals(dmInbox, user.dmInboxRelaysStrict()) assertEquals(dmInbox, user.dmInboxRelaysStrict())
} }
@@ -76,25 +84,27 @@ class DesktopDmRelayListConsumeTest {
fun `a newer kind 10050 replaces an older one`() { fun `a newer kind 10050 replaces an older one`() {
val cache = DesktopLocalCache() val cache = DesktopLocalCache()
val signer = NostrSignerSync(KeyPair()) val signer = NostrSignerSync(KeyPair())
val user = cache.getOrCreateUser(signer.pubKey)
cache.consume(signedDmRelayList(signer, dmInbox, createdAt = 1_700_000_000), relayUrl) cache.consume(signedDmRelayList(signer, dmInbox, createdAt = 1_700_000_000), relayUrl)
val newerRelays = listOf(NormalizedRelayUrl("wss://moved.example/")) val newerRelays = listOf(NormalizedRelayUrl("wss://moved.example/"))
cache.consume(signedDmRelayList(signer, newerRelays, createdAt = 1_700_000_100), relayUrl) cache.consume(signedDmRelayList(signer, newerRelays, createdAt = 1_700_000_100), relayUrl)
assertEquals(newerRelays, cache.getOrCreateUser(signer.pubKey).dmInboxRelaysStrict()) assertEquals(newerRelays, user.dmInboxRelaysStrict())
} }
@Test @Test
fun `an older kind 10050 does not overwrite a newer one`() { fun `an older kind 10050 does not overwrite a newer one`() {
val cache = DesktopLocalCache() val cache = DesktopLocalCache()
val signer = NostrSignerSync(KeyPair()) val signer = NostrSignerSync(KeyPair())
val user = cache.getOrCreateUser(signer.pubKey)
cache.consume(signedDmRelayList(signer, dmInbox, createdAt = 1_700_000_100), relayUrl) cache.consume(signedDmRelayList(signer, dmInbox, createdAt = 1_700_000_100), relayUrl)
val staleRelays = listOf(NormalizedRelayUrl("wss://stale.example/")) val staleRelays = listOf(NormalizedRelayUrl("wss://stale.example/"))
cache.consume(signedDmRelayList(signer, staleRelays, createdAt = 1_700_000_000), relayUrl) cache.consume(signedDmRelayList(signer, staleRelays, createdAt = 1_700_000_000), relayUrl)
assertEquals(dmInbox, cache.getOrCreateUser(signer.pubKey).dmInboxRelaysStrict()) assertEquals(dmInbox, user.dmInboxRelaysStrict())
} }
} }