OXYGENHUB
# 144cbf7bf2b5 oxygenhub-android / app / src / main / java / io / oxygenhub / android / ui / dashboard / DashboardScreen.kt
5.3 KiB · last changed Sign-in, dashboard, repositories, issues and inbox screens, adaptive (refs #2) 11 days ago source blame history raw
1
package io.oxygenhub.android.ui.dashboard
2
3
import androidx.compose.foundation.clickable
4
import androidx.compose.foundation.layout.Arrangement
5
import androidx.compose.foundation.layout.BoxWithConstraints
6
import androidx.compose.foundation.layout.Column
7
import androidx.compose.foundation.layout.Row
8
import androidx.compose.foundation.layout.Spacer
9
import androidx.compose.foundation.layout.fillMaxWidth
10
import androidx.compose.foundation.layout.height
11
import androidx.compose.foundation.layout.padding
12
import androidx.compose.foundation.rememberScrollState
13
import androidx.compose.foundation.verticalScroll
14
import androidx.compose.material.icons.Icons
15
import androidx.compose.material.icons.outlined.Inbox
16
import androidx.compose.material.icons.outlined.MarkEmailUnread
17
import androidx.compose.material3.Card
18
import androidx.compose.material3.HorizontalDivider
19
import androidx.compose.material3.Icon
20
import androidx.compose.material3.ListItem
21
import androidx.compose.material3.MaterialTheme
22
import androidx.compose.material3.Text
23
import androidx.compose.material3.TextButton
24
import androidx.compose.runtime.Composable
25
import androidx.compose.runtime.getValue
26
import androidx.compose.ui.Modifier
27
import androidx.compose.ui.text.font.FontWeight
28
import androidx.compose.ui.unit.dp
29
import androidx.lifecycle.compose.collectAsStateWithLifecycle
30
import io.oxygenhub.android.api.Inbox
31
import io.oxygenhub.android.api.Repository
32
import io.oxygenhub.android.ui.common.AppScreen
33
import io.oxygenhub.android.ui.common.Load
34
import io.oxygenhub.android.ui.common.LoadContent
35
import io.oxygenhub.android.ui.repos.RepositoryRow
36
37
@Composable
38
fun DashboardScreen(
39
    vm: DashboardViewModel,
40
    hub: String,
41
    inbox: Load<Inbox>,
42
    onRefreshInbox: () -> Unit,
43
    onOpenRepository: (Repository) -> Unit,
44
    onAllRepositories: () -> Unit,
45
    onInbox: () -> Unit,
46
) {
47
    val state by vm.state.collectAsStateWithLifecycle()
48
    AppScreen(title = "Dashboard", subtitle = hub.substringAfter("://")) {
49
        LoadContent(state, onRefresh = { vm.refresh(); onRefreshInbox() }) { d ->
50
            DashboardContent(d, inbox, onOpenRepository, onAllRepositories, onInbox)
51
        }
52
    }
53
}
54
55
@Composable
56
fun DashboardContent(
57
    d: Dashboard,
58
    inbox: Load<Inbox>,
59
    onOpenRepository: (Repository) -> Unit,
60
    onAllRepositories: () -> Unit,
61
    onInbox: () -> Unit,
62
) {
63
    BoxWithConstraints {
64
        // Side by side once there is room (a tablet, or a foldable open).
65
        val wide = maxWidth >= 720.dp
66
        Column(
67
            Modifier
68
                .fillMaxWidth()
69
                .verticalScroll(rememberScrollState())
70
                .padding(16.dp),
71
        ) {
72
            Text(d.greeting, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold)
73
            Spacer(Modifier.height(16.dp))
74
            if (wide) {
75
                Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
76
                    Column(Modifier.weight(2f)) { Recent(d, onOpenRepository, onAllRepositories) }
77
                    Column(Modifier.weight(1f)) { Unread(inbox, onInbox) }
78
                }
79
            } else {
80
                Unread(inbox, onInbox)
81
                Spacer(Modifier.height(16.dp))
82
                Recent(d, onOpenRepository, onAllRepositories)
83
            }
84
        }
85
    }
86
}
87
88
@Composable
89
private fun Unread(inbox: Load<Inbox>, onInbox: () -> Unit) {
90
    Card(Modifier.fillMaxWidth().clickable(onClick = onInbox)) {
91
        ListItem(
92
            leadingContent = {
93
                Icon(if ((inbox.valueOrNull?.unread ?: 0) > 0) Icons.Outlined.MarkEmailUnread else Icons.Outlined.Inbox, contentDescription = null)
94
            },
95
            headlineContent = {
96
                Text(
97
                    when (inbox) {
98
                        Load.Loading -> "Inbox"
99
                        is Load.Failed -> "Inbox unavailable"
100
                        is Load.Ready -> when (val n = inbox.value.unread) {
101
                            0L -> "Nothing unread"
102
                            1L -> "1 unread notification"
103
                            else -> "$n unread notifications"
104
                        }
105
                    },
106
                )
107
            },
108
            supportingContent = (inbox as? Load.Failed)?.let { f -> { Text(f.message) } },
109
        )
110
    }
111
}
112
113
@Composable
114
private fun Recent(d: Dashboard, onOpenRepository: (Repository) -> Unit, onAllRepositories: () -> Unit) {
115
    Card(Modifier.fillMaxWidth()) {
116
        Text(
117
            if (d.recentAreYours) "Your recent repositories" else "Recently updated on the hub",
118
            style = MaterialTheme.typography.titleMedium,
119
            modifier = Modifier.padding(start = 16.dp, top = 16.dp, end = 16.dp),
120
        )
121
        if (!d.recentAreYours) {
122
            Text(
123
                "You have no repositories of your own yet.",
124
                style = MaterialTheme.typography.bodySmall,
125
                color = MaterialTheme.colorScheme.onSurfaceVariant,
126
                modifier = Modifier.padding(horizontal = 16.dp),
127
            )
128
        }
129
        if (d.recent.isEmpty()) {
130
            Text("No repositories to show.", modifier = Modifier.padding(16.dp))
131
        }
132
        d.recent.forEachIndexed { i, repo ->
133
            if (i > 0) HorizontalDivider()
134
            RepositoryRow(repo, onClick = { onOpenRepository(repo) })
135
        }
136
        TextButton(onClick = onAllRepositories, modifier = Modifier.padding(8.dp)) { Text("All repositories") }
137
    }
138
}