OXYGENHUB
# db00632a61d6 oxygenhub-android / app / src / screenshotTest / kotlin / io / oxygenhub / android / Screenshots.kt
5.9 KiB · last changed Screenshots from Compose previews, and what rendering them showed (refs #2) 11 days ago source blame history raw
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)