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