Merge pull request #822 from permissionlesstech/uniform-lists-design

Unify people lists with conversation-row avatar design
This commit is contained in:
callebtc 2026-07-29 19:39:07 +02:00 committed by GitHub
commit c7ced41bcb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 261 additions and 164 deletions

View File

@ -385,7 +385,7 @@ org.jetbrains.kotlin:kotlin-daemon-client:2.4.10=kotlinBuildToolsApiClasspath,ko
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.4.0=kotlinAbiValidationCompatClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
org.jetbrains.kotlin:kotlin-parcelize-compiler:2.4.10=kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugRuntimeClasspathCopy,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseRuntimeClasspathCopy
org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
org.jetbrains.kotlin:kotlin-reflect:1.8.21=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlin:kotlin-reflect:2.1.20=debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
@ -407,7 +407,7 @@ org.jetbrains.kotlin:kotlin-stdlib:1.9.0=unified-test-platform-android-driver-in
org.jetbrains.kotlin:kotlin-stdlib:2.2.10=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=composeMappingProducerClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.4.0=kotlinAbiValidationCompatClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugRuntimeClasspathCopy,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseRuntimeClasspathCopy
org.jetbrains.kotlin:kotlin-tooling-core:2.4.10=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
org.jetbrains.kotlinx:atomicfu-jvm:0.22.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlinx:atomicfu-jvm:0.28.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
@ -429,7 +429,7 @@ org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0=debugAndroidTestCompileClas
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains:annotations:13.0=composeMappingProducerClasspath,kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
org.jetbrains:annotations:13.0=composeMappingProducerClasspath,debugRuntimeClasspathCopy,kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,releaseRuntimeClasspathCopy
org.jetbrains:annotations:23.0.0=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jspecify:jspecify:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jvnet.staxex:stax-ex:1.8.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle

View File

@ -281,6 +281,7 @@ private fun GeohashPersonItem(
onTap: () -> Unit
) {
val palette = LocalBitchatPalette.current
val colorScheme = MaterialTheme.colorScheme
val statusIconRes =
if (isTeleported) R.drawable.ic_spec_teleport
@ -298,25 +299,14 @@ private fun GeohashPersonItem(
Row(
modifier = Modifier
.fillMaxWidth()
// Exact height, not padding: a row that sizes to its content makes the card change
// height whenever the list reorders.
.height(SheetRowHeight)
.clickable(onClick = onTap)
.padding(horizontal = SheetRowHorizontal),
.padding(horizontal = SheetRowHorizontal, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier.size(SheetRowLeadingSlot),
contentAlignment = Alignment.Center
) {
if (hasUnreadDM) {
Icon(
imageVector = Icons.Filled.Email,
contentDescription = stringResource(R.string.cd_unread_message),
modifier = Modifier.size(22.dp),
tint = palette.accentOrange
)
} else {
PeerAvatar(
name = baseNameRaw,
color = baseColor,
badge = {
Icon(
painter = painterResource(statusIconRes),
contentDescription = if (isTeleported) {
@ -324,13 +314,13 @@ private fun GeohashPersonItem(
} else {
stringResource(R.string.section_on_location)
},
modifier = Modifier.size(22.dp),
tint = baseColor
modifier = Modifier.size(13.dp),
tint = if (isTeleported) palette.accentPurple else colorScheme.primary
)
}
}
)
Spacer(modifier = Modifier.width(SheetRowLeadingGutter))
Spacer(modifier = Modifier.width(12.dp))
Row(
modifier = Modifier.weight(1f),
@ -365,5 +355,16 @@ private fun GeohashPersonItem(
)
}
}
if (hasUnreadDM) {
Icon(
imageVector = Icons.Filled.Email,
contentDescription = stringResource(R.string.cd_unread_message),
modifier = Modifier
.padding(start = 8.dp)
.size(18.dp),
tint = palette.accentOrange
)
}
}
}

View File

@ -831,18 +831,14 @@ fun PeopleSection(
displayName = displayName,
isDirect = isDirectLive,
isWifiAware = peerID in wifiAwarePeerIDs,
isConnected = true,
isSelected = conversationID == selectedPrivatePeer || peerID == selectedPrivatePeer,
isFavorite = isFavorite,
theyFavoritedUs = theyFavoritedUs,
isVerified = isVerified,
hasUnreadDM = combinedHasUnread,
colorScheme = colorScheme,
viewModel = viewModel,
onItemClick = { onPrivateChatStart(peerID) },
onToggleFavorite = {
Log.d("SidebarComponents", "Sidebar toggle favorite: peerID=$peerID, currentFavorite=$isFavorite")
viewModel.toggleFavorite(peerID)
},
unreadCount = if (combinedUnreadCount > 0) combinedUnreadCount else if (combinedHasUnread) 1 else 0,
showNostrGlobe = false,
showHashSuffix = showHash
@ -881,18 +877,14 @@ fun PeopleSection(
peerID = favPeerID,
displayName = dn,
isDirect = false,
isConnected = false,
isSelected = conversationID == selectedPrivatePeer || (mappedConnectedPeerID ?: favPeerID) == selectedPrivatePeer,
isFavorite = true,
theyFavoritedUs = fav.theyFavoritedUs,
isVerified = isVerified,
hasUnreadDM = hasUnread,
colorScheme = colorScheme,
viewModel = viewModel,
onItemClick = { onPrivateChatStart(mappedConnectedPeerID ?: favPeerID) },
onToggleFavorite = {
Log.d("SidebarComponents", "Sidebar toggle favorite (offline): peerID=$favPeerID")
viewModel.toggleFavorite(favPeerID)
},
unreadCount = if (unreadCount > 0) unreadCount else if (hasUnread) 1 else 0,
showNostrGlobe = (fav.isMutual && fav.peerNostrPublicKey != null),
showHashSuffix = showHash
@ -1263,72 +1255,42 @@ private fun ConversationRow(
),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier.size(42.dp),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.size(38.dp)
.background(assignedColor.copy(alpha = 0.16f), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = baseNameRaw
.trim()
.firstOrNull()
?.uppercase()
?: "#",
style = MaterialTheme.typography.titleMedium.copy(
fontFamily = BitchatFontFamily,
fontWeight = FontWeight.SemiBold
),
color = assignedColor
)
}
PeerAvatar(
name = baseNameRaw,
color = assignedColor,
badge = {
when {
conversation.isConnected -> Icon(
painter = painterResource(
conversationTransportIcon(
isReachedOverInternet = false,
isWifiAware = isWifiAware,
isDirect = isDirect
)
),
contentDescription = connectionDescription,
modifier = Modifier.size(13.dp),
tint = colorScheme.primary
)
Surface(
modifier = Modifier
.size(18.dp)
.align(Alignment.BottomEnd),
shape = CircleShape,
color = colorScheme.surface,
tonalElevation = 1.dp
) {
Box(contentAlignment = Alignment.Center) {
when {
conversation.isConnected -> Icon(
painter = painterResource(
conversationTransportIcon(
isReachedOverInternet = false,
isWifiAware = isWifiAware,
isDirect = isDirect
)
),
contentDescription = connectionDescription,
modifier = Modifier.size(13.dp),
tint = colorScheme.primary
)
conversation.transport == DirectMessageTransport.NOSTR -> Icon(
painter = painterResource(R.drawable.ic_spec_globe),
contentDescription = stringResource(
R.string.offline_reachable_via_nostr
),
modifier = Modifier.size(13.dp),
tint = palette.accentPurple
)
conversation.transport == DirectMessageTransport.NOSTR -> Icon(
painter = painterResource(R.drawable.ic_spec_globe),
contentDescription = stringResource(
R.string.offline_reachable_via_nostr
),
modifier = Modifier.size(13.dp),
tint = palette.accentPurple
)
else -> Icon(
imageVector = Icons.Outlined.Circle,
contentDescription = stringResource(R.string.offline_not_in_mesh),
modifier = Modifier.size(11.dp),
tint = palette.textTertiary
)
}
else -> Icon(
imageVector = Icons.Outlined.Circle,
contentDescription = stringResource(R.string.offline_not_in_mesh),
modifier = Modifier.size(11.dp),
tint = palette.textTertiary
)
}
}
}
)
Spacer(modifier = Modifier.width(12.dp))
@ -1559,15 +1521,14 @@ private fun PeerItem(
displayName: String,
isDirect: Boolean,
isWifiAware: Boolean = false,
isConnected: Boolean = true,
isSelected: Boolean,
isFavorite: Boolean,
theyFavoritedUs: Boolean = false,
isVerified: Boolean,
hasUnreadDM: Boolean,
colorScheme: ColorScheme,
viewModel: ChatViewModel,
onItemClick: () -> Unit,
onToggleFavorite: () -> Unit,
unreadCount: Int = 0,
showNostrGlobe: Boolean = false,
showHashSuffix: Boolean = true
@ -1595,57 +1556,47 @@ private fun PeerItem(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onItemClick)
.padding(horizontal = SheetRowHorizontal, vertical = SheetRowVertical),
.padding(horizontal = SheetRowHorizontal, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier.size(SheetRowLeadingSlot),
contentAlignment = Alignment.Center
) {
if (isSelected) {
Box(
modifier = Modifier
.size(SheetRowSelectedDot)
.background(colorScheme.primary, CircleShape)
)
} else if (hasUnreadDM) {
Icon(
painter = painterResource(R.drawable.ic_spec_envelope),
contentDescription = stringResource(R.string.cd_unread_message),
modifier = Modifier.size(PeerRowIconSize),
tint = palette.accentOrange
)
} else if (showNostrGlobe) {
Icon(
painter = painterResource(R.drawable.ic_spec_globe),
contentDescription = stringResource(R.string.cd_reachable_via_nostr),
modifier = Modifier.size(PeerRowIconSize),
tint = palette.accentPurple
)
} else if (!isDirect && isFavorite) {
Icon(
imageVector = Icons.Outlined.Circle,
contentDescription = stringResource(R.string.cd_offline_favorite),
modifier = Modifier.size(PeerRowIconSize),
tint = palette.textTertiary
)
} else {
Icon(
painter = painterResource(
conversationTransportIcon(
isReachedOverInternet = false,
isWifiAware = isWifiAware,
isDirect = isDirect
)
),
contentDescription = connectionDescription,
modifier = Modifier.size(PeerRowIconSize),
tint = colorScheme.onSurfaceVariant
)
}
}
PeerAvatar(
name = baseNameRaw,
color = baseColor,
isFavorite = isFavorite,
theyFavoritedUs = theyFavoritedUs,
badge = {
when {
isConnected -> Icon(
painter = painterResource(
conversationTransportIcon(
isReachedOverInternet = false,
isWifiAware = isWifiAware,
isDirect = isDirect
)
),
contentDescription = connectionDescription,
modifier = Modifier.size(13.dp),
tint = colorScheme.primary
)
Spacer(modifier = Modifier.width(SheetRowLeadingGutter))
showNostrGlobe -> Icon(
painter = painterResource(R.drawable.ic_spec_globe),
contentDescription = stringResource(R.string.cd_reachable_via_nostr),
modifier = Modifier.size(13.dp),
tint = palette.accentPurple
)
else -> Icon(
imageVector = Icons.Outlined.Circle,
contentDescription = stringResource(R.string.cd_offline_favorite),
modifier = Modifier.size(11.dp),
tint = palette.textTertiary
)
}
}
)
Spacer(modifier = Modifier.width(12.dp))
Row(
modifier = Modifier.weight(1f),
@ -1682,21 +1633,18 @@ private fun PeerItem(
}
}
Box(
modifier = Modifier
.size(36.dp)
.clickable(onClick = onToggleFavorite),
contentAlignment = Alignment.Center
) {
// Three-state star (matches private-chat header): grey outline (no relation),
// orange outline (they favorited us), filled orange (we favorited them).
Icon(
painter = painterResource(
if (isFavorite) R.drawable.ic_spec_star_filled else R.drawable.ic_spec_star
),
contentDescription = if (isFavorite) "Remove from favorites" else "Add to favorites",
modifier = Modifier.size(PeerRowIconSize),
tint = if (isFavorite || theyFavoritedUs) palette.accentOrange else palette.textTertiary
UnreadBadge(
count = unreadCount,
colorScheme = colorScheme,
modifier = Modifier.padding(start = 4.dp)
)
if (isSelected) {
Box(
modifier = Modifier
.padding(start = 8.dp)
.size(SheetRowSelectedDot)
.background(colorScheme.primary, CircleShape)
)
}
}

View File

@ -0,0 +1,105 @@
package com.bitchat.android.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.bitchat.android.R
import com.bitchat.android.ui.theme.BitchatFontFamily
import com.bitchat.android.ui.theme.LocalBitchatPalette
internal val PeerAvatarBadgeSize = 18.dp
private val PeerAvatarStarSize = 16.dp
@Composable
internal fun PeerAvatar(
name: String,
color: Color,
modifier: Modifier = Modifier,
isFavorite: Boolean = false,
theyFavoritedUs: Boolean = false,
badge: (@Composable () -> Unit)? = null
) {
val palette = LocalBitchatPalette.current
val colorScheme = MaterialTheme.colorScheme
Box(
modifier = modifier.size(42.dp),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.size(38.dp)
.background(color.copy(alpha = 0.16f), CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = name.trim().firstOrNull()?.uppercase() ?: "#",
style = MaterialTheme.typography.titleMedium.copy(
fontFamily = BitchatFontFamily,
fontWeight = FontWeight.SemiBold
),
color = color
)
}
if (badge != null) {
Surface(
modifier = Modifier
.size(PeerAvatarBadgeSize)
.align(Alignment.BottomEnd),
shape = CircleShape,
color = colorScheme.surface,
tonalElevation = 1.dp
) {
Box(contentAlignment = Alignment.Center) {
badge()
}
}
}
if (isFavorite || theyFavoritedUs) {
Surface(
modifier = Modifier
.size(PeerAvatarStarSize)
.align(Alignment.TopEnd),
shape = CircleShape,
color = colorScheme.surface,
tonalElevation = 1.dp
) {
Box(contentAlignment = Alignment.Center) {
Icon(
painter = painterResource(
if (isFavorite) {
R.drawable.ic_spec_star_filled
} else {
R.drawable.ic_spec_star
}
),
contentDescription = stringResource(
if (isFavorite) {
R.string.cd_favorite
} else {
R.string.cd_favorited_you
}
),
modifier = Modifier.size(10.dp),
tint = palette.accentOrange
)
}
}
}
}
}

View File

@ -69,6 +69,8 @@
<!-- Favorites accessibility -->
<string name="cd_add_favorite">Add to favorites</string>
<string name="cd_remove_favorite">Remove from favorites</string>
<string name="cd_favorite">Favorite</string>
<string name="cd_favorited_you">Favorited you</string>
<string name="cd_add_bookmark">Add bookmark</string>
<!-- Chat header &amp; accessibility -->

View File

@ -7,11 +7,37 @@ plugins {
alias(libs.plugins.kotlin.compose) apply false
}
val resolveIdeRuntimeClasspathCopyLocks = tasks.register("resolveIdeRuntimeClasspathCopyLocks") {
group = "build setup"
description = "Resolves Android Studio runtime classpath copies when refreshing lock state."
}
subprojects {
dependencyLocking {
lockAllConfigurations()
lockMode.set(LockMode.STRICT)
}
pluginManager.withPlugin("com.android.application") {
val resolveIdeRuntimeClasspathCopyLock = tasks.register("resolveIdeRuntimeClasspathCopyLock") {
group = "build setup"
description = "Resolves this module's Android Studio runtime classpath copies."
notCompatibleWithConfigurationCache("Resolves copied configurations at execution time")
doFirst {
check(gradle.startParameter.isWriteDependencyLocks) {
"$path must be run with --write-locks"
}
}
doLast {
listOf("debugRuntimeClasspath", "releaseRuntimeClasspath").forEach { configurationName ->
configurations.getByName(configurationName).copy().resolve()
}
}
}
resolveIdeRuntimeClasspathCopyLocks.configure {
dependsOn(resolveIdeRuntimeClasspathCopyLock)
}
}
}
tasks.whenTaskAdded {

View File

@ -224,11 +224,16 @@ Dependency changes must update and review both the lock state and verification
metadata:
```bash
./gradlew testDebugUnitTest lintDebug \
./gradlew testDebugUnitTest lintDebug resolveIdeRuntimeClasspathCopyLocks \
--write-locks \
--write-verification-metadata sha256
```
`resolveIdeRuntimeClasspathCopyLocks` records the transient runtime classpath
copies that Android Studio resolves during model import. Their selected versions
are persisted under the generated copy configuration names while the canonical
debug and release runtime classpaths remain strictly locked.
Generate release lock entries in separate invocations because split APK and AAB
intermediates cannot coexist:
@ -245,6 +250,11 @@ Review every new repository, component, artifact name, version, and checksum.
Do not accept verification metadata generated after an unexplained checksum
failure.
The verification metadata deliberately trusts only IDE documentation and source
attachments (`*-javadoc.jar`, `*-sources.jar`, and Gradle's `*-src.zip`). Android
Studio resolves these outside the build dependency graph, and they are not build
inputs. Compiled artifacts and dependency metadata remain checksum-verified.
When changing Gradle, update the wrapper and independently verify the new
distribution SHA-256. When changing JDK or Android tools, update the exact
version, archive checksum, and base-image digest together. Native updates follow

View File

@ -3,6 +3,11 @@
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>false</verify-signatures>
<trusted-artifacts>
<trust file=".*-javadoc[.]jar" regex="true" reason="IDE documentation attachments are not build inputs"/>
<trust file=".*-sources[.]jar" regex="true" reason="IDE source attachments are not build inputs"/>
<trust file=".*-src[.]zip" regex="true" reason="Gradle IDE source attachments are not build inputs"/>
</trusted-artifacts>
</configuration>
<components>
<component group="androidx.activity" name="activity" version="1.13.0">

View File

@ -339,7 +339,7 @@ org.jetbrains.kotlin:kotlin-daemon-client:2.4.10=kotlinBuildToolsApiClasspath,ko
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.4.0=kotlinAbiValidationCompatClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
org.jetbrains.kotlin:kotlin-parcelize-compiler:2.4.10=kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugRuntimeClasspathCopy,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseRuntimeClasspathCopy
org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
org.jetbrains.kotlin:kotlin-reflect:1.8.21=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlin:kotlin-reflect:2.1.20=debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
@ -359,7 +359,7 @@ org.jetbrains.kotlin:kotlin-stdlib:1.9.0=unified-test-platform-android-driver-in
org.jetbrains.kotlin:kotlin-stdlib:2.2.10=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=composeMappingProducerClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.4.0=kotlinAbiValidationCompatClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugRuntimeClasspathCopy,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseRuntimeClasspathCopy
org.jetbrains.kotlin:kotlin-tooling-core:2.4.10=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
org.jetbrains.kotlinx:atomicfu-jvm:0.22.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlinx:atomicfu:0.22.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher
@ -379,7 +379,7 @@ org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0=debugUnitTestCompileClasspa
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains:annotations:13.0=composeMappingProducerClasspath,kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
org.jetbrains:annotations:13.0=composeMappingProducerClasspath,debugRuntimeClasspathCopy,kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,releaseRuntimeClasspathCopy
org.jetbrains:annotations:23.0.0=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jspecify:jspecify:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jvnet.staxex:stax-ex:1.8.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle