OXYGENHUB
# a49fd826e10b oxygenhub-android / app / src / main / java / io / oxygenhub / android / ui / inbox / InboxViewModel.kt
2.9 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.inbox
2
3
import androidx.lifecycle.ViewModel
4
import androidx.lifecycle.viewModelScope
5
import io.oxygenhub.android.api.HubApi
6
import io.oxygenhub.android.api.Inbox
7
import io.oxygenhub.android.api.Notification
8
import io.oxygenhub.android.api.userMessage
9
import io.oxygenhub.android.ui.common.Load
10
import io.oxygenhub.android.ui.common.load
11
import kotlinx.coroutines.CancellationException
12
import kotlinx.coroutines.Job
13
import kotlinx.coroutines.flow.MutableStateFlow
14
import kotlinx.coroutines.flow.StateFlow
15
import kotlinx.coroutines.flow.asStateFlow
16
import kotlinx.coroutines.flow.update
17
import kotlinx.coroutines.launch
18
19
/**
20
 * The inbox, shared by the inbox screen, the dashboard's unread count and
21
 * the navigation badge. Marking read shows at once and is put back if the
22
 * hub refuses it.
23
 */
24
class InboxViewModel(private val api: HubApi) : ViewModel() {
25
    private val _state = MutableStateFlow<Load<Inbox>>(Load.Loading)
26
    val state: StateFlow<Load<Inbox>> = _state.asStateFlow()
27
28
    /** A one-line message for a snackbar (a failed mark-read). */
29
    private val _message = MutableStateFlow<String?>(null)
30
    val message: StateFlow<String?> = _message.asStateFlow()
31
32
    private var job: Job? = null
33
34
    init {
35
        refresh()
36
    }
37
38
    fun refresh() {
39
        job = load(_state, job) { api.inbox(LIMIT) }
40
    }
41
42
    /** Marks [n] read (if it is not already); the caller then opens its page. */
43
    fun markRead(n: Notification) {
44
        if (n.read) return
45
        val before = _state.value
46
        edit { inbox ->
47
            inbox.copy(
48
                unread = (inbox.unread - 1).coerceAtLeast(0),
49
                items = inbox.items.map { if (it.id == n.id) it.copy(read = true) else it },
50
            )
51
        }
52
        viewModelScope.launch {
53
            try {
54
                api.markRead(n.id)
55
            } catch (e: CancellationException) {
56
                throw e
57
            } catch (e: Exception) {
58
                _state.value = before
59
                _message.value = "Couldn't mark it read: ${e.userMessage()}"
60
            }
61
        }
62
    }
63
64
    fun markAllRead() {
65
        val before = _state.value
66
        if ((before.valueOrNull?.unread ?: 0) == 0L) return
67
        edit { inbox -> inbox.copy(unread = 0, items = inbox.items.map { it.copy(read = true) }) }
68
        viewModelScope.launch {
69
            try {
70
                api.markAllRead()
71
            } catch (e: CancellationException) {
72
                throw e
73
            } catch (e: Exception) {
74
                _state.value = before
75
                _message.value = "Couldn't mark the inbox read: ${e.userMessage()}"
76
            }
77
        }
78
    }
79
80
    fun messageShown() {
81
        _message.value = null
82
    }
83
84
    private fun edit(change: (Inbox) -> Inbox) {
85
        _state.update { s -> if (s is Load.Ready) s.copy(value = change(s.value)) else s }
86
    }
87
88
    companion object {
89
        const val LIMIT = 100
90
    }
91
}