OXYGENHUB
# 26530d9758ee oxygenhub-android / app / src / main / java / io / oxygenhub / android / ui / dashboard / DashboardViewModel.kt
3.7 KiB · last changed Proposals on the dashboard (closes #18) 10 days ago source blame history raw
1
package io.oxygenhub.android.ui.dashboard
2
3
import androidx.lifecycle.ViewModel
4
import io.oxygenhub.android.api.Avatar
5
import io.oxygenhub.android.api.HubApi
6
import io.oxygenhub.android.api.Repository
7
import io.oxygenhub.android.api.User
8
import io.oxygenhub.android.api.UserProposals
9
import io.oxygenhub.android.auth.Session
10
import io.oxygenhub.android.ui.common.Avatars
11
import io.oxygenhub.android.ui.common.Load
12
import io.oxygenhub.android.ui.common.load
13
import kotlinx.coroutines.CancellationException
14
import kotlinx.coroutines.Job
15
import kotlinx.coroutines.async
16
import kotlinx.coroutines.coroutineScope
17
import kotlinx.coroutines.flow.MutableStateFlow
18
import kotlinx.coroutines.flow.StateFlow
19
import kotlinx.coroutines.flow.asStateFlow
20
import java.util.Calendar
21
22
data class Dashboard(
23
    val greeting: String,
24
    /** The account, or null when the token may not read it (no `user:read`). */
25
    val user: User?,
26
    /** The account's avatar (#13), or null while the hub has none this app can draw. */
27
    val avatar: Avatar? = null,
28
    /** Whose avatar: the signed-in account's name. */
29
    val username: String = "",
30
    /** Proposals to review and the account's own (#18); null when the hub cannot say. */
31
    val proposals: UserProposals? = null,
32
    /** The newest-updated repositories: the user's own, or everyone's when they own none. */
33
    val recent: List<Repository>,
34
    val recentAreYours: Boolean,
35
    val repositoryCount: Int,
36
)
37
38
class DashboardViewModel(
39
    private val api: HubApi,
40
    private val session: Session,
41
    private val hourOfDay: () -> Int = { Calendar.getInstance().get(Calendar.HOUR_OF_DAY) },
42
) : ViewModel() {
43
    private val _state = MutableStateFlow<Load<Dashboard>>(Load.Loading)
44
    val state: StateFlow<Load<Dashboard>> = _state.asStateFlow()
45
46
    private var job: Job? = null
47
48
    init {
49
        refresh()
50
    }
51
52
    fun refresh() {
53
        job = load(_state, job) { fetch() }
54
    }
55
56
    private suspend fun fetch(): Dashboard = coroutineScope {
57
        // The account is a nicety (a display name); the repositories are the page.
58
        val user = async {
59
            try {
60
                api.user()
61
            } catch (e: CancellationException) {
62
                throw e
63
            } catch (_: Exception) {
64
                null
65
            }
66
        }
67
        // As are the proposals: a hub without the call shows none.
68
        val proposals = async {
69
            try {
70
                api.userProposals()
71
            } catch (e: CancellationException) {
72
                throw e
73
            } catch (_: Exception) {
74
                null
75
            }
76
        }
77
        // As is the avatar: its failure leaves the last one, or the initial.
78
        val avatar = async { Avatars.load(api, session.username) }
79
        val repos = api.repositories()
80
        val me = user.await()
81
        val username = me?.username ?: session.username
82
        val mine = repos.filter { it.owner == username }
83
        val pool = mine.ifEmpty { repos }
84
        Dashboard(
85
            greeting = greeting(hourOfDay(), me?.shownName ?: session.username),
86
            user = me,
87
            avatar = avatar.await(),
88
            username = username,
89
            proposals = proposals.await(),
90
            recent = pool.sortedByDescending { it.updatedMs }.take(RECENT),
91
            recentAreYours = mine.isNotEmpty(),
92
            repositoryCount = mine.size,
93
        )
94
    }
95
96
    companion object {
97
        const val RECENT = 5
98
99
        fun greeting(hour: Int, name: String): String {
100
            val part = when (hour) {
101
                in 5..11 -> "Good morning"
102
                in 12..17 -> "Good afternoon"
103
                else -> "Good evening"
104
            }
105
            return "$part, $name"
106
        }
107
    }
108
}