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