Screenshots from Compose previews, and what rendering them showed (refs #2) 0a2858c0dad3
Compose Preview Screenshot Testing renders the app with layoutlib, no emulator: sign-in, the dashboard on a phone, repositories and issues side by side on an open foldable and a tablet, the inbox dark, from a sample hub (scripts/screenshots.sh, then copied to docs/screenshots). The plugin's own reference folder is ignored: the greeting and relative times move with the clock, so it is no baseline. Rendering showed selected chips and rows in Material's lavender (the hub's schemes now set the secondary container), the device code in the muted colour, and list rows painting over their cards (now transparent). The signed-in app can start at a destination with a repository selected.
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key f4ec0f2e7863
- When
- · 2026-10-24 22:32 UTC
- Signature
- ◈ sealed by agent:claude@andymccall
- Hash
0a2858c0dad3080a6f62d02070da227f0c6b25b86ea11e199f2d047f5d656a03- Depends on
- 144cbf7bf2b5 206d05a042e7 57c0824300db 5ae4af48ddd2 9481bfa7c5d0 e0e4a1c48a77 f821ee741b9f
- Depended on by
- aa627197ec18 821035db5a1c dce8ebd2482a bfa2eff05d35 8a657957d5a0 98dbd1220929 ff65ea456ff1 4aee5293461d d1f635be9442 0381bd8af55e 3f3508bd5f16
- In bookmarks
- main
- In tags
- v0.1.0 v0.1.1 v0.1.2 v0.1.3 v0.1.4 v0.1.5 v0.1.6 v0.1.7 v0.1.8 v0.1.9
- Edits
- 31 atoms, 7 tree operations, 14 files
Browse the tree at this change
added app/src/screenshotTest/kotlin/io/oxygenhub/android/Screenshots.kt +128 −0
| 1 | + | package io.oxygenhub.android |
|
| 2 | + | ||
| 3 | + | import android.content.res.Configuration |
|
| 4 | + | import androidx.compose.runtime.Composable |
|
| 5 | + | import androidx.compose.runtime.remember |
|
| 6 | + | import androidx.compose.ui.tooling.preview.Preview |
|
| 7 | + | import com.android.tools.screenshot.PreviewTest |
|
| 8 | + | import io.oxygenhub.android.api.DeviceCode |
|
| 9 | + | import io.oxygenhub.android.api.HubApi |
|
| 10 | + | import io.oxygenhub.android.api.Inbox |
|
| 11 | + | import io.oxygenhub.android.api.Issue |
|
| 12 | + | import io.oxygenhub.android.api.IssueState |
|
| 13 | + | import io.oxygenhub.android.api.Label |
|
| 14 | + | import io.oxygenhub.android.api.Notification |
|
| 15 | + | import io.oxygenhub.android.api.Repository |
|
| 16 | + | import io.oxygenhub.android.api.User |
|
| 17 | + | import io.oxygenhub.android.api.Visibility |
|
| 18 | + | import io.oxygenhub.android.auth.Session |
|
| 19 | + | import io.oxygenhub.android.ui.nav.SignedInApp |
|
| 20 | + | import io.oxygenhub.android.ui.nav.TopLevel |
|
| 21 | + | import io.oxygenhub.android.ui.signin.SignInContent |
|
| 22 | + | import io.oxygenhub.android.ui.signin.SignInState |
|
| 23 | + | import io.oxygenhub.android.ui.theme.OxygenHubTheme |
|
| 24 | + | import kotlinx.coroutines.Dispatchers |
|
| 25 | + | import kotlinx.coroutines.test.setMain |
|
| 26 | + | ||
| 27 | + | // The README's screenshots, rendered by layoutlib with no emulator: |
|
| 28 | + | // ./gradlew :app:updateDebugScreenshotTest |
|
| 29 | + | // writes them under app/src/screenshotTestDebug/reference; scripts/screenshots.sh |
|
| 30 | + | // copies them to docs/screenshots. The data is made up. |
|
| 31 | + | ||
| 32 | + | private const val PHONE = "spec:width=411dp,height=891dp" |
|
| 33 | + | private const val FOLDABLE = "spec:width=791dp,height=820dp" |
|
| 34 | + | private const val TABLET = "spec:width=1280dp,height=800dp,dpi=240" |
|
| 35 | + | ||
| 36 | + | private val hour = 3_600_000L |
|
| 37 | + | private val day = 24 * hour |
|
| 38 | + | ||
| 39 | + | /** A hub in memory, for the pictures. */ |
|
| 40 | + | private class SampleHub : HubApi { |
|
| 41 | + | private val now = System.currentTimeMillis() |
|
| 42 | + | ||
| 43 | + | override suspend fun user() = User("ada", "Ada Lovelace") |
|
| 44 | + | ||
| 45 | + | override suspend fun repositories() = listOf( |
|
| 46 | + | Repository("ada", "engine", "The analytical engine's notes, in Oxygen", visibility = Visibility.Public, updatedMs = now - 2 * hour), |
|
| 47 | + | Repository("ada", "bernoulli", "Computing Bernoulli numbers, note G", visibility = Visibility.Private, updatedMs = now - 1 * day), |
|
| 48 | + | Repository("ada", "punch-cards", "Card decks for the Jacquard loom", updatedMs = now - 3 * day), |
|
| 49 | + | Repository("babbage", "difference-engine", "Engine No. 2", visibility = Visibility.Internal, updatedMs = now - 5 * hour), |
|
| 50 | + | Repository("ada", "letters", "Correspondence, sealed and signed", updatedMs = now - 12 * day), |
|
| 51 | + | ) |
|
| 52 | + | ||
| 53 | + | override suspend fun issues(owner: String, name: String, state: IssueState) = listOf( |
|
| 54 | + | Issue( |
|
| 55 | + | 42, "Note G: the loop that computes B7", author = "ada", pinned = true, comments = 4, |
|
| 56 | + | labels = listOf(Label("feature", "#0064e0")), createdMs = now - 3 * hour, |
|
| 57 | + | ), |
|
| 58 | + | Issue(41, "The mill overflows on forty-digit carries", author = "babbage", comments = 2, labels = listOf(Label("bug", "#c62d47")), createdMs = now - 1 * day), |
|
| 59 | + | Issue(39, "Describe the store's capacity in the README", author = "ada", labels = listOf(Label("docs", "#00b8d4")), createdMs = now - 4 * day), |
|
| 60 | + | Issue(37, "Punch-card reader skips every hundredth card", author = "menabrea", comments = 7, createdMs = now - 9 * day), |
|
| 61 | + | ).filter { state == IssueState.All || it.state == state.query } |
|
| 62 | + | ||
| 63 | + | override suspend fun inbox(limit: Int) = Inbox( |
|
| 64 | + | unread = 3, |
|
| 65 | + | items = listOf( |
|
| 66 | + | Notification(5, now - 20 * 60_000L, "proposal.review_requested", "Carry look-ahead for the mill", actor = "babbage"), |
|
| 67 | + | Notification(4, now - 2 * hour, "flow.failed", "engine main: tests failed"), |
|
| 68 | + | Notification(3, now - 5 * hour, "issue.comment", "Note G: the loop that computes B7", actor = "menabrea"), |
|
| 69 | + | Notification(2, now - 1 * day, "proposal.landed", "Store of a thousand numbers", actor = "ada", read = true), |
|
| 70 | + | Notification(1, now - 2 * day, "issue.assigned", "The mill overflows on forty-digit carries", actor = "babbage", read = true), |
|
| 71 | + | ), |
|
| 72 | + | ) |
|
| 73 | + | ||
| 74 | + | override suspend fun markRead(id: Long) {} |
|
| 75 | + | override suspend fun markAllRead() {} |
|
| 76 | + | override suspend fun revokeToken() {} |
|
| 77 | + | } |
|
| 78 | + | ||
| 79 | + | private val session = Session("https://oxygenhub.io", "ada", "", listOf("repo:read", "user:read")) |
|
| 80 | + | ||
| 81 | + | @Composable |
|
| 82 | + | private fun App(start: TopLevel, selected: String? = null, dark: Boolean = false) { |
|
| 83 | + | // layoutlib never runs the main looper, so the view models' first load |
|
| 84 | + | // would never land: run it in place (the sample hub never suspends). |
|
| 85 | + | remember { Dispatchers.setMain(Dispatchers.Unconfined) } |
|
| 86 | + | OxygenHubTheme(darkTheme = dark, dynamicColor = false) { |
|
| 87 | + | SignedInApp(session, SampleHub(), { "Hello, ada! Oxygen Native Core is active." }, {}, start, selected) |
|
| 88 | + | } |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | @PreviewTest |
|
| 92 | + | @Preview(name = "sign-in", device = PHONE, showSystemUi = false) |
|
| 93 | + | @Composable |
|
| 94 | + | fun SignInPhone() { |
|
| 95 | + | OxygenHubTheme(dynamicColor = false) { |
|
| 96 | + | SignInContent("https://oxygenhub.io", SignInState.Idle(), null, {}, {}, {}) |
|
| 97 | + | } |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | @PreviewTest |
|
| 101 | + | @Preview(name = "sign-in-code", device = PHONE, uiMode = Configuration.UI_MODE_NIGHT_YES) |
|
| 102 | + | @Composable |
|
| 103 | + | fun SignInCodeDark() { |
|
| 104 | + | val code = DeviceCode("", "BCDF-GHJK", "https://oxygenhub.io/device", "https://oxygenhub.io/device?code=BCDF-GHJK") |
|
| 105 | + | OxygenHubTheme(darkTheme = true, dynamicColor = false) { |
|
| 106 | + | SignInContent("https://oxygenhub.io", SignInState.Approving(code), null, {}, {}, {}) |
|
| 107 | + | } |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | @PreviewTest |
|
| 111 | + | @Preview(name = "dashboard-phone", device = PHONE) |
|
| 112 | + | @Composable |
|
| 113 | + | fun DashboardPhone() = App(TopLevel.Dashboard) |
|
| 114 | + | ||
| 115 | + | @PreviewTest |
|
| 116 | + | @Preview(name = "repositories-foldable", device = FOLDABLE) |
|
| 117 | + | @Composable |
|
| 118 | + | fun RepositoriesFoldable() = App(TopLevel.Repositories, selected = "ada/engine") |
|
| 119 | + | ||
| 120 | + | @PreviewTest |
|
| 121 | + | @Preview(name = "repositories-tablet-dark", device = TABLET, uiMode = Configuration.UI_MODE_NIGHT_YES) |
|
| 122 | + | @Composable |
|
| 123 | + | fun RepositoriesTabletDark() = App(TopLevel.Repositories, selected = "ada/engine", dark = true) |
|
| 124 | + | ||
| 125 | + | @PreviewTest |
|
| 126 | + | @Preview(name = "inbox-phone-dark", device = PHONE, uiMode = Configuration.UI_MODE_NIGHT_YES) |
|
| 127 | + | @Composable |
|
| 128 | + | fun InboxPhoneDark() = App(TopLevel.Inbox, dark = true) |
added scripts/screenshots.sh +14 −0
| 1 | + | #!/usr/bin/env bash |
|
| 2 | + | # Renders the README's screenshots from the previews in app/src/screenshotTest |
|
| 3 | + | # (layoutlib: no emulator or device) and copies them to docs/screenshots. |
|
| 4 | + | set -euo pipefail |
|
| 5 | + | cd "$(dirname "$0")/.." |
|
| 6 | + | ./gradlew --max-workers=2 :app:updateDebugScreenshotTest |
|
| 7 | + | src=app/src/screenshotTestDebug/reference/io/oxygenhub/android/ScreenshotsKt |
|
| 8 | + | mkdir -p docs/screenshots |
|
| 9 | + | for f in "$src"/*.png; do |
|
| 10 | + | # SignInPhone_sign-in_0eb2062e_0.png -> sign-in.png |
|
| 11 | + | name=$(basename "$f" | sed -E 's/^[^_]+_(.+)_[0-9a-f]+_[0-9]+\.png$/\1/') |
|
| 12 | + | cp "$f" "docs/screenshots/$name.png" |
|
| 13 | + | done |
|
| 14 | + | ls docs/screenshots |
modified .oxyignore +4 −0
| … | 18 unchanged lines |
||
| 19 | 19 | /.idea/navEditor.xml |
|
| 20 | 20 | /.idea/assetWizardSettings.xml |
|
| 21 | 21 | .DS_Store |
|
| 22 | + | ||
| 23 | + | # Compose screenshot references: the README copies live in docs/screenshots |
|
| 24 | + | # (scripts/screenshots.sh); these change with the clock, so are not kept. |
|
| 25 | + | /app/src/screenshotTestDebug/ |
modified app/build.gradle.kts +7 −0
| … | 4 unchanged lines |
||
| 5 | 5 | alias(libs.plugins.android.application) |
|
| 6 | 6 | alias(libs.plugins.kotlin.compose) |
|
| 7 | 7 | alias(libs.plugins.kotlin.serialization) |
|
| 8 | + | alias(libs.plugins.screenshot) |
|
| 8 | 9 | } |
|
| 9 | 10 | ||
| 10 | 11 | // The Rust core (rust-core/, over oxy-core) is built for the app's ABIs by |
|
| … | 74 unchanged lines |
||
| 85 | 86 | buildFeatures { |
|
| 86 | 87 | compose = true |
|
| 87 | 88 | } |
|
| 89 | + | // README screenshots, rendered from previews without an emulator: |
|
| 90 | + | // ./gradlew :app:updateDebugScreenshotTest (src/screenshotTest). |
|
| 91 | + | experimentalProperties["android.experimental.enableScreenshotTest"] = true |
|
| 88 | 92 | sourceSets["main"].jniLibs.directories.add(rustJniLibs.get().asFile.path) |
|
| 89 | 93 | sourceSets["main"].kotlin.directories.add(rustBindings.get().asFile.path) |
|
| 90 | 94 | } |
|
| … | 26 unchanged lines |
||
| 117 | 121 | androidTestImplementation(libs.androidx.compose.ui.test.junit4) |
|
| 118 | 122 | androidTestImplementation(libs.androidx.espresso.core) |
|
| 119 | 123 | androidTestImplementation(libs.androidx.junit) |
|
| 124 | + | screenshotTestImplementation(libs.screenshot.validation.api) |
|
| 125 | + | screenshotTestImplementation(libs.androidx.compose.ui.tooling) |
|
| 126 | + | screenshotTestImplementation(libs.kotlinx.coroutines.test) |
|
| 120 | 127 | debugImplementation(libs.androidx.compose.ui.test.manifest) |
|
| 121 | 128 | debugImplementation(libs.androidx.compose.ui.tooling) |
|
| 122 | 129 | } |
modified app/src/main/java/io/oxygenhub/android/ui/dashboard/DashboardScreen.kt +3 −0
| … | 17 unchanged lines |
||
| 18 | 18 | import androidx.compose.material3.HorizontalDivider |
|
| 19 | 19 | import androidx.compose.material3.Icon |
|
| 20 | 20 | import androidx.compose.material3.ListItem |
|
| 21 | + | import androidx.compose.material3.ListItemDefaults |
|
| 21 | 22 | import androidx.compose.material3.MaterialTheme |
|
| 22 | 23 | import androidx.compose.material3.Text |
|
| 23 | 24 | import androidx.compose.material3.TextButton |
|
| 24 | 25 | import androidx.compose.runtime.Composable |
|
| 25 | 26 | import androidx.compose.runtime.getValue |
|
| 26 | 27 | import androidx.compose.ui.Modifier |
|
| 28 | + | import androidx.compose.ui.graphics.Color |
|
| 27 | 29 | import androidx.compose.ui.text.font.FontWeight |
|
| 28 | 30 | import androidx.compose.ui.unit.dp |
|
| 29 | 31 | import androidx.lifecycle.compose.collectAsStateWithLifecycle |
|
| … | 59 unchanged lines |
||
| 89 | 91 | private fun Unread(inbox: Load<Inbox>, onInbox: () -> Unit) { |
|
| 90 | 92 | Card(Modifier.fillMaxWidth().clickable(onClick = onInbox)) { |
|
| 91 | 93 | ListItem( |
|
| 94 | + | colors = ListItemDefaults.colors(containerColor = Color.Transparent), |
|
| 92 | 95 | leadingContent = { |
|
| 93 | 96 | Icon(if ((inbox.valueOrNull?.unread ?: 0) > 0) Icons.Outlined.MarkEmailUnread else Icons.Outlined.Inbox, contentDescription = null) |
|
| 94 | 97 | }, |
|
| … | 44 unchanged lines |
modified app/src/main/java/io/oxygenhub/android/ui/nav/AppNavigation.kt +5 −2
| … | 79 unchanged lines |
||
| 80 | 80 | api: HubApi, |
|
| 81 | 81 | nativeCore: () -> String, |
|
| 82 | 82 | onSignOut: suspend () -> Unit, |
|
| 83 | + | start: TopLevel = TopLevel.Dashboard, |
|
| 84 | + | /** A repository to show beside the list from the start (`owner/name`). */ |
|
| 85 | + | selectedRepository: String? = null, |
|
| 83 | 86 | ) { |
|
| 84 | 87 | val nav = rememberNavController() |
|
| 85 | 88 | // One inbox for the badge, the dashboard's count and the inbox screen. |
|
| … | 25 unchanged lines |
||
| 111 | 114 | } |
|
| 112 | 115 | }, |
|
| 113 | 116 | ) { |
|
| 114 | - | NavHost(nav, startDestination = DashboardRoute) { |
|
| 117 | + | NavHost(nav, startDestination = start.route) { |
|
| 115 | 118 | composable<DashboardRoute> { |
|
| 116 | 119 | DashboardScreen( |
|
| 117 | 120 | vm = rememberViewModel { DashboardViewModel(api, session) }, |
|
| … | 6 unchanged lines |
||
| 124 | 127 | ) |
|
| 125 | 128 | } |
|
| 126 | 129 | composable<RepositoriesRoute> { |
|
| 127 | - | RepositoriesScreen(rememberViewModel { RepositoriesViewModel(api, session.username) }) { slug, onBack -> |
|
| 130 | + | RepositoriesScreen(rememberViewModel { RepositoriesViewModel(api, session.username) }, selectedRepository) { slug, onBack -> |
|
| 128 | 131 | val (owner, name) = slug.split('/', limit = 2) |
|
| 129 | 132 | IssuesScreen(rememberViewModel(key = "issues:$slug") { IssuesViewModel(api, owner, name) }, onBack) |
|
| 130 | 133 | } |
|
| … | 19 unchanged lines |
modified app/src/main/java/io/oxygenhub/android/ui/repos/RepositoriesScreen.kt +9 −1
| … | 21 unchanged lines |
||
| 22 | 22 | import androidx.compose.material3.adaptive.layout.AnimatedPane |
|
| 23 | 23 | import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole |
|
| 24 | 24 | import androidx.compose.material3.adaptive.layout.PaneAdaptedValue |
|
| 25 | + | import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem |
|
| 25 | 26 | import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective |
|
| 26 | 27 | import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold |
|
| 27 | 28 | import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator |
|
| … | 20 unchanged lines |
||
| 48 | 49 | @Composable |
|
| 49 | 50 | fun RepositoriesScreen( |
|
| 50 | 51 | vm: RepositoriesViewModel, |
|
| 52 | + | initialSelection: String? = null, |
|
| 51 | 53 | detail: @Composable (slug: String, onBack: (() -> Unit)?) -> Unit, |
|
| 52 | 54 | ) { |
|
| 53 | 55 | val info = currentWindowAdaptiveInfo() |
|
| 54 | 56 | val base = calculatePaneScaffoldDirective(info) |
|
| 55 | 57 | val twoPanes = info.windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND) |
|
| 56 | 58 | val directive = if (twoPanes && base.maxHorizontalPartitions < 2) base.copy(maxHorizontalPartitions = 2) else base |
|
| 57 | - | val navigator = rememberListDetailPaneScaffoldNavigator<String>(scaffoldDirective = directive) |
|
| 59 | + | val navigator = rememberListDetailPaneScaffoldNavigator( |
|
| 60 | + | scaffoldDirective = directive, |
|
| 61 | + | initialDestinationHistory = listOfNotNull( |
|
| 62 | + | ThreePaneScaffoldDestinationItem<String>(ListDetailPaneScaffoldRole.List), |
|
| 63 | + | initialSelection?.let { ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail, it) }, |
|
| 64 | + | ), |
|
| 65 | + | ) |
|
| 58 | 66 | val scope = rememberCoroutineScope() |
|
| 59 | 67 | val selected = navigator.currentDestination?.contentKey |
|
| 60 | 68 | val detailShownAlone = navigator.scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Hidden |
|
| … | 65 unchanged lines |
modified app/src/main/java/io/oxygenhub/android/ui/repos/RepositoryRow.kt +3 −1
| … | 16 unchanged lines |
||
| 17 | 17 | import androidx.compose.runtime.Composable |
|
| 18 | 18 | import androidx.compose.ui.Alignment |
|
| 19 | 19 | import androidx.compose.ui.Modifier |
|
| 20 | + | import androidx.compose.ui.graphics.Color |
|
| 20 | 21 | import androidx.compose.ui.text.style.TextOverflow |
|
| 21 | 22 | import androidx.compose.ui.unit.dp |
|
| 22 | 23 | import io.oxygenhub.android.api.Repository |
|
| … | 7 unchanged lines |
||
| 30 | 31 | colors = if (selected) { |
|
| 31 | 32 | ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.secondaryContainer) |
|
| 32 | 33 | } else { |
|
| 33 | - | ListItemDefaults.colors() |
|
| 34 | + | // Transparent: the row takes the colour of what holds it (a card, a pane). |
|
| 35 | + | ListItemDefaults.colors(containerColor = Color.Transparent) |
|
| 34 | 36 | }, |
|
| 35 | 37 | leadingContent = { Icon(Icons.Outlined.Book, contentDescription = null) }, |
|
| 36 | 38 | headlineContent = { Text(repo.slug, maxLines = 1, overflow = TextOverflow.Ellipsis) }, |
|
| … | 18 unchanged lines |
modified app/src/main/java/io/oxygenhub/android/ui/signin/SignInScreen.kt +1 −0
| … | 144 unchanged lines |
||
| 145 | 145 | ) { |
|
| 146 | 146 | Text( |
|
| 147 | 147 | code.userCode, |
|
| 148 | + | color = MaterialTheme.colorScheme.onSurface, |
|
| 148 | 149 | fontFamily = FontFamily.Monospace, |
|
| 149 | 150 | fontSize = 34.sp, |
|
| 150 | 151 | fontWeight = FontWeight.Bold, |
|
| … | 29 unchanged lines |
modified app/src/main/java/io/oxygenhub/android/ui/theme/Color.kt +4 −0
| … | 18 unchanged lines |
||
| 19 | 19 | val DarkBad = Color(0xFFFF5E7A) |
|
| 20 | 20 | val DarkWarn = Color(0xFFB48CFF) |
|
| 21 | 21 | ||
| 22 | + | // Selected chips and rows: the accent, quietly. |
|
| 23 | + | val DarkSelected = Color(0xFF123A66) |
|
| 24 | + | ||
| 22 | 25 | // Light |
|
| 23 | 26 | val LightBackground = Color(0xFFF4F7FC) |
|
| 24 | 27 | val LightSurface = Color(0xFFFFFFFF) |
|
| … | 7 unchanged lines |
||
| 32 | 35 | val LightOk = Color(0xFF1A7850) |
|
| 33 | 36 | val LightBad = Color(0xFFC62D47) |
|
| 34 | 37 | val LightWarn = Color(0xFF6B3FD1) |
|
| 38 | + | val LightSelected = Color(0xFFD7E5FB) |
modified app/src/main/java/io/oxygenhub/android/ui/theme/Theme.kt +4 −0
| … | 19 unchanged lines |
||
| 20 | 20 | onPrimaryContainer = Color.White, |
|
| 21 | 21 | secondary = DarkAccent2, |
|
| 22 | 22 | onSecondary = Color.White, |
|
| 23 | + | secondaryContainer = DarkSelected, |
|
| 24 | + | onSecondaryContainer = DarkInk, |
|
| 23 | 25 | tertiary = DarkWarn, |
|
| 24 | 26 | background = DarkBackground, |
|
| 25 | 27 | onBackground = DarkInk, |
|
| … | 18 unchanged lines |
||
| 44 | 46 | onPrimaryContainer = Color.White, |
|
| 45 | 47 | secondary = LightAccent2, |
|
| 46 | 48 | onSecondary = Color.White, |
|
| 49 | + | secondaryContainer = LightSelected, |
|
| 50 | + | onSecondaryContainer = LightInk, |
|
| 47 | 51 | tertiary = LightWarn, |
|
| 48 | 52 | background = LightBackground, |
|
| 49 | 53 | onBackground = LightInk, |
|
| … | 41 unchanged lines |
modified build.gradle.kts +1 −0
| … | 2 unchanged lines |
||
| 3 | 3 | alias(libs.plugins.android.application) apply false |
|
| 4 | 4 | alias(libs.plugins.kotlin.compose) apply false |
|
| 5 | 5 | alias(libs.plugins.kotlin.serialization) apply false |
|
| 6 | + | alias(libs.plugins.screenshot) apply false |
|
| 6 | 7 | } |
modified gradle.properties +3 −1
| … | 15 unchanged lines |
||
| 16 | 16 | # has changed. Additionally, Gradle applies performance optimizations to task execution. |
|
| 17 | 17 | org.gradle.configuration-cache=true |
|
| 18 | 18 | # Kotlin code style for this project: "official" or "obsolete": |
|
| 19 | - | kotlin.code.style=official |
|
| 19 | + | kotlin.code.style=official |
|
| 20 | + | # Compose Preview Screenshot Testing (the README's screenshots). |
|
| 21 | + | android.experimental.enableScreenshotTest=true |
modified gradle/libs.versions.toml +3 −0
| … | 13 unchanged lines |
||
| 14 | 14 | coroutines = "1.10.2" |
|
| 15 | 15 | okhttp = "4.12.0" |
|
| 16 | 16 | jna = "5.17.0" |
|
| 17 | + | screenshot = "0.0.1-alpha16" |
|
| 17 | 18 | ||
| 18 | 19 | [libraries] |
|
| 19 | 20 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } |
|
| … | 23 unchanged lines |
||
| 43 | 44 | okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } |
|
| 44 | 45 | androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } |
|
| 45 | 46 | jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" } |
|
| 47 | + | screenshot-validation-api = { group = "com.android.tools.screenshot", name = "screenshot-validation-api", version.ref = "screenshot" } |
|
| 46 | 48 | okhttp-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp" } |
|
| 47 | 49 | ||
| 48 | 50 | [plugins] |
|
| 49 | 51 | android-application = { id = "com.android.application", version.ref = "agp" } |
|
| 50 | 52 | kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } |
|
| 51 | 53 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } |
|
| 54 | + | screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot" } |